diff --git a/CMakeLists.txt b/CMakeLists.txt index 85689ce..a73ea2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,8 +13,14 @@ find_package(Ruby 3.4.8 EXACT REQUIRED) include(cmake/Bundler.cmake) include(cmake/CompilerOptions.cmake) include(cmake/CPM.cmake) +include(CTest) include(cmake/dependencies.cmake) +include(cmake/softfloat.cmake) add_subdirectory(lib) add_subdirectory(sim_lib) add_subdirectory(sim_gen) + +if(UNIT_TESTS) + add_subdirectory(tests) +endif() diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake index 618e78f..4e2c33e 100644 --- a/cmake/dependencies.cmake +++ b/cmake/dependencies.cmake @@ -21,3 +21,18 @@ CPMAddPackage( GIT_TAG 12.0.0 EXCLUDE_FROM_ALL True SYSTEM True) + +# Add GTest for unit testing. +if(UNIT_TESTS) + # GoogleTest: C++ unit testing framework + CPMAddPackage( + NAME googletest + GITHUB_REPOSITORY google/googletest + GIT_TAG v1.17.0 + VERSION 1.17.0 + EXCLUDE_FROM_ALL True + SYSTEM True + OPTIONS + "INSTALL_GTEST OFF" + "gtest_force_shared_crt ON") +endif() diff --git a/cmake/softfloat.cmake b/cmake/softfloat.cmake new file mode 100644 index 0000000..39d630a --- /dev/null +++ b/cmake/softfloat.cmake @@ -0,0 +1,58 @@ +# softfloat: floating point arithmetic library +CPMAddPackage( + NAME softfloat + GITHUB_REPOSITORY ucb-bar/berkeley-softfloat-3 + GIT_TAG a0c6494cdc11865811dec815d5c0049fba9d82a8 + DOWNLOAD_ONLY YES + EXCLUDE_FROM_ALL True + SYSTEM True) + +# Generate platform.h +set(SOFTFLOAT_PLATFORM_H ${CMAKE_CURRENT_BINARY_DIR}/platform.h) +file(WRITE ${SOFTFLOAT_PLATFORM_H} +"#ifndef SOFTFLOAT_PLATFORM_H +#define SOFTFLOAT_PLATFORM_H + +#include +#include + +#endif +") + +# Collect all required source files +file(GLOB SOFTFLOAT_SRC + ${softfloat_SOURCE_DIR}/source/*.c + ${softfloat_SOURCE_DIR}/source/s_*.c + ${softfloat_SOURCE_DIR}/source/specialize/*.c +) + +# Use the RISC-V specialization so NaN handling, default-NaN values and +# float-to-int saturation match the RISC-V F/D spec (e.g. default NaN +# 0x7FC00000, i32_fromNaN 0x7FFFFFFF) rather than the 8086/x86 semantics. +file(GLOB SOFTFLOAT_PLATFORM_SRC + ${softfloat_SOURCE_DIR}/source/RISCV/*.c +) +set(SOFTFLOAT_PLATFORM_DIR ${softfloat_SOURCE_DIR}/source/RISCV) + +list(APPEND SOFTFLOAT_SRC ${SOFTFLOAT_PLATFORM_SRC}) + +# Exclude unsupported formats +list(FILTER SOFTFLOAT_SRC EXCLUDE REGEX ".*F80.*") +list(FILTER SOFTFLOAT_SRC EXCLUDE REGEX ".*F128.*") +list(FILTER SOFTFLOAT_SRC EXCLUDE REGEX ".*bf16.*") +list(FILTER SOFTFLOAT_SRC EXCLUDE REGEX ".*f16_.*") + +# Create the static library +add_library(softfloat STATIC ${SOFTFLOAT_SRC}) + +target_include_directories(softfloat PUBLIC + ${softfloat_SOURCE_DIR}/source/include + ${softfloat_SOURCE_DIR}/source + ${SOFTFLOAT_PLATFORM_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) + +target_compile_definitions(softfloat PRIVATE + SOFTFLOAT_FAST_INT64 + SOFTFLOAT_FAST_INT64 +) diff --git a/code_gen/cpp_gen.rb b/code_gen/cpp_gen.rb index 4388b34..dc668e4 100644 --- a/code_gen/cpp_gen.rb +++ b/code_gen/cpp_gen.rb @@ -1,9 +1,13 @@ require 'Utility/helper_cpp' +require 'Utility/fp_helper_cpp' +require 'code_gen/fp_cpp_gen' # frozen_string_literal: true # Semantics Generator: Converts IR to C++ code module CodeGen class CppGenerator + include FPEmitters + attr_reader :emitter, :mapping def initialize(emitter, mapping = {}) @@ -22,6 +26,20 @@ def binary_operation(emitter, operation, op_str) emitter.emit_line("#{dst} = #{src1} #{op_str} #{src2};") end + # Helpers for easier operand mapping in operations emitters + def map_n_operands(op, n) + ops = [] + (0...n).each do |i| + ops[i] = map_operand(op[:oprnds][i]) + end + ops + end + + def map_operand(op) + val = @mapping[op[:name]] || op[:name] + val.nil? ? op[:value] : val + end + def self.generate_statement(operation) emitter = Utility::GenEmitter.new CppGenerator.new(emitter, operation[:attrs][:mapping]).generate_statement(operation) @@ -36,8 +54,8 @@ def cpu_read_reg(dst) "cpu.get#{dst[:regset]}" end - def cpu_write_mem(addr, val) - "cpu.m_memory->write(#{addr}, #{val})" + def cpu_write_mem(addr, val, val_op) + "cpu.m_memory->write<#{Utility::HelperCpp.gen_small_type(val_op[:type])}>(#{addr}, #{val})" end def cpu_read_mem(dst, addr) @@ -45,6 +63,9 @@ def cpu_read_mem(dst, addr) end def generate_statement(operation) + # Extract rm from attrs if present. + rm = operation[:attrs] if operation[:attrs].is_a?(Integer) + case operation[:name] when :add binary_operation(@emitter, operation, '+') @@ -102,7 +123,6 @@ def generate_statement(operation) src_name = @mapping[operation[:oprnds][1][:name]] || operation[:oprnds][1][:name] expr = @mapping[operation[:oprnds][0][:name]] || operation[:oprnds][0][:name] expr = expr.nil? ? operation[:oprnds][0][:value] : expr - @emitter.emit_line("#{expr} = #{cpu_read_reg(src)}<#{Utility::HelperCpp.gen_small_type(src[:type])}>(#{src_name});") when :writeReg dst = operation[:oprnds][0] @@ -121,7 +141,7 @@ def generate_statement(operation) when :writeMem addr = @mapping[operation[:oprnds][0][:name]] || operation[:oprnds][0][:name] val = @mapping[operation[:oprnds][1][:name]] || operation[:oprnds][1][:name] - @emitter.emit_line("#{cpu_write_mem addr, val};") + @emitter.emit_line("#{cpu_write_mem addr, val, operation[:oprnds][1]};") when :extract dst = @mapping[operation[:oprnds][0][:name]] || operation[:oprnds][0][:name] src = @mapping[operation[:oprnds][1][:name]] || operation[:oprnds][1][:name] @@ -129,6 +149,17 @@ def generate_statement(operation) @emitter.emit_line("#{dst} = static_cast<#{Utility::HelperCpp.gen_small_type operation[:oprnds][0][:type]}>(#{src} << #{operation[:oprnds][3][:value]});") when :sysCall @emitter.emit_line('cpu.doExit();') + when :readCSR + dst = @mapping[operation[:oprnds][0][:name]] || operation[:oprnds][0][:name] + csr = @mapping[operation[:oprnds][1][:name]] || operation[:oprnds][1][:name] + csr = csr.nil? ? operation[:oprnds][1][:value] : csr + @emitter.emit_line("#{dst} = cpu.readCSR(#{csr});") + when :writeCSR + csr = @mapping[operation[:oprnds][0][:name]] || operation[:oprnds][0][:name] + csr = csr.nil? ? operation[:oprnds][0][:value] : csr + val = @mapping[operation[:oprnds][1][:name]] || operation[:oprnds][1][:name] + val = val.nil? ? operation[:oprnds][1][:value] : val + @emitter.emit_line("cpu.writeCSR(#{csr}, #{val});") when :select dst = @mapping[operation[:oprnds][0][:name]] || operation[:oprnds][0][:name] cond = @mapping[operation[:oprnds][1][:name]] || operation[:oprnds][1][:name] @@ -136,6 +167,171 @@ def generate_statement(operation) false_val = @mapping[operation[:oprnds][3][:name]] || operation[:oprnds][3][:name] @emitter.emit_line("#{dst} = #{cond} ? #{true_val} : #{false_val};") + + # Add emitters for RV64F + # Floating point arithmetic operations WITH rounding mode (SoftFloat global) + when :f32_add then emit_fp_binary('f32_add', operation, + dst_type: :f32, + src_types: %i[f32 f32], + rm: rm) + + when :f64_add then emit_fp_binary('f64_add', operation, + dst_type: :f64, + src_types: %i[f64 f64], + rm: rm) + + when :f32_sub then emit_fp_binary('f32_sub', operation, + dst_type: :f32, + src_types: %i[f32 f32], + rm: rm) + + when :f64_sub then emit_fp_binary('f64_sub', operation, + dst_type: :f64, + src_types: %i[f64 f64], + rm: rm) + + when :f32_mul then emit_fp_binary('f32_mul', operation, + dst_type: :f32, + src_types: %i[f32 f32], + rm: rm) + + when :f64_mul then emit_fp_binary('f64_mul', operation, + dst_type: :f64, + src_types: %i[f64 f64], + rm: rm) + + when :f32_div then emit_fp_binary('f32_div', operation, + dst_type: :f32, + src_types: %i[f32 f32], + rm: rm) + + when :f64_div then emit_fp_binary('f64_div', operation, + dst_type: :f64, + src_types: %i[f64 f64], + rm: rm) + when :f32_sqrt then emit_fp_unary('f32_sqrt', operation, + dst_type: :f32, + src_type: :f32, + rm: rm) + + when :f64_sqrt then emit_fp_unary('f64_sqrt', operation, + dst_type: :f64, + src_type: :f64, + rm: rm) + when :f32_mul_add then emit_fp_ternary('f32_mulAdd', operation, + dst_type: :f32, + src_types: %i[f32 f32 f32], + rm: rm) + + when :f64_mul_add then emit_fp_ternary('f64_mulAdd', operation, + dst_type: :f64, + src_types: %i[f64 f64 f64], + rm: rm) + + # Fused Multiply-Add with Negation + when :f32_mul_add_n then emit_fp_ternary('f32_mulAdd', operation, + dst_type: :f32, + src_types: %i[f32 f32 f32], + negate_src: [0, 2], + rm: rm) + + when :f64_mul_add_n then emit_fp_ternary('f64_mulAdd', operation, + dst_type: :f64, + src_types: %i[f64 f64 f64], + negate_src: [0, 2], + rm: rm) + + when :f32_mul_sub then emit_fp_ternary('f32_mulAdd', operation, + dst_type: :f32, + src_types: %i[f32 f32 f32], + negate_src: [2], + rm: rm) + + when :f64_mul_sub then emit_fp_ternary('f64_mulAdd', operation, + dst_type: :f64, + src_types: %i[f64 f64 f64], + negate_src: [2], + rm: rm) + + when :f32_mul_sub_n then emit_fp_ternary('f32_mulAdd', operation, + dst_type: :f32, + src_types: %i[f32 f32 f32], + negate_src: [0], + rm: rm) + + when :f64_mul_sub_n then emit_fp_ternary('f64_mulAdd', operation, + dst_type: :f64, + src_types: %i[f64 f64 f64], + negate_src: [0], + rm: rm) + + # Floating point comparisons (no rm) + when :f32_eq then emit_fp_binary('f32_eq', operation, + dst_type: :i32, + src_types: %i[f32 f32]) + when :f64_eq then emit_fp_binary('f64_eq', operation, + dst_type: :i32, + src_types: %i[f64 f64]) + when :f32_lt then emit_fp_binary('f32_lt', operation, + dst_type: :i32, + src_types: %i[f32 f32]) + when :f64_lt then emit_fp_binary('f64_lt', operation, + dst_type: :i32, + src_types: %i[f64 f64]) + when :f32_le then emit_fp_binary('f32_le', operation, + dst_type: :i32, + src_types: %i[f32 f32]) + when :f64_le then emit_fp_binary('f64_le', operation, + dst_type: :i32, + src_types: %i[f64 f64]) + + when :f32_min then emit_fp_min_max('f32_min', operation, type: :f32) + when :f64_min then emit_fp_min_max('f64_min', operation, type: :f64) + when :f32_max then emit_fp_min_max('f32_max', operation, type: :f32) + when :f64_max then emit_fp_min_max('f64_max', operation, type: :f64) + + # Floating point injections + when :f32_sign_injection then emit_sign_inject(operation, width: 32, mode: :copy) + when :f64_sign_injection then emit_sign_inject(operation, width: 64, mode: :copy) + + when :f32_sign_injection_n then emit_sign_inject(operation, width: 32, mode: :neg) + when :f64_sign_injection_n then emit_sign_inject(operation, width: 64, mode: :neg) + + when :f32_sign_xor then emit_sign_inject(operation, width: 32, mode: :xor) + when :f64_sign_xor then emit_sign_inject(operation, width: 64, mode: :xor) + + # Floating point conversions with rm + when :f32_to_i32 then emit_fp_to_int_conv('f32_to_i32', operation, dst_type: :i32, src_type: :f32, + rounding_mode: rm) + when :f32_to_u32 then emit_fp_to_int_conv('f32_to_ui32', operation, dst_type: :u32, src_type: :f32, + rounding_mode: rm) + when :f32_to_i64 then emit_fp_to_int_conv('f32_to_i64', operation, dst_type: :i64, src_type: :f32, + rounding_mode: rm) + when :f32_to_u64 then emit_fp_to_int_conv('f32_to_ui64', operation, dst_type: :u64, src_type: :f32, + rounding_mode: rm) + + when :i32_to_f32 then emit_fp_unary('i32_to_f32', operation, + dst_type: :f32, src_type: :i32, rm: rm) + + when :u32_to_f32 then emit_fp_unary('ui32_to_f32', operation, + dst_type: :f32, src_type: :u32, rm: rm) + + when :i64_to_f32 then emit_fp_unary('i64_to_f32', operation, + dst_type: :f32, src_type: :i64, rm: rm) + + when :u64_to_f32 then emit_fp_unary('ui64_to_f32', operation, + dst_type: :f32, src_type: :u64, rm: rm) + + # Classification + when :f32_classify + dst, src = map_n_operands(operation, 2) + @emitter.emit_line("#{dst} = kArchSupport.f32_classify(static_cast(#{src}));") + + when :f64_classify + dst, src = map_n_operands(operation, 2) + @emitter.emit_line("#{dst} = kArchSupport.f64_classify(static_cast(#{src}));") + + else raise "Unknown statement type: #{operation[:name]}, terminating program" end end end diff --git a/code_gen/fp_cpp_gen.rb b/code_gen/fp_cpp_gen.rb new file mode 100644 index 0000000..8031306 --- /dev/null +++ b/code_gen/fp_cpp_gen.rb @@ -0,0 +1,310 @@ +require 'Utility/helper_cpp' +require 'Utility/fp_helper_cpp' + +# Floating point C++ emitters, mixed into CodeGen::CppGenerator. +# Relies on @emitter/@mapping and CppGenerator#map_operand/#map_n_operands, +# so it is not usable on its own. +module CodeGen + module FPEmitters + # RISC-V RM to SoftFloat RM mapping + # RNE=0 -> softfloat_round_near_even(0), RTZ=1 -> softfloat_round_minMag(1), + # RDN=2 -> softfloat_round_min(2), RUP=3 -> softfloat_round_max(3), + # RMM=4 -> softfloat_round_near_maxMag(4), DYN=7 -> read from fcsr + SOFTFLOAT_RM_MAP = { + 0 => 'softfloat_round_near_even', + 1 => 'softfloat_round_minMag', + 2 => 'softfloat_round_min', + 3 => 'softfloat_round_max', + 4 => 'softfloat_round_near_maxMag', + 7 => nil # DYN - handled specially + }.freeze + + # Emit code to set SoftFloat rounding mode before FP operation + # For most instruction in softfloat, rounding mode is defined + # by global variable, so we have to change it before we execute. + # We also store it into a temporal variable, so we can restore it later. + def emit_rm_setup(rm, tmp_var = '_rm_save') + return unless rm # nil means no rm handling needed + + # DYN (rm == 7) means "use the rounding mode from fcsr". writeCSR keeps + # softfloat_roundingMode synced with fcsr.frm, so for DYN we leave the + # global as-is (save/restore only, no override). + rm_const = SOFTFLOAT_RM_MAP[rm] + @emitter.emit_line("uint_fast8_t #{tmp_var} = softfloat_roundingMode;") + @emitter.emit_line("softfloat_roundingMode = #{rm_const};") if rm_const + end + + # Emit code to restore SoftFloat rounding mode after FP operation + def emit_rm_restore(tmp_var = '_rm_save') + @emitter.emit_line("softfloat_roundingMode = #{tmp_var};") + end + + # Emits binary fp operation. + # This is different from RV32I binop, because: + # 1. typing is different (check Utility/fp_helper_cpp.rb) + # 2. Rounding mode has to be changed + def emit_fp_binary(opname, operation, dst_type:, src_types:, rm: nil) + dst = map_operand(operation[:oprnds][0]) + src1 = map_operand(operation[:oprnds][1]) + src2 = map_operand(operation[:oprnds][2]) + + t1 = Utility.gen_typed_tmp(src1, src_types[0]) + t2 = Utility.gen_typed_tmp(src2, src_types[1]) + tr = Utility.gen_typed_tmp(dst, dst_type) + + if Utility::FP_INFO[src_types[0]] + info = Utility::FP_INFO[src_types[0]] + @emitter.emit_line("#{info[:c_type]} #{t1} = { #{info[:unpack]}(#{src1}) };") + else + ctype = Utility::INT_INFO[src_types[0]] + @emitter.emit_line("#{ctype} #{t1} = (#{ctype})(#{src1});") + end + + if Utility::FP_INFO[src_types[1]] + info = Utility::FP_INFO[src_types[1]] + @emitter.emit_line("#{info[:c_type]} #{t2} = { #{info[:unpack]}(#{src2}) };") + else + ctype = Utility::INT_INFO[src_types[1]] + @emitter.emit_line("#{ctype} #{t2} = (#{ctype})(#{src2});") + end + + # Set rounding mode before operation if rm is specified + emit_rm_setup(rm) if rm + + if Utility::FP_INFO[dst_type] + ctype = Utility::FP_INFO[dst_type][:c_type] + @emitter.emit_line("#{ctype} #{tr} = #{opname}(#{t1}, #{t2});") + pack = Utility::FP_INFO[dst_type][:pack] + @emitter.emit_line("#{dst} = #{pack % tr};") + else + ctype = Utility::INT_INFO[dst_type] + @emitter.emit_line("#{ctype} #{tr} = #{opname}(#{t1}, #{t2});") + @emitter.emit_line("#{dst} = (uint64_t)#{tr};") + end + + # Restore rounding mode after operation if rm was set + emit_rm_restore if rm + end + + # float -> int conversions need to be handled separately, because + # they break general pattern of softfloat instructions by demanding + # rounding mode as an argument (they ignore global constant that all other + # fp functions use for some unknown reason). + def emit_fp_to_int_conv(opname, operation, dst_type:, src_type:, rounding_mode:, exact: true) + dst = map_operand(operation[:oprnds][0]) + src = map_operand(operation[:oprnds][1]) + + ts = Utility.gen_typed_tmp(src, src_type) + tr = Utility.gen_typed_tmp(dst, dst_type) + + if Utility::FP_INFO[src_type] + info = Utility::FP_INFO[src_type] + @emitter.emit_line("#{info[:c_type]} #{ts} = { #{info[:unpack]}(#{src}) };") + else + ctype = Utility::INT_INFO[src_type] + @emitter.emit_line("#{ctype} #{ts} = (#{ctype})(#{src});") + end + + exact_arg = exact ? '1' : '0' + # softfloat's fXX_to_iXX takes an explicit rounding mode argument and does + # not understand DYN(7); for DYN use the live softfloat_roundingMode global + # which writeCSR keeps synced with fcsr.frm. + sf_rm = rounding_mode == 7 ? 'softfloat_roundingMode' : rounding_mode + @emitter.emit_line("#{Utility::INT_INFO[dst_type]} #{tr} = #{opname}(#{ts}, #{sf_rm}, #{exact_arg});") + # RISC-V sign-extends all W-form (32-bit) integer results to XLEN, even + # the unsigned fcvt.wu.* variants. + if %i[i32 u32].include?(dst_type) + @emitter.emit_line("#{dst} = (uint64_t)(int64_t)(int32_t)#{tr};") + else + @emitter.emit_line("#{dst} = (uint64_t)#{tr};") + end + end + + # There is no min/max/neg functions in softfloat, + # so we have to implement them ourselves + def emit_fp_min_max(opname, operation, type:) + dst, src1, src2 = map_n_operands(operation, 3) + t1 = Utility.gen_typed_tmp(src1, type) + t2 = Utility.gen_typed_tmp(src2, type) + tr = Utility.gen_typed_tmp(dst, type) + info = Utility::FP_INFO[type] + prefix = type == :f32 ? 'f32' : 'f64' + sign_bit = type == :f32 ? 31 : 63 + + @emitter.emit_line("#{info[:c_type]} #{t1} = { #{info[:unpack]}(#{src1}) };") + @emitter.emit_line("#{info[:c_type]} #{t2} = { #{info[:unpack]}(#{src2}) };") + @emitter.emit_line("#{info[:c_type]} #{tr};") + + # Bitwise NaN detection. Couldn't find softfloat's one, also this would faster with JIT + # in comparison to function call. + if type == :f32 + @emitter.emit_line("bool _a_nan = ((#{t1}.v >> 23) & 0xFF) == 0xFF && (#{t1}.v & 0x7FFFFF) != 0;") + @emitter.emit_line("bool _b_nan = ((#{t2}.v >> 23) & 0xFF) == 0xFF && (#{t2}.v & 0x7FFFFF) != 0;") + # signaling NaN: quiet bit (mantissa MSB) clear + @emitter.emit_line("bool _a_snan = _a_nan && (#{t1}.v & 0x400000) == 0;") + @emitter.emit_line("bool _b_snan = _b_nan && (#{t2}.v & 0x400000) == 0;") + else + @emitter.emit_line("bool _a_nan = ((#{t1}.v >> 52) & 0x7FF) == 0x7FF && (#{t1}.v & 0xFFFFFFFFFFFFF) != 0;") + @emitter.emit_line("bool _b_nan = ((#{t2}.v >> 52) & 0x7FF) == 0x7FF && (#{t2}.v & 0xFFFFFFFFFFFFF) != 0;") + @emitter.emit_line("bool _a_snan = _a_nan && (#{t1}.v & 0x8000000000000ULL) == 0;") + @emitter.emit_line("bool _b_snan = _b_nan && (#{t2}.v & 0x8000000000000ULL) == 0;") + end + + # RISC-V raises the invalid-operation flag when either fmin/fmax operand + # is a signaling NaN. + @emitter.emit_line("if (_a_snan || _b_snan) softfloat_exceptionFlags |= softfloat_flag_invalid;") + + is_min = opname.include?('min') + + canonical_nan = type == :f32 ? '0x7FC00000U' : '0x7FF8000000000000ULL' + + @emitter.emit_line('{') + # if both are NaN, return the canonical quiet NaN; if only one is NaN, + # return the other (numeric) operand. + @emitter.emit_line(" if (_a_nan && _b_nan) #{tr}.v = #{canonical_nan};") + @emitter.emit_line(" else if (_a_nan) #{tr} = #{t2};") + @emitter.emit_line(" else if (_b_nan) #{tr} = #{t1};") + @emitter.emit_line(' else {') + if is_min + # smaller or neg zero + @emitter.emit_line(" if (#{prefix}_lt(#{t1}, #{t2}) || (#{prefix}_eq(#{t1}, #{t2}) && ((#{t1}.v >> #{sign_bit}) & 1))) #{tr} = #{t1};") + else + # larger or pos zero + @emitter.emit_line(" if (#{prefix}_lt(#{t2}, #{t1}) || (#{prefix}_eq(#{t1}, #{t2}) && ((#{t2}.v >> #{sign_bit}) & 1))) #{tr} = #{t1};") + end + @emitter.emit_line(" else #{tr} = #{t2};") + @emitter.emit_line(' }') + @emitter.emit_line('}') + + pack = info[:pack] + @emitter.emit_line("#{dst} = #{pack % tr};") + end + + def gen_fp_neg(tmp_name, type) + case type + when :f32 then "#{tmp_name}.v ^= 0x80000000U;" + when :f64 then "#{tmp_name}.v ^= 0x8000000000000000ULL;" + end + end + + # same as above + def emit_fp_unary(opname, operation, dst_type:, src_type:, rm: nil) + dst = map_operand(operation[:oprnds][0]) + src = map_operand(operation[:oprnds][1]) + + ts = Utility.gen_typed_tmp(src, src_type) + tr = Utility.gen_typed_tmp(dst, dst_type) + + if Utility::FP_INFO[src_type] + info = Utility::FP_INFO[src_type] + @emitter.emit_line("#{info[:c_type]} #{ts} = { #{info[:unpack]}(#{src}) };") + else + ctype = Utility::INT_INFO[src_type] + @emitter.emit_line("#{ctype} #{ts} = (#{ctype})(#{src});") + end + + # Set rounding mode before operation if rm is specified + emit_rm_setup(rm) if rm + + if Utility::FP_INFO[dst_type] + ctype = Utility::FP_INFO[dst_type][:c_type] + @emitter.emit_line("#{ctype} #{tr} = #{opname}(#{ts});") + pack = Utility::FP_INFO[dst_type][:pack] + @emitter.emit_line("#{dst} = #{pack % tr};") + else + ctype = Utility::INT_INFO[dst_type] + @emitter.emit_line("#{ctype} #{tr} = #{opname}(#{ts});") + @emitter.emit_line("#{dst} = (uint64_t)#{tr};") + end + + # Restore rounding mode after operation if rm was set + emit_rm_restore if rm + end + + # RV64F has FMA instructions that have 3 sources, so this + # new emitter is necessary. + def emit_fp_ternary(opname, operation, + dst_type:, + src_types:, + negate_src: [], + rm: nil) + dst = map_operand(operation[:oprnds][0]) + src1 = map_operand(operation[:oprnds][1]) + src2 = map_operand(operation[:oprnds][2]) + src3 = map_operand(operation[:oprnds][3]) + + srcs = [src1, src2, src3] + svars = [] + + srcs.each_with_index do |src, i| + ty = src_types[i] + t = Utility.gen_typed_tmp(src, ty) + svars << t + + if Utility::FP_INFO[ty] + info = Utility::FP_INFO[ty] + @emitter.emit_line("#{info[:c_type]} #{t} = { #{info[:unpack]}(#{src}) };") + if negate_src.include?(i) + # TODO: change for gen_fp_neg + negation = gen_fp_neg(t, ty) + @emitter.emit_line(negation) + # negf = ty == :f32 ? 'f32_neg' : 'f64_neg' + # @emitter.emit_line("#{t} = #{negf}(#{t});") + end + + else + ctype = Utility::INT_INFO[ty] + @emitter.emit_line("#{ctype} #{t} = (#{ctype})(#{src});") + + @emitter.emit_line("#{t} = -#{t};") if negate_src.include?(i) + end + end + + tr = Utility.gen_typed_tmp(dst, dst_type) + + # Set rounding mode before operation if rm is specified + emit_rm_setup(rm) if rm + + if Utility::FP_INFO[dst_type] + ctype = Utility::FP_INFO[dst_type][:c_type] + @emitter.emit_line("#{ctype} #{tr} = #{opname}(#{svars.join(', ')});") + pack = Utility::FP_INFO[dst_type][:pack] + @emitter.emit_line("#{dst} = #{pack % tr};") + else + ctype = Utility::INT_INFO[dst_type] + @emitter.emit_line("#{ctype} #{tr} = #{opname}(#{svars.join(', ')});") + @emitter.emit_line("#{dst} = (uint64_t)#{tr};") + end + + # Restore rounding mode after operation if rm was set + emit_rm_restore if rm + end + + # No softfloat lib function, so hand-written again + def emit_sign_inject(operation, width:, mode:) + dst, src1, src2 = map_n_operands(operation, 3) + + mag_mask, sign_mask = + if width == 32 + [Utility::F32_MAG_MASK, Utility::F32_SIGN_MASK] + else + [Utility::F64_MAG_MASK, Utility::F64_SIGN_MASK] + end + + sign_expr = + case mode + when :copy then "(#{src2} & #{sign_mask})" + when :neg then "(~#{src2} & #{sign_mask})" + when :xor then "(#{src1} ^ (#{src2} & #{sign_mask}))" + end + + result = if mode == :xor + sign_expr + else + "(#{src1} & #{mag_mask}) | #{sign_expr}" + end + result = "0xFFFFFFFF00000000ULL | (uint64_t)(#{result})" if width == 32 + @emitter.emit_line("#{dst} = #{result};") + end + end +end diff --git a/lib/ADL/base.rb b/lib/ADL/base.rb index 8587824..7b0852a 100644 --- a/lib/ADL/base.rb +++ b/lib/ADL/base.rb @@ -2,12 +2,15 @@ # Testing infra module SimInfra + @@architecture_name = nil + # @@instructions -array of instruction description # shows result of our tests in interactive Ruby (IRB) or standalone def self.serialize(msg= nil) require 'yaml' yaml_data = YAML.dump( { + architecture_name: @@architecture_name, regfiles: @@regfiles.map(&:to_h), instructions: @@instructions.map(&:to_h), } @@ -18,6 +21,7 @@ def self.serialize(msg= nil) def self.state yaml_data = YAML.dump( { + architecture_name: @@architecture_name, regfiles: @@regfiles.map(&:to_h), instructions: @@instructions.map(&:to_h), } @@ -25,7 +29,14 @@ def self.state end # reset state - def siminfra_reset_module_state; @@instructions = []; end + def siminfra_reset_module_state + @@instructions = [] + @@architecture_name = nil + end + + def set_architecture_name(name) + @@architecture_name = name + end # mixin for global counter, function returns 0,1,2,.... module GlobalCounter diff --git a/lib/ADL/builder.rb b/lib/ADL/builder.rb index 83d0313..991af3c 100644 --- a/lib/ADL/builder.rb +++ b/lib/ADL/builder.rb @@ -1,12 +1,12 @@ require_relative "scope" require "Utility/type" -module SimInfra +module SimInfra class IrStmt attr_reader :name, :oprnds, :attrs - def initialize(name, oprnds, attrs) - @name = name; @oprnds = oprnds; @attrs = attrs; - end + def initialize(name, oprnds, attrs) + @name = name; @oprnds = oprnds; @attrs = attrs; + end def to_h { @@ -29,7 +29,7 @@ def self.from_h(h) end # Basics -module SimInfra +module SimInfra def assert(condition, msg = nil); raise msg if !condition; end @@instructions = [] @@ -77,25 +77,25 @@ def self.from_h(h) class InstructionInfoBuilder include SimInfra - def initialize(name, feature) - @info = InstructionInfo.new(name, feature) - @info.code = Scope.new(nil) + def initialize(name, feature) + @info = InstructionInfo.new(name, feature) + @info.code = Scope.new(nil) @@interface_functions.each do |func| if !func[:return_types].empty? @info.code.instance_eval "def #{func[:name]}(*args) in_s = *args.map { |a| resolve_const(a) } - in_stmt = [tmpvar(#{func[:return_types][0]})] + in_stmt = [tmpvar(#{func[:return_types][0].inspect})] in_stmt.concat(in_s) return stmt :#{func[:name]}, in_stmt end - " + ", __FILE__, __LINE__ - 6 else @info.code.instance_eval "def #{func[:name]}(*args) in_s = *args.map { |a| resolve_const(a) } return stmt :#{func[:name]}, in_s end - " + ", __FILE__, __LINE__ - 4 end end @@ -112,7 +112,7 @@ def encoding(frmt, fields, *args) sum_bits += Utility.get_type(f.value.type).bitsize end @info.XLEN = sum_bits / 8 - @info.code.instance_eval "def xlen(); return #{@info.XLEN.to_s}; end" + @info.code.instance_eval "def xlen(); return #{@info.XLEN.to_s}; end", __FILE__, __LINE__ end attr_reader :info end @@ -136,7 +136,7 @@ def function(name, output_types = [], input_types = []) def Interface(&blck) bldr = InterfaceBuilder.new() - + bldr.instance_eval &blck end @@ -180,8 +180,8 @@ def self.from_h(h) end @@regfiles = [] - class RegisterFileBuilder - def initialize(name) + class RegisterFileBuilder + def initialize(name) @info = RegisterFileInfo.new(name) @info.regs = [] end @@ -207,6 +207,14 @@ def r32(sym, *args) @info.regs << Register.new(sym, 32, args[0] ? [args[0]] : []) end + def r64(sym, *args) + @info.regs << Register.new(sym, 64, args[0] ? [args[0]] : []) + end + + def f64(sym, *args) + @info.regs << Register.new(sym, 64, args[0] ? [args[0]] : []) + end + def zero() :zero end @@ -249,4 +257,4 @@ def asm(&block) @info.asm_str = instance_eval &block end end -end +end \ No newline at end of file diff --git a/lib/ADL/fp_ops.rb b/lib/ADL/fp_ops.rb new file mode 100644 index 0000000..5fdae3b --- /dev/null +++ b/lib/ADL/fp_ops.rb @@ -0,0 +1,60 @@ +module SimInfra + # Floating point DSL operations, mixed into Scope. + # Relies on Scope#binOp/#unOp/#ternOp for IR emission, so it is not + # usable on its own. + module FloatingPointOps + # Floating point Arithmetic + def f32_add(a, b, rm = nil) = binOp(a, b, :f32_add, rm) + def f64_add(a, b, rm = nil) = binOp(a, b, :f64_add, rm) + def f32_sub(a, b, rm = nil) = binOp(a, b, :f32_sub, rm) + def f64_sub(a, b, rm = nil) = binOp(a, b, :f64_sub, rm) + def f32_mul(a, b, rm = nil) = binOp(a, b, :f32_mul, rm) + def f64_mul(a, b, rm = nil) = binOp(a, b, :f64_mul, rm) + def f32_div(a, b, rm = nil) = binOp(a, b, :f32_div, rm) + def f64_div(a, b, rm = nil) = binOp(a, b, :f64_div, rm) + + def f32_sqrt(a, rm = nil) = unOp(a, :f32_sqrt, rm) + def f64_sqrt(a, rm = nil) = unOp(a, :f64_sqrt, rm) + + def f32_mul_add(a, b, c, rm = nil) = ternOp(a, b, c, :f32_mul_add, rm) + def f64_mul_add(a, b, c, rm = nil) = ternOp(a, b, c, :f64_mul_add, rm) + def f32_mul_sub(a, b, c, rm = nil) = ternOp(a, b, c, :f32_mul_sub, rm) + def f64_mul_sub(a, b, c, rm = nil) = ternOp(a, b, c, :f64_mul_sub, rm) + def f32_mul_add_n(a, b, c, rm = nil) = ternOp(a, b, c, :f32_mul_add_n, rm) + def f64_mul_add_n(a, b, c, rm = nil) = ternOp(a, b, c, :f64_mul_add_n, rm) + def f32_mul_sub_n(a, b, c, rm = nil) = ternOp(a, b, c, :f32_mul_sub_n, rm) + def f64_mul_sub_n(a, b, c, rm = nil) = ternOp(a, b, c, :f64_mul_sub_n, rm) + + # Minmax, sign injection + def f32_min(a, b) = binOp(a, b, :f32_min) + def f64_min(a, b) = binOp(a, b, :f64_min) + def f32_max(a, b) = binOp(a, b, :f32_max) + def f64_max(a, b) = binOp(a, b, :f64_max) + def f32_eq(a, b) = binOp(a, b, :f32_eq) + def f64_eq(a, b) = binOp(a, b, :f64_eq) + def f32_lt(a, b) = binOp(a, b, :f32_lt) + def f64_lt(a, b) = binOp(a, b, :f64_lt) + def f32_le(a, b) = binOp(a, b, :f32_le) + def f64_le(a, b) = binOp(a, b, :f64_le) + def f32_sign_injection(a, b) = binOp(a, b, :f32_sign_injection) + def f64_sign_injection(a, b) = binOp(a, b, :f64_sign_injection) + def f32_sign_injection_n(a, b) = binOp(a, b, :f32_sign_injection_n) + def f64_sign_injection_n(a, b) = binOp(a, b, :f64_sign_injection_n) + def f32_sign_xor(a, b) = binOp(a, b, :f32_sign_xor) + def f64_sign_xor(a, b) = binOp(a, b, :f64_sign_xor) + + # Conversion + def f32_to_i32(a, rm = nil) = unOp(a, :f32_to_i32, rm) + def f32_to_u32(a, rm = nil) = unOp(a, :f32_to_u32, rm) + def f32_to_i64(a, rm = nil) = unOp(a, :f32_to_i64, rm) + def f32_to_u64(a, rm = nil) = unOp(a, :f32_to_u64, rm) + def i32_to_f32(a, rm = nil) = unOp(a, :i32_to_f32, rm) + def u32_to_f32(a, rm = nil) = unOp(a, :u32_to_f32, rm) + def i64_to_f32(a, rm = nil) = unOp(a, :i64_to_f32, rm) + def u64_to_f32(a, rm = nil) = unOp(a, :u64_to_f32, rm) + + # Classification + def f32_classify(a) = unOp(a, :f32_classify) + def f64_classify(a) = unOp(a, :f64_classify) + end +end diff --git a/lib/ADL/scope.rb b/lib/ADL/scope.rb index 57d4643..4ccf39e 100644 --- a/lib/ADL/scope.rb +++ b/lib/ADL/scope.rb @@ -1,5 +1,6 @@ require_relative 'base' require_relative 'var' +require_relative 'fp_ops' require 'Utility/type' module SimInfra @@ -10,6 +11,7 @@ def assert(condition, msg = nil) class Scope include GlobalCounter # used for temp variables IDs include SimInfra + include FloatingPointOps attr_reader :tree, :vars, :parent, :mem @@ -59,34 +61,61 @@ def resolve_const(what) Constant.new(self, "const_#{next_counter}", what) if what.class == Integer end - def binOp(a, b, op) + def binOp(a, b, op, attrs = nil) binOpWType(a, b, op, - Utility.get_type(a.type).typeof == :r ? ('b' + Utility.get_type(a.type).bitsize.to_s).to_sym : a.type) + Utility.get_type(a.type).typeof == :r ? ('b' + Utility.get_type(a.type).bitsize.to_s).to_sym : a.type, + attrs) end - def binOpWType(a, b, op, t) + def binOpWType(a, b, op, t, attrs = nil) a = resolve_const(a) b = resolve_const(b) # TODO: check constant size <= bitsize(var) # assert(a.type== b.type|| a.type == :iconst || b.type== :iconst) - stmt op, [tmpvar(t), a, b] - end - - # redefine! add & sub will never be the same - def add(a, b) = binOp(a, b, :add) - def sub(a, b) = binOp(a, b, :sub) - def shl(a, b) = binOp(a, b, :shl) - def lt(a, b) = binOpWType(a, b, :lt, :b1) - def gt(a, b) = binOpWType(a, b, :gt, :b1) - def le(a, b) = binOpWType(a, b, :le, :b1) - def ge(a, b) = binOpWType(a, b, :ge, :b1) - def xor(a, b) = binOp(a, b, :xor) - def shr(a, b) = binOp(a, b, :shr) - def ashr(a, b) = binOp(a, b, :ashr) - def or(a, b) = binOp(a, b, :or) - def and(a, b) = binOp(a, b, :and) - def eq(a, b) = binOpWType(a, b, :eq, :b1) - def ne(a, b) = binOpWType(a, b, :ne, :b1) + stmt op, [tmpvar(t), a, b], attrs + end + + def getOpType(a) + Utility.get_type(a.type).typeof == :r ? ('b' + Utility.get_type(a.type).bitsize.to_s).to_sym : a.type + end + + def unOp(a, op, attrs = nil) + unOpWType(a, op, getOpType(a), attrs) + end + + def unOpWType(a, op, t, attrs = nil) + a = resolve_const(a) + stmt op, [tmpvar(t), a], attrs + end + + def ternOp(a, b, c, op, attrs = nil) + ternOpWType(a, b, c, op, getOpType(a), attrs) + end + + def ternOpWType(a, b, c, op, t, attrs = nil) + a = resolve_const(a) + b = resolve_const(b) + c = resolve_const(c) + # TODO: check constant size <= bitsize(var) + # assert(a.type== b.type|| a.type == :iconst || b.type== :iconst) + stmt op, [tmpvar(t), a, b, c], attrs + end + + # Integer arithmetic + def add(a, b, attrs = nil) = binOp(a, b, :add, attrs) + def sub(a, b, attrs = nil) = binOp(a, b, :sub, attrs) + def shl(a, b, attrs = nil) = binOp(a, b, :shl, attrs) + def lt(a, b, attrs = nil) = binOpWType(a, b, :lt, :b1, attrs) + def gt(a, b, attrs = nil) = binOpWType(a, b, :gt, :b1, attrs) + def le(a, b, attrs = nil) = binOpWType(a, b, :le, :b1, attrs) + def ge(a, b, attrs = nil) = binOpWType(a, b, :ge, :b1, attrs) + def xor(a, b, attrs = nil) = binOp(a, b, :xor, attrs) + def shr(a, b, attrs = nil) = binOp(a, b, :shr, attrs) + def ashr(a, b, attrs = nil) = binOp(a, b, :ashr, attrs) + def or(a, b, attrs = nil) = binOp(a, b, :or, attrs) + def and(a, b, attrs = nil) = binOp(a, b, :and, attrs) + def eq(a, b, attrs = nil) = binOpWType(a, b, :eq, :b1, attrs) + def ne(a, b, attrs = nil) = binOpWType(a, b, :ne, :b1, attrs) def select(p, a, b) a = resolve_const(a) @@ -153,7 +182,7 @@ def arlet(sym, regset, attrs, type, expr) def branch(expr) = stmt(:branch, [expr]) private def tmpvar(type) = var("_tmp#{next_counter}".to_sym, type) - # stmtadds statement into tree and retursoperand[0] + # stmt adds statement into tree and returns operand[0] # which result in near all cases def stmt(name, operands, attrs = nil) for i in 1...operands.length @@ -165,7 +194,11 @@ def stmt(name, operands, attrs = nil) def read_transform(operation_name, op) if op.class == Var && !op.regset.nil? - x = tmpvar(('b' + op.type.to_s[1..-1]).to_sym) + case op.regset + when :XRegs then x = tmpvar(('b' + op.type.to_s[1..-1]).to_sym) + when :FRegs then x = tmpvar(('f' + op.type.to_s[1..-1]).to_sym) + else raise 'Unknown regset' + end @tree << IrStmt.new(:readReg, [x, op], nil) x else diff --git a/lib/ADL/value.rb b/lib/ADL/value.rb index 1a6f397..73db308 100644 --- a/lib/ADL/value.rb +++ b/lib/ADL/value.rb @@ -47,4 +47,4 @@ def self.from_h(h, scope) Constant.new(scope, h[:name], h[:value]) end end -end +end \ No newline at end of file diff --git a/lib/ADL/var.rb b/lib/ADL/var.rb index 671f258..58126f0 100644 --- a/lib/ADL/var.rb +++ b/lib/ADL/var.rb @@ -55,6 +55,7 @@ def ==(other) = @scope.eq(self, other) def !=(other) = @scope.ne(self, other) def [](r, l) = @scope.extract(self, r, l) + def f = @scope.cast(self, ('f' + Utility.get_type(@type).bitsize.to_s).to_sym) def u = @scope.cast(self, ('u' + Utility.get_type(@type).bitsize.to_s).to_sym) def s = @scope.cast(self, ('s' + Utility.get_type(@type).bitsize.to_s).to_sym) def b = @scope.cast(self, ('b' + Utility.get_type(@type).bitsize.to_s).to_sym) diff --git a/lib/Target/RISC-V/64F.rb b/lib/Target/RISC-V/64F.rb new file mode 100644 index 0000000..020b5b2 --- /dev/null +++ b/lib/Target/RISC-V/64F.rb @@ -0,0 +1,436 @@ +require_relative 'fp_encoding' +require_relative '../../ADL/base' +require_relative '../../ADL/builder' + +module RV64F + include SimInfra + extend SimInfra + + RegisterFile(:FRegs) do + (0..31).each do |i| + send(:f64, :"f#{i}") + end + end + + RV_64_F_ROUNDING_MODES = { RNE: 0b000, RTZ: 0b001, RDN: 0b010, RUP: 0b011, + RMM: 0b100, DYN: 0b111 }.freeze + # Generic helper for any FP instruction with rounding mode + # Usage: fp_inst(:fadd_d) { |rm| encoding(...); asm {...}; code {...} } + def self.fp_inst(name, &block) + module_name = :RV64F + RV_64_F_ROUNDING_MODES.each do |rm_name, rm_value| + inst_name = :"#{name}_#{rm_name.to_s.downcase}" + bldr = InstructionInfoBuilder.new(inst_name, module_name) + bldr.instance_exec(rm_value, &block) + @@instructions << bldr.info + end + end + + # Basic arithmetic + fp_inst(:fadd_s) do |rm| + encoding(*format_r_fp(0b1010011, 0b0000000, rm)) + asm { 'fadd.s {frd}, {frs1}, {frs2}' } + code do + frd[] = f32_add(frs1, frs2, rm) + end + end + + fp_inst(:fadd_d) do |rm| + encoding(*format_r_fp(0b1010011, 0b0000001, rm)) + asm { 'fadd.d {frd}, {frs1}, {frs2}' } + code do + frd[] = f64_add(frs1, frs2, rm) + end + end + + fp_inst(:fsub_s) do |rm| + encoding(*format_r_fp(0b1010011, 0b0000100, rm)) + asm { 'fsub.s {frd}, {frs1}, {frs2}' } + code do + frd[] = f32_sub(frs1, frs2, rm) + end + end + + fp_inst(:fsub_d) do |rm| + encoding(*format_r_fp(0b1010011, 0b0000101, rm)) + asm { 'fsub.d {frd}, {frs1}, {frs2}' } + code do + frd[] = f64_sub(frs1, frs2, rm) + end + end + + fp_inst(:fmul_s) do |rm| + encoding(*format_r_fp(0b1010011, 0b0001000, rm)) + asm { 'fmul.s {frd}, {frs1}, {frs2}' } + code do + frd[] = f32_mul(frs1, frs2, rm) + end + end + + fp_inst(:fmul_d) do |rm| + encoding(*format_r_fp(0b1010011, 0b0001001, rm)) + asm { 'fmul.d {frd}, {frs1}, {frs2}' } + code do + frd[] = f64_mul(frs1, frs2, rm) + end + end + + fp_inst(:fdiv_s) do |rm| + encoding(*format_r_fp(0b1010011, 0b0001100, rm)) + asm { 'fdiv.s {frd}, {frs1}, {frs2}' } + code do + frd[] = f32_div(frs1, frs2, rm) + end + end + + fp_inst(:fdiv_d) do |rm| + encoding(*format_r_fp(0b1010011, 0b0001101, rm)) + asm { 'fdiv.d {frd}, {frs1}, {frs2}' } + code do + frd[] = f64_div(frs1, frs2, rm) + end + end + + fp_inst(:fsqrt_s) do |rm| + encoding(*format_r_fp(0b1010011, 0b0101100, rm)) + asm { 'fsqrt.s {frd}, {frs1}' } + code do + frd[] = f32_sqrt(frs1, rm) + end + end + + fp_inst(:fsqrt_d) do |rm| + encoding(*format_r_fp(0b1010011, 0b0101101, rm)) + asm { 'fsqrt.d {frd}, {frs1}' } + code do + frd[] = f64_sqrt(frs1, rm) + end + end + + # Memory (no rm field - unchanged) + Instruction(:flw) do + encoding(*format_i_fp(0b0000111, 0b010)) + asm { 'flw {frd}, {imm}({rs1})' } + code do + frd[] = mem[rs1 + imm, :b32] + end + end + + Instruction(:fld) do + encoding(*format_i_fp(0b0000111, 0b011)) + asm { 'fld {frd}, {imm}({rs1})' } + code do + frd[] = mem[rs1 + imm, :b64] + end + end + + Instruction(:fsw) do + encoding(*format_s_fp(0b0100111, 0b010)) + asm { 'fsw {frs2}, {imm}({rs1})' } + code do + mem[rs1 + imm] = frs2[31, 0] + end + end + + Instruction(:fsd) do + encoding(*format_s_fp(0b0100111, 0b011)) + asm { 'fsd {frs2}, {imm}({rs1})' } + code do + mem[rs1 + imm] = frs2[63, 0] + end + end + + # Fused Mul/Add + fp_inst(:fmadd_s) do |rm| + encoding(*format_r4_fp(0b1000011, 0b00, rm)) + asm { 'fmadd.s {frd}, {frs1}, {frs2}, {frs3}' } + code do + frd[] = f32_mul_add(frs1, frs2, frs3, rm) + end + end + + fp_inst(:fmadd_d) do |rm| + encoding(*format_r4_fp(0b1000011, 0b01, rm)) + asm { 'fmadd.d {frd}, {frs1}, {frs2}, {frs3}' } + code do + frd[] = f64_mul_add(frs1, frs2, frs3, rm) + end + end + + fp_inst(:fmsub_s) do |rm| + encoding(*format_r4_fp(0b1000111, 0b00, rm)) + asm { 'fmsub.s {frd}, {frs1}, {frs2}, {frs3}' } + code do + frd[] = f32_mul_sub(frs1, frs2, frs3, rm) + end + end + + fp_inst(:fmsub_d) do |rm| + encoding(*format_r4_fp(0b1000111, 0b01, rm)) + asm { 'fmsub.d {frd}, {frs1}, {frs2}, {frs3}' } + code do + frd[] = f64_mul_sub(frs1, frs2, frs3, rm) + end + end + + fp_inst(:fnmadd_s) do |rm| + encoding(*format_r4_fp(0b1001111, 0b00, rm)) + asm { 'fnmadd.s {frd}, {frs1}, {frs2}, {frs3}' } + code do + frd[] = f32_mul_add_n(frs1, frs2, frs3, rm) + end + end + + fp_inst(:fnmadd_d) do |rm| + encoding(*format_r4_fp(0b1001111, 0b01, rm)) + asm { 'fnmadd.d {frd}, {frs1}, {frs2}, {frs3}' } + code do + frd[] = f64_mul_add_n(frs1, frs2, frs3, rm) + end + end + + fp_inst(:fnmsub_s) do |rm| + encoding(*format_r4_fp(0b1001011, 0b00, rm)) + asm { 'fnmsub.s {frd}, {frs1}, {frs2}, {frs3}' } + code do + frd[] = f32_mul_sub_n(frs1, frs2, frs3, rm) + end + end + + fp_inst(:fnmsub_d) do |rm| + encoding(*format_r4_fp(0b1001011, 0b01, rm)) + asm { 'fnmsub.d {frd}, {frs1}, {frs2}, {frs3}' } + code do + frd[] = f64_mul_sub_n(frs1, frs2, frs3, rm) + end + end + + # Sign injection (no rm field - unchanged) + Instruction(:fsgnj_s) do + encoding(*format_r_fp_no_rm(0b1010011, 0b000, 0b0010000)) + asm { 'fsgnj.s {frd}, {frs1}, {frs2}' } + code do + frd[] = f32_sign_injection(frs1, frs2) + end + end + + Instruction(:fsgnj_d) do + encoding(*format_r_fp_no_rm(0b1010011, 0b000, 0b0010001)) + asm { 'fsgnj.d {frd}, {frs1}, {frs2}' } + code do + frd[] = f64_sign_injection(frs1, frs2) + end + end + + Instruction(:fsgnjn_s) do + encoding(*format_r_fp_no_rm(0b1010011, 0b001, 0b0010000)) + asm { 'fsgnjn.s {frd}, {frs1}, {frs2}' } + code do + frd[] = f32_sign_injection_n(frs1, frs2) + end + end + + Instruction(:fsgnjn_d) do + encoding(*format_r_fp_no_rm(0b1010011, 0b001, 0b0010001)) + asm { 'fsgnjn.d {frd}, {frs1}, {frs2}' } + code do + frd[] = f64_sign_injection_n(frs1, frs2) + end + end + + Instruction(:fsgnjx_s) do + encoding(*format_r_fp_no_rm(0b1010011, 0b010, 0b0010000)) + asm { 'fsgnjx.s {frd}, {frs1}, {frs2}' } + code do + frd[] = f32_sign_xor(frs1, frs2) + end + end + + Instruction(:fsgnjx_d) do + encoding(*format_r_fp_no_rm(0b1010011, 0b010, 0b0010001)) + asm { 'fsgnjx.d {frd}, {frs1}, {frs2}' } + code do + frd[] = f64_sign_xor(frs1, frs2) + end + end + + # Comparisons (no rm field - unchanged) + Instruction(:fmin_s) do + encoding(*format_r_fp_no_rm(0b1010011, 0b000, 0b0010100)) + asm { 'fmin.s {frd}, {frs1}, {frs2}' } + code do + frd[] = f32_min(frs1, frs2) + end + end + + Instruction(:fmin_d) do + encoding(*format_r_fp_no_rm(0b1010011, 0b000, 0b0010101)) + asm { 'fmin.d {frd}, {frs1}, {frs2}' } + code do + frd[] = f64_min(frs1, frs2) + end + end + + Instruction(:fmax_s) do + encoding(*format_r_fp_no_rm(0b1010011, 0b001, 0b0010100)) + asm { 'fmax.s {frd}, {frs1}, {frs2}' } + code do + frd[] = f32_max(frs1, frs2) + end + end + + Instruction(:fmax_d) do + encoding(*format_r_fp_no_rm(0b1010011, 0b001, 0b0010101)) + asm { 'fmax.d {frd}, {frs1}, {frs2}' } + code do + frd[] = f64_max(frs1, frs2) + end + end + + Instruction(:feq_s) do + encoding(*format_r_fp_comp(0b1010011, 0b010, 0b1010000)) + asm { 'feq.s {rd}, {frs1}, {frs2}' } + code do + rd[] = f32_eq(frs1, frs2) + end + end + + Instruction(:feq_d) do + encoding(*format_r_fp_comp(0b1010011, 0b010, 0b1010001)) + asm { 'feq.d {rd}, {frs1}, {frs2}' } + code do + rd[] = f64_eq(frs1, frs2) + end + end + + Instruction(:flt_s) do + encoding(*format_r_fp_comp(0b1010011, 0b001, 0b1010000)) + asm { 'flt.s {rd}, {frs1}, {frs2}' } + code do + rd[] = f32_lt(frs1, frs2) + end + end + + Instruction(:flt_d) do + encoding(*format_r_fp_comp(0b1010011, 0b001, 0b1010001)) + asm { 'flt.d {rd}, {frs1}, {frs2}' } + code do + rd[] = f64_lt(frs1, frs2) + end + end + + Instruction(:fle_s) do + encoding(*format_r_fp_comp(0b1010011, 0b000, 0b1010000)) + asm { 'fle.s {rd}, {frs1}, {frs2}' } + code do + rd[] = f32_le(frs1, frs2) + end + end + + Instruction(:fle_d) do + encoding(*format_r_fp_comp(0b1010011, 0b000, 0b1010001)) + asm { 'fle.d {rd}, {frs1}, {frs2}' } + code do + rd[] = f64_le(frs1, frs2) + end + end + + # Conversions + fp_inst(:fcvt_w_s) do |rm| + encoding(*format_r_fp_fcvt_rd(0b1010011, 0b00000, 0b1100000, rm)) + asm { 'fcvt.w.s {rd}, {frs1}' } + code do + rd[] = f32_to_i32(frs1, rm) + end + end + + fp_inst(:fcvt_wu_s) do |rm| + encoding(*format_r_fp_fcvt_rd(0b1010011, 0b00001, 0b1100000, rm)) + asm { 'fcvt.wu.s {rd}, {frs1}' } + code do + rd[] = f32_to_u32(frs1, rm) + end + end + + fp_inst(:fcvt_l_s) do |rm| + encoding(*format_r_fp_fcvt_rd(0b1010011, 0b00010, 0b1100000, rm)) + asm { 'fcvt.l.s {rd}, {frs1}' } + code do + rd[] = f32_to_i64(frs1, rm) + end + end + + fp_inst(:fcvt_lu_s) do |rm| + encoding(*format_r_fp_fcvt_rd(0b1010011, 0b00011, 0b1100000, rm)) + asm { 'fcvt.lu.s {rd}, {frs1}' } + code do + rd[] = f32_to_u64(frs1, rm) + end + end + + fp_inst(:fcvt_s_w) do |rm| + encoding(*format_r_fp_fcvt_frd(0b1010011, 0b00000, 0b1101000, rm)) + asm { 'fcvt.s.w {frd}, {rs1}' } + code do + frd[] = i32_to_f32(rs1, rm) + end + end + + fp_inst(:fcvt_s_wu) do |rm| + encoding(*format_r_fp_fcvt_frd(0b1010011, 0b00001, 0b1101000, rm)) + asm { 'fcvt.s.wu {frd}, {rs1}' } + code do + frd[] = u32_to_f32(rs1, rm) + end + end + + fp_inst(:fcvt_s_l) do |rm| + encoding(*format_r_fp_fcvt_frd(0b1010011, 0b00010, 0b1101000, rm)) + asm { 'fcvt.s.l {frd}, {rs1}' } + code do + frd[] = i64_to_f32(rs1, rm) + end + end + + fp_inst(:fcvt_s_lu) do |rm| + encoding(*format_r_fp_fcvt_frd(0b1010011, 0b00011, 0b1101000, rm)) + asm { 'fcvt.s.lu {frd}, {rs1}' } + code do + frd[] = u64_to_f32(rs1, rm) + end + end + + # Classification (no rm field - unchanged) + Instruction(:fclass_s) do + encoding(*format_r_fp_class(0b1010011, 0b001, 0b00000, 0b1110000)) + asm { 'fclass.s {rd}, {frs1}' } + code do + rd[] = f32_classify(frs1) + end + end + + Instruction(:fclass_d) do + encoding(*format_r_fp_class(0b1010011, 0b001, 0b00000, 0b1110001)) + asm { 'fclass.d {rd}, {frs1}' } + code do + rd[] = f64_classify(frs1) + end + end + + # Move (no rm field - unchanged) + Instruction(:fmv_x_w) do + encoding(*format_r_fp_no_rm_move_rd(0b1010011, 0b000, 0b1110000)) + asm { 'fmv.x.w {rd}, {frs1}' } + code do + rd[] = frs1[31, 0].s32 + end + end + + Instruction(:fmv_w_x) do + encoding(*format_r_fp_no_rm_move_frd(0b1010011, 0b000, 0b1111000)) + asm { 'fmv.w.x {frd}, {rs1}' } + code do + frd[] = rs1[31, 0] + end + end +end diff --git a/lib/Target/RISC-V/64I.rb b/lib/Target/RISC-V/64I.rb new file mode 100644 index 0000000..89fdafd --- /dev/null +++ b/lib/Target/RISC-V/64I.rb @@ -0,0 +1,434 @@ +require_relative "encoding" +require_relative "../../ADL/base" +require_relative "../../ADL/builder" + +module RV64I + include SimInfra + extend SimInfra + + set_architecture_name 'RISC-V' + + Interface { + function :sysCall + function :readCSR, [:b64], [:b64] + function :writeCSR, [], [:b64, :b64] + } + + RegisterFile(:XRegs) { + r64 :x0, zero + r64 :x1 + r64 :x2 + r64 :x3 + r64 :x4 + r64 :x5 + r64 :x6 + r64 :x7 + r64 :x8 + r64 :x9 + r64 :x10 + r64 :x11 + r64 :x12 + r64 :x13 + r64 :x14 + r64 :x15 + r64 :x16 + r64 :x17 + r64 :x18 + r64 :x19 + r64 :x20 + r64 :x21 + r64 :x22 + r64 :x23 + r64 :x24 + r64 :x25 + r64 :x26 + r64 :x27 + r64 :x28 + r64 :x29 + r64 :x30 + r64 :x31 + r64 :pc, pc + } + + Instruction(:lui) { + encoding *format_u(0b0110111) + asm { "lui {rd}, {imm}" } + code { rd[]= imm } + } + + Instruction(:auipc) { + encoding *format_u(0b0010111) + asm { "auipc {rd}, {imm}" } + code { rd[]= pc + imm } + } + + Instruction(:add) { + encoding *format_r(0b0110011, 0b000, 0b0000000) + asm { "add {rd}, {rs1}, {rs2}" } + code { rd[]= rs1.u + rs2.u } + } + + Instruction(:sub) { + encoding *format_r(0b0110011, 0b000, 0b0100000) + asm { "sub {rd}, {rs1}, {rs2}" } + code { rd[]= rs1.u - rs2.u } + } + + Instruction(:sll) { + encoding *format_r(0b0110011, 0b001, 0b0000000) + asm { "sll {rd}, {rs1}, {rs2}" } + code { rd[]= rs1.u << (rs2.u & 63) } + } + + Instruction(:slt) { + encoding *format_r(0b0110011, 0b010, 0b0000000) + asm { "slt {rd}, {rs1}, {rs2}" } + code { rd[]= (rs1.s < rs2.s).b32 } + } + + Instruction(:sltu) { + encoding *format_r(0b0110011, 0b011, 0b0000000) + asm { "sltu {rd}, {rs1}, {rs2}" } + code { rd[]= (rs1.u < rs2.u).b32 } + } + + Instruction(:xor) { + encoding *format_r(0b0110011, 0b100, 0b0000000) + asm { "xor {rd}, {rs1}, {rs2}" } + code { rd[]= rs1 ^ rs2 } + } + + Instruction(:srl) { + encoding *format_r(0b0110011, 0b101, 0b0000000) + asm { "srl {rd}, {rs1}, {rs2}" } + code { rd[]= rs1.u >> (rs2.u & 63) } + } + + Instruction(:sra) { + encoding *format_r(0b0110011, 0b101, 0b0100000) + asm { "sra {rd}, {rs1}, {rs2}" } + code { rd[]= rs1.s >> (rs2.u & 63) } + } + + Instruction(:or) { + encoding *format_r(0b0110011, 0b110, 0b0000000) + asm { "or {rd}, {rs1}, {rs2}" } + code { rd[]= rs1 | rs2 } + } + + Instruction(:and) { + encoding *format_r(0b0110011, 0b111, 0b0000000) + asm { "and {rd}, {rs1}, {rs2}" } + code { rd[]= rs1 & rs2 } + } + + Instruction(:addi) { + encoding *format_i(0b0010011, 0b000) + asm { "addi {rd}, {rs1}, {imm}" } + code { rd[]= rs1 + imm } + } + + Instruction(:slti) { + encoding *format_i(0b0010011, 0b010) + asm { "slti {rd}, {rs1}, {imm}" } + code { rd[]= (rs1.s < imm.s64).b32 } + } + + Instruction(:sltiu) { + encoding *format_i(0b0010011, 0b011) + asm { "sltiu {rd}, {rs1}, {imm}" } + code { rd[]= (rs1.u < imm.u64).b32 } + } + + Instruction(:xori) { + encoding *format_i(0b0010011, 0b100) + asm { "xori {rd}, {rs1}, {imm}" } + code { rd[]= rs1 ^ imm } + } + + Instruction(:ori) { + encoding *format_i(0b0010011, 0b110) + asm { "ori {rd}, {rs1}, {imm}" } + code { rd[]= rs1 | imm } + } + + Instruction(:andi) { + encoding *format_i(0b0010011, 0b111) + asm { "andi {rd}, {rs1}, {imm}" } + code { rd[]= rs1 & imm } + } + + Instruction(:slli) { + encoding *format_i_shift64(0b0010011, 0b001, 0b000000) + asm { "slli {rd}, {rs1}, {imm}" } + code { rd[]= rs1.u << imm } + } + + Instruction(:srli) { + encoding *format_i_shift64(0b0010011, 0b101, 0b000000) + asm { "srli {rd}, {rs1}, {imm}" } + code { rd[]= rs1.u >> imm } + } + + Instruction(:srai) { + encoding *format_i_shift64(0b0010011, 0b101, 0b010000) + asm { "srai {rd}, {rs1}, {imm}" } + code { rd[]= rs1.s >> imm } + } + + Instruction(:addiw) { + encoding *format_i(0b0011011, 0b000) + asm { "addiw {rd}, {rs1}, {imm}" } + code { rd[]= (rs1[31, 0] + imm).s32 } + } + + Instruction(:slliw) { + encoding *format_i_shiftw(0b0011011, 0b001, 0b0000000) + asm { "slliw {rd}, {rs1}, {imm}" } + code { rd[]= (rs1[31, 0] << imm).s32 } + } + + Instruction(:srliw) { + encoding *format_i_shiftw(0b0011011, 0b101, 0b0000000) + asm { "srliw {rd}, {rs1}, {imm}" } + code { rd[]= (rs1[31, 0] >> imm).s32 } + } + + Instruction(:sraiw) { + encoding *format_i_shiftw(0b0011011, 0b101, 0b0100000) + asm { "sraiw {rd}, {rs1}, {imm}" } + code { rd[]= (rs1[31, 0].s32 >> imm).s32 } + } + + Instruction(:addw) { + encoding *format_r(0b0111011, 0b000, 0b0000000) + asm { "addw {rd}, {rs1}, {rs2}" } + code { rd[]= (rs1[31, 0] + rs2[31, 0]).s32 } + } + + Instruction(:subw) { + encoding *format_r(0b0111011, 0b000, 0b0100000) + asm { "subw {rd}, {rs1}, {rs2}" } + code { rd[]= (rs1[31, 0] - rs2[31, 0]).s32 } + } + + Instruction(:sllw) { + encoding *format_r(0b0111011, 0b001, 0b0000000) + asm { "sllw {rd}, {rs1}, {rs2}" } + code { rd[]= (rs1[31, 0] << (rs2.u & 31)).s32 } + } + + Instruction(:srlw) { + encoding *format_r(0b0111011, 0b101, 0b0000000) + asm { "srlw {rd}, {rs1}, {rs2}" } + code { rd[]= (rs1[31, 0] >> (rs2.u & 31)).s32 } + } + + Instruction(:sraw) { + encoding *format_r(0b0111011, 0b101, 0b0100000) + asm { "sraw {rd}, {rs1}, {rs2}" } + code { rd[]= (rs1[31, 0].s32 >> (rs2.u & 31)).s32 } + } + + Instruction(:beq) { + encoding *format_b(0b1100011, 0b000) + asm { "beq {rs1}, {rs2}, {imm}" } + code { branch(select(rs1 == rs2, pc + imm, pc + xlen)) } + } + + Instruction(:bne) { + encoding *format_b(0b1100011, 0b001) + asm { "bne {rs1}, {rs2}, {imm}" } + code { branch(select(rs1 != rs2, pc + imm, pc + xlen)) } + } + + Instruction(:blt) { + encoding *format_b(0b1100011, 0b100) + asm { "blt {rs1}, {rs2}, {imm}" } + code { branch(select(rs1.s < rs2.s, pc + imm, pc + xlen)) } + } + + Instruction(:bge) { + encoding *format_b(0b1100011, 0b101) + asm { "bge {rs1}, {rs2}, {imm}" } + code { branch(select(rs1.s >= rs2.s, pc + imm, pc + xlen)) } + } + + Instruction(:bltu) { + encoding *format_b(0b1100011, 0b110) + asm { "bltu {rs1}, {rs2}, {imm}" } + code { branch(select(rs1.u < rs2.u, pc + imm, pc + xlen)) } + } + + Instruction(:bgeu) { + encoding *format_b(0b1100011, 0b111) + asm { "bgeu {rs1}, {rs2}, {imm}" } + code { branch(select(rs1.u >= rs2.u, pc + imm, pc + xlen)) } + } + + Instruction(:jal) { + encoding *format_j(0b1101111) + asm { "jal {rd}, {imm}" } + code { rd[]= pc + xlen; branch(pc + imm) } + } + + Instruction(:jalr) { + encoding *format_i(0b1100111, 0b000) + asm { "jalr {rd}, {rs1}, {imm}" } + code { + let :t, :b64, pc + xlen + branch((rs1 + imm) & (~1)) + rd[]= t + } + } + + Instruction(:sb) { + encoding *format_s(0b0100011, 0b000) + asm { "sb {rs2}, {imm}({rs1})" } + code { mem[rs1 + imm]= rs2[7, 0] } + } + + Instruction(:sh) { + encoding *format_s(0b0100011, 0b001) + asm { "sh {rs2}, {imm}({rs1})" } + code { mem[rs1 + imm]= rs2[15, 0] } + } + + Instruction(:sw) { + encoding *format_s(0b0100011, 0b010) + asm { "sw {rs2}, {imm}({rs1})" } + code { mem[rs1 + imm]= rs2[31, 0] } + } + + Instruction(:sd) { + encoding *format_s(0b0100011, 0b011) + asm { "sd {rs2}, {imm}({rs1})" } + code { mem[rs1 + imm]= rs2 } + } + + Instruction(:lb) { + encoding *format_i(0b0000011, 0b000) + asm { "lb {rd}, {imm}({rs1})" } + code { rd[]= mem[rs1 + imm, :b8].s32 } + } + + Instruction(:lh) { + encoding *format_i(0b0000011, 0b001) + asm { "lh {rd}, {imm}({rs1})" } + code { rd[]= mem[rs1 + imm, :b16].s32 } + } + + Instruction(:lw) { + encoding *format_i(0b0000011, 0b010) + asm { "lw {rd}, {imm}({rs1})" } + code { rd[]= mem[rs1 + imm, :b32].s32 } + } + + Instruction(:lbu) { + encoding *format_i(0b0000011, 0b100) + asm { "lbu {rd}, {imm}({rs1})" } + code { rd[]= mem[rs1 + imm, :b8].u32 } + } + + Instruction(:lhu) { + encoding *format_i(0b0000011, 0b101) + asm { "lhu {rd}, {imm}({rs1})" } + code { rd[]= mem[rs1 + imm, :b16].u32 } + } + + Instruction(:lwu) { + encoding *format_i(0b0000011, 0b110) + asm { "lwu {rd}, {imm}({rs1})" } + code { rd[]= mem[rs1 + imm, :b32].u32 } + } + + Instruction(:ld) { + encoding *format_i(0b0000011, 0b011) + asm { "ld {rd}, {imm}({rs1})" } + code { rd[]= mem[rs1 + imm, :b64] } + } + + Instruction(:ecall) { + encoding :E, [field(:c, 31, 0, 0b1110011)] + asm { "ecall" } + code { sysCall } + } + + Instruction(:ebreak) { + encoding :E, [field(:c, 31, 0, 0b100000000000001110011)] + asm { "ebreak" } + code { } + } + + Instruction(:fence) { + encoding :E, [field(:c1, 31, 28, 0b0000), field(:c2, 27, 24), field(:c3, 23, 20), field(:c4, 19, 0, 0b00000000000000001111)] + asm { "fence" } + code { } + } + + Instruction(:fence_i) { + encoding :E, [field(:c1, 31, 20), field(:c2, 19, 15), field(:c3, 14, 12, 0b001), field(:c4, 11, 7), field(:c5, 6, 0, 0b0001111)] + asm { "fence.i" } + code { } + } + + Instruction(:csrrw) { + encoding(*format_csr(0b1110011, 0b001)) + asm { "csrrw {rd}, {csr}, {rs1}" } + code { + t = readCSR(csr) + writeCSR(csr, rs1) + rd[] = t + } + } + + Instruction(:csrrs) { + encoding(*format_csr(0b1110011, 0b010)) + asm { "csrrs {rd}, {csr}, {rs1}" } + code { + t = readCSR(csr) + writeCSR(csr, t | rs1) + rd[] = t + } + } + + Instruction(:csrrc) { + encoding(*format_csr(0b1110011, 0b011)) + asm { "csrrc {rd}, {csr}, {rs1}" } + code { + t = readCSR(csr) + writeCSR(csr, t & (rs1 ^ -1)) + rd[] = t + } + } + + Instruction(:csrrwi) { + encoding(*format_csri(0b1110011, 0b101)) + asm { "csrrwi {rd}, {csr}, {zimm}" } + code { + t = readCSR(csr) + writeCSR(csr, zimm) + rd[] = t + } + } + + Instruction(:csrrsi) { + encoding(*format_csri(0b1110011, 0b110)) + asm { "csrrsi {rd}, {csr}, {zimm}" } + code { + t = readCSR(csr) + writeCSR(csr, t | zimm) + rd[] = t + } + } + + Instruction(:csrrci) { + encoding(*format_csri(0b1110011, 0b111)) + asm { "csrrci {rd}, {csr}, {zimm}" } + code { + t = readCSR(csr) + writeCSR(csr, t & (zimm ^ -1)) + rd[] = t + } + } +end diff --git a/lib/Target/RISC-V/encoding.rb b/lib/Target/RISC-V/encoding.rb index 5ff0310..be93459 100644 --- a/lib/Target/RISC-V/encoding.rb +++ b/lib/Target/RISC-V/encoding.rb @@ -26,7 +26,23 @@ def s_imm(imm) end def xreg(name) - return name, :r32, "let :#{name}, :XRegs, [:op], :r32, f_#{name}" + return name, :r64, "let :#{name}, :XRegs, [:op], :r64, f_#{name}" + end + + def shamt6_imm(imm) + return imm, :s32, "let :#{imm}, [:op], :s32, f_shamt6" + end + + def shamt5_imm(imm) + return imm, :s32, "let :#{imm}, [:op], :s32, f_shamt5" + end + + def csr_imm(imm) + return imm, :s32, "let :#{imm}, [:op], :s32, f_csr" + end + + def zimm_imm(imm) + return imm, :s32, "let :#{imm}, [:op], :s32, f_zimm" end end @@ -72,6 +88,48 @@ def format_i_shift(opcode, func3, sopcode) ], is_imm(:imm), xreg(:rs1), xreg(:rd) end + def format_i_shift64(opcode, func3, funct6) + return :I_SHIFT64, [ + field(:f_opcode, 6, 0, opcode), + field(:func3, 14, 12, func3), + field(:f_shamt6, 25, 20), + field(:f_rd, 11, 7), + field(:f_rs1, 19, 15), + field(:f_funct6, 31, 26, funct6), + ], shamt6_imm(:imm), xreg(:rs1), xreg(:rd) + end + + def format_i_shiftw(opcode, func3, funct7) + return :I_SHIFTW, [ + field(:f_opcode, 6, 0, opcode), + field(:func3, 14, 12, func3), + field(:f_shamt5, 24, 20), + field(:f_rd, 11, 7), + field(:f_rs1, 19, 15), + field(:f_funct7, 31, 25, funct7), + ], shamt5_imm(:imm), xreg(:rs1), xreg(:rd) + end + + def format_csr(opcode, func3) + return :CSR, [ + field(:f_opcode, 6, 0, opcode), + field(:f_func3, 14, 12, func3), + field(:f_rd, 11, 7), + field(:f_rs1, 19, 15), + field(:f_csr, 31, 20), + ], csr_imm(:csr), xreg(:rs1), xreg(:rd) + end + + def format_csri(opcode, func3) + return :CSRI, [ + field(:f_opcode, 6, 0, opcode), + field(:f_func3, 14, 12, func3), + field(:f_rd, 11, 7), + field(:f_zimm, 19, 15), + field(:f_csr, 31, 20), + ], csr_imm(:csr), zimm_imm(:zimm), xreg(:rd) + end + def format_b(opcode, funct3) return :B, [ field(:f_opcode, 6, 0, opcode), diff --git a/lib/Target/RISC-V/fp_encoding.rb b/lib/Target/RISC-V/fp_encoding.rb new file mode 100644 index 0000000..bc133ac --- /dev/null +++ b/lib/Target/RISC-V/fp_encoding.rb @@ -0,0 +1,131 @@ +require_relative "encoding" +require_relative "../../ADL/base" + +module SimInfra + def freg(name) + return name, :f64, "let :#{name}, :FRegs, [:op], :f64, f_#{name}" + end +end + +module SimInfra + def format_r4_fp(opcode, funct2, rm) + return :R4_FP, [ + field(:f_opcode, 6, 0, opcode), + field(:f_frd, 11, 7), + field(:f_rm, 14, 12, rm), + field(:f_frs1, 19, 15), + field(:f_frs2, 24, 20), + field(:f_frs3, 31, 27), + field(:f_funct2, 26, 25, funct2), + ], freg(:frs3), freg(:frs2), freg(:frs1), freg(:frd) + end + + def format_r_fp(opcode, funct7, rm) + return :R_FP, [ + field(:f_opcode, 6, 0, opcode), + field(:f_frd, 11, 7), + field(:f_rm, 14, 12, rm), + field(:f_frs1, 19, 15), + field(:f_frs2, 24, 20), + field(:f_funct7, 31, 25, funct7), + ], freg(:frs2), freg(:frs1), freg(:frd) + end + + def format_r_fp_comp(opcode, funct3, funct7) + return :R_FP_COMP, [ + field(:f_opcode, 6, 0, opcode), + field(:f_rd, 11, 7), + field(:f_funct3, 14, 12, funct3), + field(:f_frs1, 19, 15), + field(:f_frs2, 24, 20), + field(:f_funct7, 31, 25, funct7), + ], freg(:frs2), freg(:frs1), xreg(:rd) + end + + def format_r_fp_class(opcode, funct3, funct5, funct7) + return :R_FP_CLASS, [ + field(:f_opcode, 6, 0, opcode), + field(:f_rd, 11, 7), + field(:f_funct3, 14, 12, funct3), + field(:f_frs1, 19, 15), + field(:f_funct5, 24, 20, funct5), + field(:f_funct7, 31, 25, funct7), + ], freg(:frs1), xreg(:rd) + end + + def format_r_fp_no_rm(opcode, funct3, funct7) + return :R_FP_NO_RM, [ + field(:f_opcode, 6, 0, opcode), + field(:f_frd, 11, 7), + field(:f_funct3, 14, 12, funct3), + field(:f_frs1, 19, 15), + field(:f_frs2, 24, 20), + field(:f_funct7, 31, 25, funct7), + ], freg(:frs2), freg(:frs1), freg(:frd) + end + + def format_r_fp_fcvt_frd(opcode, funct5, funct7, rm) + return :R_FCVT, [ + field(:f_opcode, 6, 0, opcode), + field(:f_frd, 11, 7), + field(:f_rm, 14, 12, rm), + field(:f_rs1, 19, 15), + field(:f_funct5, 24, 20, funct5), + field(:f_funct7, 31, 25, funct7), + ], xreg(:rs1), freg(:frd) + end + + def format_r_fp_fcvt_rd(opcode, funct5, funct7, rm) + return :R_FCVT, [ + field(:f_opcode, 6, 0, opcode), + field(:f_rd, 11, 7), + field(:f_rm, 14, 12, rm), + field(:f_frs1, 19, 15), + field(:f_funct5, 24, 20, funct5), + field(:f_funct7, 31, 25, funct7), + ], freg(:frs1), xreg(:rd) + end + + def format_r_fp_no_rm_move_rd(opcode, funct3, funct7) + return :R_FCVT, [ + field(:f_opcode, 6, 0, opcode), + field(:f_rd, 11, 7), + field(:f_funct3, 14, 12, funct3), + field(:f_frs1, 19, 15), + field(:f_funct5, 24, 20, 0b00000), + field(:f_funct7, 31, 25, funct7), + ], freg(:frs1), xreg(:rd) + end + + def format_r_fp_no_rm_move_frd(opcode, funct3, funct7) + return :R_FCVT, [ + field(:f_opcode, 6, 0, opcode), + field(:f_frd, 11, 7), + field(:f_funct3, 14, 12, funct3), + field(:f_rs1, 19, 15), + field(:f_funct5, 24, 20, 0b00000), + field(:f_funct7, 31, 25, funct7), + ], xreg(:rs1), freg(:frd) + end + + def format_i_fp(opcode, funct3) + return :I, [ + field(:f_opcode, 6, 0, opcode), + field(:f_frd, 11, 7), + field(:f_funct3, 14, 12, funct3), + field(:f_rs1, 19, 15), + field(:f_imm, 31, 20), + ], i_imm(:imm), xreg(:rs1), freg(:frd) + end + + def format_s_fp(opcode, func3) + return :S, [ + field(:f_opcode, 6, 0, opcode), + field(:func3, 14, 12, func3), + field(:f_imm4_0, 11, 7), + field(:f_rs1, 19, 15), + field(:f_frs2, 24, 20), + field(:f_imm11_5, 31, 25), + ], s_imm(:imm), xreg(:rs1), freg(:frs2) + end +end diff --git a/lib/Utility/fp_helper_cpp.rb b/lib/Utility/fp_helper_cpp.rb new file mode 100644 index 0000000..dcf2a1d --- /dev/null +++ b/lib/Utility/fp_helper_cpp.rb @@ -0,0 +1,24 @@ +module Utility + extend Utility + FP_INFO = { + f32: { c_type: 'float32_t', unpack: '(uint32_t)', pack: '0xFFFFFFFF00000000ULL | (uint64_t)%s.v' }, + f64: { c_type: 'float64_t', unpack: '(uint64_t)', pack: '(uint64_t)%s.v' } + }.freeze + + INT_INFO = { + i32: 'int32_t', + u32: 'uint32_t', + i64: 'int64_t', + u64: 'uint64_t' + }.freeze + + def gen_typed_tmp(base, type) + "#{base}_#{type}" + end + + F32_MAG_MASK = '0x7fffffffU'.freeze + F32_SIGN_MASK = '0x80000000U'.freeze + + F64_MAG_MASK = '0x7fffffffffffffffULL'.freeze + F64_SIGN_MASK = '0x8000000000000000ULL'.freeze +end diff --git a/lib/Utility/type.rb b/lib/Utility/type.rb index c5ae344..6421d9e 100644 --- a/lib/Utility/type.rb +++ b/lib/Utility/type.rb @@ -31,12 +31,12 @@ def get_type(sym) # Checks if two symbols have the same underlying type. # If symbol is not [usbr][1-9][0-9]* the behaviour is undefined - # + # # equal_typeof :i32 :i36 -> true # equal_typeof :u32 :i36 -> false def equal_typeof(sym1, sym2) return get_type(sym1).typeof == get_type(sym2).typeof - end + end private_constant :Type module_function :get_type, :equal_typeof diff --git a/lib/ir_gen.rb b/lib/ir_gen.rb index cde48e4..ae950ac 100644 --- a/lib/ir_gen.rb +++ b/lib/ir_gen.rb @@ -3,8 +3,8 @@ require 'ADL/base' require 'ADL/builder' -require 'Target/RISC-V/32I' - +require 'Target/RISC-V/64I' +require 'Target/RISC-V/64F' require 'yaml' yaml_data = SimInfra.serialize diff --git a/ser2ruby/base2ruby.rb b/ser2ruby/base2ruby.rb index b119979..ff597bd 100644 --- a/ser2ruby/base2ruby.rb +++ b/ser2ruby/base2ruby.rb @@ -55,7 +55,7 @@ def generate_instructions(instructions) def ir2ruby(ir) isa_name = ir[:isa_name] - + regfiles = generate_regfiles(ir[:regfiles]).to_s instructions = generate_instructions(ir[:instructions]).to_s diff --git a/sim_gen/CMakeLists.txt b/sim_gen/CMakeLists.txt index db5540a..adecd01 100644 --- a/sim_gen/CMakeLists.txt +++ b/sim_gen/CMakeLists.txt @@ -36,11 +36,12 @@ add_dependencies(${PROJECT_NAME} protea_irgen) add_executable(sim ${PROTEA_SIMGEN_OUTPUT_SOURCES} ${protea_simlib_SOURCE_DIR}/elf_loader.cc ${protea_simlib_SOURCE_DIR}/base_jit.cc + ${protea_simlib_SOURCE_DIR}/riscv_arch_ops.cc ${protea_simlib_SOURCE_DIR}/sim.cc ${protea_simlib_SOURCE_DIR}/jit_factory.cc ${protea_simlib_SOURCE_DIR}/memory.cc) target_include_directories(sim PRIVATE ${CMAKE_CURRENT_BINARY_DIR} ${protea_simlib_SOURCE_DIR}) -target_link_libraries(sim elfio CLI11 fmt) +target_link_libraries(sim elfio CLI11 fmt softfloat) target_compile_features(sim PRIVATE cxx_std_23) install(TARGETS sim) diff --git a/sim_gen/CPUState/cpu_state.rb b/sim_gen/CPUState/cpu_state.rb index d75201f..aa36f06 100644 --- a/sim_gen/CPUState/cpu_state.rb +++ b/sim_gen/CPUState/cpu_state.rb @@ -123,6 +123,7 @@ def generate_do_exit_func emitter.emit_line('// Function to stop the CPU execution') emitter.emit_line('void doExit() {') emitter.increase_indent + emitter.emit_line('m_exit_code = static_cast(m_XRegs[10]);') emitter.emit_line('m_finished = true;') emitter.decrease_indent emitter.emit_line('}') @@ -154,7 +155,7 @@ module SimGen module CPUState module Header module_function - + def generate_cpu_state(input_ir) pc_decl = Helper.generate_pc_decl(input_ir[:regfiles]) pc_functions = Helper.generate_pc_functions(input_ir[:regfiles]) @@ -169,6 +170,9 @@ def generate_cpu_state(input_ir) #define GENERATED_#{input_ir[:isa_name].upcase}_CPUSTATE_HH_INCLUDED #include \"memory.hh\" +extern \"C\" { +#include \"softfloat.h\" +} #include #include @@ -194,6 +198,50 @@ class CPU final { // Finished flag bool m_finished{false}; + int m_exit_code{0}; + + // FP rounding mode (fcsr.frm); fflags are backed by softfloat_exceptionFlags + std::uint64_t m_frm{0}; + + static uint_fast8_t frmToSoftfloat(std::uint64_t frm) { + switch (frm) { + case 0: return softfloat_round_near_even; + case 1: return softfloat_round_minMag; + case 2: return softfloat_round_min; + case 3: return softfloat_round_max; + case 4: return softfloat_round_near_maxMag; + default: return softfloat_round_near_even; + } + } + + std::uint64_t readCSR(std::uint64_t csr) { + switch (csr) { + case 0x001: return softfloat_exceptionFlags & 0x1f; + case 0x002: return m_frm & 0x7; + case 0x003: return ((m_frm & 0x7) << 5) | (softfloat_exceptionFlags & 0x1f); + default: return 0; + } + } + + void writeCSR(std::uint64_t csr, std::uint64_t val) { + switch (csr) { + case 0x001: + softfloat_exceptionFlags = val & 0x1f; + break; + case 0x002: + m_frm = val & 0x7; + softfloat_roundingMode = frmToSoftfloat(m_frm); + break; + case 0x003: + softfloat_exceptionFlags = val & 0x1f; + m_frm = (val >> 5) & 0x7; + softfloat_roundingMode = frmToSoftfloat(m_frm); + break; + default: + break; + } + } + explicit CPU(Memory *mem) : m_memory(mem) {} #{setreg_funcs} diff --git a/sim_gen/Decoders/decoder.rb b/sim_gen/Decoders/decoder.rb index 4d2689f..fa5a3b3 100644 --- a/sim_gen/Decoders/decoder.rb +++ b/sim_gen/Decoders/decoder.rb @@ -3,257 +3,245 @@ require 'code_gen/cpp_gen' module SimGen - module Decoder - module Helper - module_function - - def calc_insn_mask(insn) - mask = 0 - for field in insn[:fields] - if !field[:value][:value_num].nil? - field_mask = ((1 << (field[:from] - field[:to] + 1)) - 1) << field[:to] - mask |= field_mask - end - end - mask + module Decoder + module Helper + module_function + + # SUGGESTION: add following comment for better readability + # Constant fields are identified by the non-nil value in their :value_num field. + def calc_insn_mask(insn) + mask = 0 + for field in insn[:fields] + unless field[:value][:value_num].nil? + field_mask = ((1 << (field[:from] - field[:to] + 1)) - 1) << field[:to] + mask |= field_mask + end + end + mask + end + + def calc_insn_value(insn) + value = 0 + for field in insn[:fields] + unless field[:value][:value_num].nil? + field_value = field[:value][:value_num] << field[:to] + value |= field_value + end + end + value + end + + def get_maj_range(lead_bits) + bits = lead_bits.keys.sort + best_range = [bits[0], bits[0]] + best_count = 0 + + for i in 0...bits.size + score = 0 + lsb = bits[i] + for j in i...bits.size + bit = bits[j] + break if bit != lsb + (j - i) + + score += lead_bits[bit].min + if score > best_count + best_count = score + best_range = [lsb, bit] end + end + end + best_range + end - def calc_insn_value(insn) - value = 0 - for field in insn[:fields] - if !field[:value][:value_num].nil? - field_value = field[:value][:value_num] << field[:to] - value |= field_value - end - end - value - end + def get_lead_bits(instructions, separ_mask = 0) + lead_bits = {} + max_len = instructions.map { |insn| insn[:XLEN] * 8 }.max + for bit in 0...max_len + next if separ_mask & (1 << bit) != 0 - def get_maj_range(lead_bits) - bits = lead_bits.keys.sort - best_range = [bits[0], bits[0]] - best_count = 0 - - for i in 0...bits.size - score = 0 - lsb = bits[i] - for j in i...bits.size - bit = bits[j] - if bit != lsb + (j - i) - break - end - - score += lead_bits[bit].min - if score > best_count - best_count = score - best_range = [lsb, bit] - end - end - end - best_range - end - - def get_lead_bits(instructions, separ_mask = 0) - lead_bits = {} - max_len = instructions.map { |insn| insn[:XLEN] * 8 }.max - for bit in 0...max_len - if separ_mask & (1 << bit) != 0 - next - end - - count_0 = 0 - count_1 = 0 - - for insn in instructions - insn_mask = calc_insn_mask(insn) - insn_value = calc_insn_value(insn) - - if insn_mask & (1 << bit) == 0 - next - end - - if (insn_value & (1 << bit)) != 0 - count_1 += 1 - else - count_0 += 1 - end - end - if count_0 > 0 && count_1 > 0 - lead_bits[bit] = [count_0, count_1] - end - end - lead_bits - end + count_0 = 0 + count_1 = 0 - def make_head_tree(instructions) - lead_bits = get_lead_bits(instructions) - lsb, msb = get_maj_range(lead_bits) - width = msb - lsb + 1 - - tree = {} - tree[:range] = [lsb, msb] - tree[:nodes] = {} - - for node_value in 0...(1 << width) - actual_node = node_value << lsb - subtree = {} - - result, is_leaf = make_child_tree(actual_node, ((1 << width) - 1) << lsb, instructions, subtree) - - if is_leaf - tree[:nodes][node_value] = result - elsif !subtree.empty? - tree[:nodes][node_value] = subtree - end - end - tree - end + for insn in instructions + insn_mask = calc_insn_mask(insn) + insn_value = calc_insn_value(insn) - def filter_instructions(instructions, node, separ_mask) - res = [] - for insn in instructions - insn_mask = calc_insn_mask(insn) - insn_value = calc_insn_value(insn) - if (insn_mask & separ_mask) != separ_mask - next - end - if (insn_value & separ_mask) == (node & separ_mask) - res << insn - end - end - res - end + next if insn_mask & (1 << bit) == 0 - def make_child_tree(node_value, separ_mask, instructions, subtree) - sublits = filter_instructions(instructions, node_value, separ_mask) - if sublits.empty? - return nil, false - end - if sublits.size == 1 - return sublits[0], true - end - lead_bits = get_lead_bits(sublits, separ_mask) - lsb, msb = get_maj_range(lead_bits) - width = msb - lsb + 1 - subtree[:range] = [lsb, msb] - subtree[:nodes] = {} - - new_mask = separ_mask | ((1 << width) - 1) << lsb - - for child_value in 0...(1 << width) - actual_node = node_value | child_value << lsb - child_subtree = {} - result, is_leaf = make_child_tree(actual_node, new_mask, sublits, child_subtree) - if is_leaf - subtree[:nodes][child_value] = result - elsif !child_subtree.empty? - subtree[:nodes][child_value] = child_subtree - end - end - return subtree, false + if (insn_value & (1 << bit)) != 0 + count_1 += 1 + else + count_0 += 1 end + end + lead_bits[bit] = [count_0, count_1] if count_0 > 0 && count_1 > 0 + end + lead_bits + end - def map_operands(insn) - operands = {} - cnt = 0 - for node in insn[:map][:tree] - if node[:name] == :new_var && !node[:attrs].nil? && node[:attrs].include?(:op) - operands[node[:oprnds][0][:name]] = "insn.operand#{cnt}" - cnt += 1 - end - end - operands - end + def make_head_tree(instructions) + lead_bits = get_lead_bits(instructions) + lsb, msb = get_maj_range(lead_bits) + width = msb - lsb + 1 - def generate_mapping_fields(insn) - emitter = Utility::GenEmitter.new - for node in insn[:fields] - var_type = Utility::HelperCpp.gen_type(Utility.get_type(node[:value][:type]).bitsize) - emitter.emit_line("#{var_type} #{node[:value][:name]} = slice<#{node[:from]}, #{node[:to]}>(raw_insn);") - end - emitter - end + tree = {} + tree[:range] = [lsb, msb] + tree[:nodes] = {} - def emit_binary_op(emitter, operand_map, op, dest, src1, src2) - var = operand_map[dest[:name]] || dest[:name] - expr1 = operand_map[src1[:name]] || src1[:name] - expr2 = operand_map[src2[:name]] || src2[:name] - expr1 = expr1.nil? ? src1[:value] : expr1 - expr2 = expr2.nil? ? src2[:value] : expr2 - emitter.emit_line("#{var} = #{expr1} #{op} #{expr2};") - end + for node_value in 0...(1 << width) + actual_node = node_value << lsb + subtree = {} - def generate_mapping_body(insn) - emitter = Utility::GenEmitter.new - operand_map = map_operands(insn) - gen = CodeGen::CppGenerator.new(emitter, operand_map) - for node in insn[:map][:tree] - gen.generate_statement(node) - end - emitter - end - - def generate_decoder_impl(tree) - emitter = Utility::GenEmitter.new - emitter.emit_line("switch ((raw_insn >> #{tree[:range][0]}) & 0b#{((1 << (tree[:range][1] - tree[:range][0] + 1)) - 1).to_s(2)}) {") - emitter.increase_indent - for node_value, node in tree[:nodes].to_a - emitter.emit_line("case 0b#{node_value.to_s(2)}: {") - emitter.increase_indent - if node.key?(:name) - body_emitter = Helper.generate_mapping_body(node) - - fields_emitter = Helper.generate_mapping_fields(node) - - body_emitter.increase_indent_all emitter.indent_size * emitter.indent_level - fields_emitter.increase_indent_all emitter.indent_size * emitter.indent_level - - emitter.emit_line("// Decoded instruction: #{node[:name]}") - emitter.emit_line("insn.m_opc = Opcode::k#{node[:name].to_s.upcase};") - emitter.concat(fields_emitter) - emitter.concat(body_emitter) - emitter.emit_line("return insn;") - else - rec_emitter = generate_decoder_impl(node) - rec_emitter.increase_indent_all 2 - emitter.concat(rec_emitter) - end - emitter.decrease_indent - emitter.emit_line("}") - end - emitter.decrease_indent - emitter.emit_line("}") - emitter - end + result, is_leaf = make_child_tree(actual_node, ((1 << width) - 1) << lsb, instructions, subtree) + if is_leaf + tree[:nodes][node_value] = result + elsif !subtree.empty? + tree[:nodes][node_value] = subtree + end end + tree + end + + def filter_instructions(instructions, node, separ_mask) + res = [] + for insn in instructions + insn_mask = calc_insn_mask(insn) + insn_value = calc_insn_value(insn) + relevant_mask = insn_mask & separ_mask + res << insn if (insn_value & relevant_mask) == (node & relevant_mask) + end + res + end + + def make_child_tree(node_value, separ_mask, instructions, subtree) + sublits = filter_instructions(instructions, node_value, separ_mask) + return nil, false if sublits.empty? + return sublits[0], true if sublits.size == 1 + + lead_bits = get_lead_bits(sublits, separ_mask) + lsb, msb = get_maj_range(lead_bits) + width = msb - lsb + 1 + subtree[:range] = [lsb, msb] + subtree[:nodes] = {} + + new_mask = separ_mask | ((1 << width) - 1) << lsb + + for child_value in 0...(1 << width) + actual_node = node_value | child_value << lsb + child_subtree = {} + result, is_leaf = make_child_tree(actual_node, new_mask, sublits, child_subtree) + if is_leaf + subtree[:nodes][child_value] = result + elsif !child_subtree.empty? + subtree[:nodes][child_value] = child_subtree + end + end + [subtree, false] + end + + def map_operands(insn) + operands = {} + cnt = 0 + for node in insn[:map][:tree] + if node[:name] == :new_var && !node[:attrs].nil? && node[:attrs].include?(:op) + operands[node[:oprnds][0][:name]] = "insn.operand#{cnt}" + cnt += 1 + end + end + operands + end + + def generate_mapping_fields(insn) + emitter = Utility::GenEmitter.new + for node in insn[:fields] + var_type = Utility::HelperCpp.gen_type(Utility.get_type(node[:value][:type]).bitsize) + emitter.emit_line("#{var_type} #{node[:value][:name]} = slice<#{node[:from]}, #{node[:to]}>(raw_insn);") + end + emitter + end + + def emit_binary_op(emitter, operand_map, op, dest, src1, src2) + var = operand_map[dest[:name]] || dest[:name] + expr1 = operand_map[src1[:name]] || src1[:name] + expr2 = operand_map[src2[:name]] || src2[:name] + expr1 = expr1.nil? ? src1[:value] : expr1 + expr2 = expr2.nil? ? src2[:value] : expr2 + emitter.emit_line("#{var} = #{expr1} #{op} #{expr2};") + end + + def generate_mapping_body(insn) + emitter = Utility::GenEmitter.new + operand_map = map_operands(insn) + gen = CodeGen::CppGenerator.new(emitter, operand_map) + for node in insn[:map][:tree] + gen.generate_statement(node) + end + emitter + end + + def generate_decoder_impl(tree) + emitter = Utility::GenEmitter.new + emitter.emit_line("switch ((raw_insn >> #{tree[:range][0]}) & 0b#{((1 << (tree[:range][1] - tree[:range][0] + 1)) - 1).to_s(2)}) {") + emitter.increase_indent + for node_value, node in tree[:nodes].to_a + emitter.emit_line("case 0b#{node_value.to_s(2)}: {") + emitter.increase_indent + if node.key?(:name) + body_emitter = Helper.generate_mapping_body(node) + + fields_emitter = Helper.generate_mapping_fields(node) + + body_emitter.increase_indent_all emitter.indent_size * emitter.indent_level + fields_emitter.increase_indent_all emitter.indent_size * emitter.indent_level + + emitter.emit_line("// Decoded instruction: #{node[:name]}") + emitter.emit_line("insn.m_opc = Opcode::k#{node[:name].to_s.upcase};") + emitter.concat(fields_emitter) + emitter.concat(body_emitter) + emitter.emit_line('return insn;') + else + rec_emitter = generate_decoder_impl(node) + rec_emitter.increase_indent_all 2 + emitter.concat(rec_emitter) + end + emitter.decrease_indent + emitter.emit_line('}') + end + emitter.decrease_indent + emitter.emit_line('}') + emitter + end + end + + module Header + module_function - module Header - module_function - def generate_decoder(input_ir) -"#ifndef GENERATED_#{input_ir[:isa_name].upcase}_DECODER_HH_INCLUDED + def generate_decoder(input_ir) + "#ifndef GENERATED_#{input_ir[:isa_name].upcase}_DECODER_HH_INCLUDED #define GENERATED_#{input_ir[:isa_name].upcase}_DECODER_HH_INCLUDED #include \"isa.hh\" #include #include - + namespace prot::decoder { using namespace prot::isa; std::optional decode(const uint32_t raw_insn); } // namespace generated::#{input_ir[:isa_name].downcase}::decoder -#endif // GENERATED_#{input_ir[:isa_name].upcase}_DECODER_HH_INCLUDED" - end - end +#endif // GENERATED_#{input_ir[:isa_name].upcase}_DECODER_HH_INCLUDED" + end + end - module TranslationUnit - module_function - def generate_decoder(input_ir) - tree = Helper::make_head_tree(input_ir[:instructions]) - decoder_impl = Helper::generate_decoder_impl(tree) - decoder_impl.increase_indent_all 2 -"#include \"decoder.hh\" + module TranslationUnit + module_function + + def generate_decoder(input_ir) + tree = Helper.make_head_tree(input_ir[:instructions]) + decoder_impl = Helper.generate_decoder_impl(tree) + decoder_impl.increase_indent_all 2 + "#include \"decoder.hh\" #include namespace { @@ -301,14 +289,14 @@ def generate_decoder(input_ir) namespace prot::decoder { using namespace isa; -std::optional decode(const isa::Word raw_insn) { +std::optional decode(const uint32_t raw_insn) { Instruction insn{}; #{decoder_impl} return {}; // No matching instruction found } } // namespace generated::#{input_ir[:isa_name].downcase}::decoder " - end - end + end end + end end diff --git a/sim_gen/ExecEngines/base_exec_engine.rb b/sim_gen/ExecEngines/base_exec_engine.rb index a2247aa..3f9fbde 100644 --- a/sim_gen/ExecEngines/base_exec_engine.rb +++ b/sim_gen/ExecEngines/base_exec_engine.rb @@ -1,4 +1,4 @@ -require "sim_gen/Utility/sim_utility" +require 'sim_gen/Utility/sim_utility' module SimGen module BaseExecEngine @@ -6,12 +6,16 @@ module Header module_function def generate_base_exec_engine(input_ir) -"#ifndef GENERATED_#{input_ir[:isa_name].upcase}_EXEC_ENGINE_HH_INCLUDED + "#ifndef GENERATED_#{input_ir[:isa_name].upcase}_EXEC_ENGINE_HH_INCLUDED #define GENERATED_#{input_ir[:isa_name].upcase}_EXEC_ENGINE_HH_INCLUDED #include \"cpu_state.hh\" #include \"isa.hh\" #include \"memory.hh\" +extern \"C\" { +#include \"softfloat.h\" +} + namespace prot::engine { using namespace prot::state; @@ -34,11 +38,14 @@ module TranslationUnit module_function def generate_base_exec_engine(input_ir) - max_xlen = SimGen::Helper::find_max_xlen(input_ir[:regfiles]) + max_xlen = SimGen::Helper.find_max_xlen(input_ir[:regfiles]) -"#include \"base_exec_engine.hh\" + "#include \"base_exec_engine.hh\" #include \"memory.hh\" #include \"decoder.hh\" +extern \"C\" { +#include \"softfloat.h\" +} namespace prot::engine { using namespace prot::state; @@ -53,7 +60,8 @@ def generate_base_exec_engine(input_ir) cpu.increaseICount(); } } // namespace prot::engine -" end +" + end end end end diff --git a/sim_gen/ExecEngines/naive_interpreter.rb b/sim_gen/ExecEngines/naive_interpreter.rb index b9fa7d1..41e43c7 100644 --- a/sim_gen/ExecEngines/naive_interpreter.rb +++ b/sim_gen/ExecEngines/naive_interpreter.rb @@ -31,6 +31,24 @@ module NaiveInterpreter module TranslationUnit module_function + def architecture_support_include(input_ir) + case input_ir[:architecture_name].to_s.upcase + when 'RISCV', 'RISC-V' + 'riscv_arch_ops.hh' + else + 'fpu_arch_ops.hh' + end + end + + def architecture_support_type(input_ir) + case input_ir[:architecture_name].to_s.upcase + when 'RISCV', 'RISC-V' + 'prot::arch::RISCVFpuUtils' + else + 'prot::arch::FpuUtils' + end + end + def map_operands(insn) operands = {} cnt = 0 @@ -118,8 +136,11 @@ def generate_function_is_branch(input_ir) def generate_naive_interpreter(input_ir) is_branch_function = generate_function_is_branch(input_ir) exec_functions = generate_exec_functions(input_ir) + arch_include = architecture_support_include(input_ir) + arch_support_type = architecture_support_type(input_ir) "#include \"naive_interpreter.hh\" +#include \"#{arch_include}\" #include @@ -130,6 +151,8 @@ def generate_naive_interpreter(input_ir) namespace { #{is_branch_function} +const #{arch_support_type} kArchSupport{}; + #{exec_functions} template diff --git a/sim_gen/sim_gen.rb b/sim_gen/sim_gen.rb index 572cf54..ff5fd46 100755 --- a/sim_gen/sim_gen.rb +++ b/sim_gen/sim_gen.rb @@ -12,7 +12,9 @@ require 'yaml' yaml_data = YAML.load_file(ARGV[0]) -yaml_data[:isa_name] = "RISCV" +architecture_name = yaml_data[:architecture_name] || 'RISC-V' +yaml_data[:architecture_name] = architecture_name +yaml_data[:isa_name] = architecture_name.gsub(/[^0-9A-Za-z]/, '') File.write('cpu_state.hh', SimGen::CPUState::Header.generate_cpu_state(yaml_data)) File.write('base_exec_engine.hh', SimGen::BaseExecEngine::Header.generate_base_exec_engine(yaml_data)) diff --git a/sim_lib/Generic/fpu_arch_ops.hh b/sim_lib/Generic/fpu_arch_ops.hh new file mode 100644 index 0000000..ad32455 --- /dev/null +++ b/sim_lib/Generic/fpu_arch_ops.hh @@ -0,0 +1,18 @@ +#ifndef PROT_ARCH_OPS_HH_INCLUDED +#define PROT_ARCH_OPS_HH_INCLUDED + +#include + +namespace prot::arch { + +class FpuUtils { +public: + virtual ~FpuUtils() = default; + + [[nodiscard]] virtual std::uint32_t f32_classify(std::uint32_t value) const = 0; + [[nodiscard]] virtual std::uint32_t f64_classify(std::uint64_t value) const = 0; +}; + +} // namespace prot::arch + +#endif // PROT_ARCH_OPS_HH_INCLUDED diff --git a/sim_lib/HelpersRISCV/riscv_arch_ops.cc b/sim_lib/HelpersRISCV/riscv_arch_ops.cc new file mode 100644 index 0000000..901ea46 --- /dev/null +++ b/sim_lib/HelpersRISCV/riscv_arch_ops.cc @@ -0,0 +1,49 @@ +#include "riscv_arch_ops.hh" + +namespace prot::arch { + +std::uint32_t RISCVFpuUtils::f32_classify(std::uint32_t value) const { + const std::uint32_t sign = value >> 31; + const std::uint32_t exp = (value >> 23) & 0xFF; + const std::uint32_t frac = value & 0x7FFFFF; + + if (exp == 0xFF) { + if (frac == 0) { + return sign ? (1u << 0) : (1u << 7); + } + return (frac & (1u << 22)) ? (1u << 9) : (1u << 8); + } + + if (exp == 0) { + if (frac == 0) { + return sign ? (1u << 3) : (1u << 4); + } + return sign ? (1u << 2) : (1u << 5); + } + + return sign ? (1u << 1) : (1u << 6); +} + +std::uint32_t RISCVFpuUtils::f64_classify(std::uint64_t value) const { + const std::uint64_t sign = value >> 63; + const std::uint64_t exp = (value >> 52) & 0x7FF; + const std::uint64_t frac = value & 0xFFFFFFFFFFFFFULL; + + if (exp == 0x7FF) { + if (frac == 0) { + return sign ? (1u << 0) : (1u << 7); + } + return (frac & (1ULL << 51)) ? (1u << 9) : (1u << 8); + } + + if (exp == 0) { + if (frac == 0) { + return sign ? (1u << 3) : (1u << 4); + } + return sign ? (1u << 2) : (1u << 5); + } + + return sign ? (1u << 1) : (1u << 6); +} + +} // namespace prot::arch diff --git a/sim_lib/HelpersRISCV/riscv_arch_ops.hh b/sim_lib/HelpersRISCV/riscv_arch_ops.hh new file mode 100644 index 0000000..6810da2 --- /dev/null +++ b/sim_lib/HelpersRISCV/riscv_arch_ops.hh @@ -0,0 +1,16 @@ +#ifndef PROT_RISCV_ARCH_OPS_HH_INCLUDED +#define PROT_RISCV_ARCH_OPS_HH_INCLUDED + +#include "Generic/fpu_arch_ops.hh" + +namespace prot::arch { + +class RISCVFpuUtils final : public FpuUtils { +public: + [[nodiscard]] std::uint32_t f32_classify(std::uint32_t value) const override; + [[nodiscard]] std::uint32_t f64_classify(std::uint64_t value) const override; +}; + +} // namespace prot::arch + +#endif // PROT_RISCV_ARCH_OPS_HH_INCLUDED diff --git a/sim_lib/elf_loader.cc b/sim_lib/elf_loader.cc index 950cafa..f188a2a 100644 --- a/sim_lib/elf_loader.cc +++ b/sim_lib/elf_loader.cc @@ -18,7 +18,8 @@ void ElfLoader::validate() const { throw std::invalid_argument{"Invalid ELF encoding"}; } - if (m_elf->get_class() != ELFIO::ELFCLASS32) { + if (m_elf->get_class() != ELFIO::ELFCLASS32 && + m_elf->get_class() != ELFIO::ELFCLASS64) { throw std::invalid_argument{"Invalid ELF class"}; } diff --git a/sim_lib/sim.cc b/sim_lib/sim.cc index cc6c9a0..1d85294 100644 --- a/sim_lib/sim.cc +++ b/sim_lib/sim.cc @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -56,7 +57,7 @@ int main(int argc, const char *argv[]) try { fmt::println("MIPS: {:.2f}", hart.getIcount() / (duration.count() * 1'000'000)); - return EXIT_SUCCESS; + return hart.m_cpu->m_exit_code; } catch (const std::exception &ex) { fmt::println(std::cerr, "Caught exception of type {}: {}", typeid(ex).name(), ex.what()); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..b0fa22f --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,29 @@ +project(protea_tests) + +include(GoogleTest) + +add_executable(rv64f_tests + rv64f/rv64f_tests.cc + ${protea_simgen_BINARY_DIR}/decoder.cc + ${protea_simgen_BINARY_DIR}/naive_interpreter.cc + ${protea_simgen_BINARY_DIR}/base_exec_engine.cc + ${protea_simlib_SOURCE_DIR}/memory.cc +) + +add_dependencies(rv64f_tests protea_simgen) + +target_include_directories(rv64f_tests + PRIVATE + ${protea_simgen_BINARY_DIR} + ${protea_simlib_SOURCE_DIR} +) + +target_link_libraries(rv64f_tests + PRIVATE + GTest::gtest_main + softfloat +) + +target_compile_features(rv64f_tests PRIVATE cxx_std_23) + +gtest_discover_tests(rv64f_tests) diff --git a/tests/rv64f/rv64f_tests.cc b/tests/rv64f/rv64f_tests.cc new file mode 100644 index 0000000..e42d79f --- /dev/null +++ b/tests/rv64f/rv64f_tests.cc @@ -0,0 +1,407 @@ +#include "decoder.hh" +#include "memory.hh" +#include "naive_interpreter.hh" + +#include +#include +#include +#include +#include +#include +#include + +#include + +extern "C" { +#include "softfloat.h" +} + +namespace { + +using prot::decoder::decode; +using prot::engine::Interpreter; +using prot::isa::Instruction; +using prot::isa::Opcode; +using prot::memory::makePlain; +using prot::state::CPU; + +} // namespace + +namespace prot::isa { +void PrintTo(Opcode opcode, std::ostream *os) { + *os << static_cast(opcode); +} +} // namespace prot::isa + +namespace { + +constexpr uint32_t kOpFp = 0b1010011; + +uint32_t encodeR(uint32_t funct7, uint32_t rs2, uint32_t rs1, uint32_t funct3, + uint32_t rd, uint32_t opcode = kOpFp) { + return (funct7 << 25) | (rs2 << 20) | (rs1 << 15) | (funct3 << 12) | + (rd << 7) | opcode; +} + +uint32_t encodeR4(uint32_t funct2, uint32_t rs3, uint32_t rs2, uint32_t rs1, + uint32_t rm, uint32_t rd, uint32_t opcode) { + return (rs3 << 27) | (funct2 << 25) | (rs2 << 20) | (rs1 << 15) | (rm << 12) | + (rd << 7) | opcode; +} + +uint32_t encodeI(uint32_t imm, uint32_t rs1, uint32_t funct3, uint32_t rd, + uint32_t opcode) { + return ((imm & 0xfff) << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | + opcode; +} + +uint32_t encodeS(uint32_t imm, uint32_t rs2, uint32_t rs1, uint32_t funct3, + uint32_t opcode) { + return (((imm >> 5) & 0x7f) << 25) | (rs2 << 20) | (rs1 << 15) | + (funct3 << 12) | ((imm & 0x1f) << 7) | opcode; +} + +uint64_t boxF32(uint32_t value) { return 0xffffffff00000000ULL | value; } + +uint32_t low32(uint64_t value) { return static_cast(value); } + +Instruction decodeOne(uint32_t raw) { + auto insn = decode(raw); + EXPECT_TRUE(insn.has_value()) << "raw instruction: 0x" << std::hex << raw; + return *insn; +} + +void expectDecode(uint32_t raw, Opcode opcode) { + const auto insn = decodeOne(raw); + EXPECT_EQ(insn.m_opc, opcode); +} + +void expectDecodeOperands(uint32_t raw, Opcode opcode, + std::span operands) { + const auto insn = decodeOne(raw); + EXPECT_EQ(insn.m_opc, opcode); + const std::array actual{insn.operand0, insn.operand1, insn.operand2, + insn.operand3}; + for (std::size_t i = 0; i < operands.size(); ++i) { + EXPECT_EQ(actual[i], operands[i]) << "operand" << i; + } +} + +Instruction decodeAndExecute(CPU &cpu, uint32_t raw) { + auto insn = decodeOne(raw); + Interpreter interpreter; + interpreter.execute(cpu, insn); + return insn; +} + +void withSoftfloatRounding(uint_fast8_t rm, const std::function &body) { + const auto saved = softfloat_roundingMode; + softfloat_roundingMode = rm; + body(); + softfloat_roundingMode = saved; +} + +struct RoundingOpcodes { + std::array opcodes; +}; + +constexpr std::array kRoundingModes{0, 1, 2, 3, 4}; + +void expectRoundedRFamily(uint32_t funct7, const RoundingOpcodes &ops) { + for (std::size_t i = 0; i < kRoundingModes.size(); ++i) { + expectDecode(encodeR(funct7, 3, 2, kRoundingModes[i], 1), ops.opcodes[i]); + } +} + +void expectRoundedR4Family(uint32_t opcode, uint32_t funct2, + const RoundingOpcodes &ops) { + for (std::size_t i = 0; i < kRoundingModes.size(); ++i) { + expectDecode(encodeR4(funct2, 4, 3, 2, kRoundingModes[i], 1, opcode), + ops.opcodes[i]); + } +} + +void expectRoundedFcvtToXFamily(uint32_t funct7, uint32_t rs2, + const RoundingOpcodes &ops) { + for (std::size_t i = 0; i < kRoundingModes.size(); ++i) { + expectDecode(encodeR(funct7, rs2, 2, kRoundingModes[i], 1), ops.opcodes[i]); + } +} + +void expectRoundedFcvtToFFamily(uint32_t funct7, uint32_t rs2, + const RoundingOpcodes &ops) { + for (std::size_t i = 0; i < kRoundingModes.size(); ++i) { + expectDecode(encodeR(funct7, rs2, 2, kRoundingModes[i], 1), ops.opcodes[i]); + } +} + +TEST(RV64FDecodeTest, DecodesAllRoundedArithmeticOpcodes) { + expectRoundedRFamily(0b0000000, {{{Opcode::kFADD_S_RNE, Opcode::kFADD_S_RTZ, + Opcode::kFADD_S_RDN, Opcode::kFADD_S_RUP, + Opcode::kFADD_S_RMM}}}); + expectRoundedRFamily(0b0000001, {{{Opcode::kFADD_D_RNE, Opcode::kFADD_D_RTZ, + Opcode::kFADD_D_RDN, Opcode::kFADD_D_RUP, + Opcode::kFADD_D_RMM}}}); + expectRoundedRFamily(0b0000100, {{{Opcode::kFSUB_S_RNE, Opcode::kFSUB_S_RTZ, + Opcode::kFSUB_S_RDN, Opcode::kFSUB_S_RUP, + Opcode::kFSUB_S_RMM}}}); + expectRoundedRFamily(0b0000101, {{{Opcode::kFSUB_D_RNE, Opcode::kFSUB_D_RTZ, + Opcode::kFSUB_D_RDN, Opcode::kFSUB_D_RUP, + Opcode::kFSUB_D_RMM}}}); + expectRoundedRFamily(0b0001000, {{{Opcode::kFMUL_S_RNE, Opcode::kFMUL_S_RTZ, + Opcode::kFMUL_S_RDN, Opcode::kFMUL_S_RUP, + Opcode::kFMUL_S_RMM}}}); + expectRoundedRFamily(0b0001001, {{{Opcode::kFMUL_D_RNE, Opcode::kFMUL_D_RTZ, + Opcode::kFMUL_D_RDN, Opcode::kFMUL_D_RUP, + Opcode::kFMUL_D_RMM}}}); + expectRoundedRFamily(0b0001100, {{{Opcode::kFDIV_S_RNE, Opcode::kFDIV_S_RTZ, + Opcode::kFDIV_S_RDN, Opcode::kFDIV_S_RUP, + Opcode::kFDIV_S_RMM}}}); + expectRoundedRFamily(0b0001101, {{{Opcode::kFDIV_D_RNE, Opcode::kFDIV_D_RTZ, + Opcode::kFDIV_D_RDN, Opcode::kFDIV_D_RUP, + Opcode::kFDIV_D_RMM}}}); + expectRoundedRFamily(0b0101100, {{{Opcode::kFSQRT_S_RNE, Opcode::kFSQRT_S_RTZ, + Opcode::kFSQRT_S_RDN, Opcode::kFSQRT_S_RUP, + Opcode::kFSQRT_S_RMM}}}); + expectRoundedRFamily(0b0101101, {{{Opcode::kFSQRT_D_RNE, Opcode::kFSQRT_D_RTZ, + Opcode::kFSQRT_D_RDN, Opcode::kFSQRT_D_RUP, + Opcode::kFSQRT_D_RMM}}}); +} + +TEST(RV64FDecodeTest, DecodesAllFusedMultiplyAddOpcodes) { + expectRoundedR4Family( + 0b1000011, 0b00, + {{{Opcode::kFMADD_S_RNE, Opcode::kFMADD_S_RTZ, Opcode::kFMADD_S_RDN, + Opcode::kFMADD_S_RUP, Opcode::kFMADD_S_RMM}}}); + expectRoundedR4Family( + 0b1000011, 0b01, + {{{Opcode::kFMADD_D_RNE, Opcode::kFMADD_D_RTZ, Opcode::kFMADD_D_RDN, + Opcode::kFMADD_D_RUP, Opcode::kFMADD_D_RMM}}}); + expectRoundedR4Family( + 0b1000111, 0b00, + {{{Opcode::kFMSUB_S_RNE, Opcode::kFMSUB_S_RTZ, Opcode::kFMSUB_S_RDN, + Opcode::kFMSUB_S_RUP, Opcode::kFMSUB_S_RMM}}}); + expectRoundedR4Family( + 0b1000111, 0b01, + {{{Opcode::kFMSUB_D_RNE, Opcode::kFMSUB_D_RTZ, Opcode::kFMSUB_D_RDN, + Opcode::kFMSUB_D_RUP, Opcode::kFMSUB_D_RMM}}}); + expectRoundedR4Family( + 0b1001111, 0b00, + {{{Opcode::kFNMADD_S_RNE, Opcode::kFNMADD_S_RTZ, Opcode::kFNMADD_S_RDN, + Opcode::kFNMADD_S_RUP, Opcode::kFNMADD_S_RMM}}}); + expectRoundedR4Family( + 0b1001111, 0b01, + {{{Opcode::kFNMADD_D_RNE, Opcode::kFNMADD_D_RTZ, Opcode::kFNMADD_D_RDN, + Opcode::kFNMADD_D_RUP, Opcode::kFNMADD_D_RMM}}}); + expectRoundedR4Family( + 0b1001011, 0b00, + {{{Opcode::kFNMSUB_S_RNE, Opcode::kFNMSUB_S_RTZ, Opcode::kFNMSUB_S_RDN, + Opcode::kFNMSUB_S_RUP, Opcode::kFNMSUB_S_RMM}}}); + expectRoundedR4Family( + 0b1001011, 0b01, + {{{Opcode::kFNMSUB_D_RNE, Opcode::kFNMSUB_D_RTZ, Opcode::kFNMSUB_D_RDN, + Opcode::kFNMSUB_D_RUP, Opcode::kFNMSUB_D_RMM}}}); +} + +TEST(RV64FDecodeTest, DecodesMemoryAndNonRoundedOpcodes) { + expectDecode(encodeI(0x10, 2, 0b010, 1, 0b0000111), Opcode::kFLW); + expectDecode(encodeI(0x10, 2, 0b011, 1, 0b0000111), Opcode::kFLD); + expectDecode(encodeS(0x10, 3, 2, 0b010, 0b0100111), Opcode::kFSW); + expectDecode(encodeS(0x10, 3, 2, 0b011, 0b0100111), Opcode::kFSD); + + expectDecode(encodeR(0b0010000, 3, 2, 0b000, 1), Opcode::kFSGNJ_S); + expectDecode(encodeR(0b0010001, 3, 2, 0b000, 1), Opcode::kFSGNJ_D); + expectDecode(encodeR(0b0010000, 3, 2, 0b001, 1), Opcode::kFSGNJN_S); + expectDecode(encodeR(0b0010001, 3, 2, 0b001, 1), Opcode::kFSGNJN_D); + expectDecode(encodeR(0b0010000, 3, 2, 0b010, 1), Opcode::kFSGNJX_S); + expectDecode(encodeR(0b0010001, 3, 2, 0b010, 1), Opcode::kFSGNJX_D); + + expectDecode(encodeR(0b0010100, 3, 2, 0b000, 1), Opcode::kFMIN_S); + expectDecode(encodeR(0b0010101, 3, 2, 0b000, 1), Opcode::kFMIN_D); + expectDecode(encodeR(0b0010100, 3, 2, 0b001, 1), Opcode::kFMAX_S); + expectDecode(encodeR(0b0010101, 3, 2, 0b001, 1), Opcode::kFMAX_D); + + expectDecode(encodeR(0b1010000, 3, 2, 0b010, 1), Opcode::kFEQ_S); + expectDecode(encodeR(0b1010001, 3, 2, 0b010, 1), Opcode::kFEQ_D); + expectDecode(encodeR(0b1010000, 3, 2, 0b001, 1), Opcode::kFLT_S); + expectDecode(encodeR(0b1010001, 3, 2, 0b001, 1), Opcode::kFLT_D); + expectDecode(encodeR(0b1010000, 3, 2, 0b000, 1), Opcode::kFLE_S); + expectDecode(encodeR(0b1010001, 3, 2, 0b000, 1), Opcode::kFLE_D); + + expectDecode(encodeR(0b1110000, 0, 2, 0b001, 1), Opcode::kFCLASS_S); + expectDecode(encodeR(0b1110001, 0, 2, 0b001, 1), Opcode::kFCLASS_D); + expectDecode(encodeR(0b1110000, 0, 2, 0b000, 1), Opcode::kFMV_X_W); + expectDecode(encodeR(0b1111000, 0, 2, 0b000, 1), Opcode::kFMV_W_X); +} + +TEST(RV64FDecodeTest, DecodesAllConversionOpcodes) { + expectRoundedFcvtToXFamily( + 0b1100000, 0b00000, + {{{Opcode::kFCVT_W_S_RNE, Opcode::kFCVT_W_S_RTZ, Opcode::kFCVT_W_S_RDN, + Opcode::kFCVT_W_S_RUP, Opcode::kFCVT_W_S_RMM}}}); + expectRoundedFcvtToXFamily( + 0b1100000, 0b00001, + {{{Opcode::kFCVT_WU_S_RNE, Opcode::kFCVT_WU_S_RTZ, Opcode::kFCVT_WU_S_RDN, + Opcode::kFCVT_WU_S_RUP, Opcode::kFCVT_WU_S_RMM}}}); + expectRoundedFcvtToXFamily( + 0b1100000, 0b00010, + {{{Opcode::kFCVT_L_S_RNE, Opcode::kFCVT_L_S_RTZ, Opcode::kFCVT_L_S_RDN, + Opcode::kFCVT_L_S_RUP, Opcode::kFCVT_L_S_RMM}}}); + expectRoundedFcvtToXFamily( + 0b1100000, 0b00011, + {{{Opcode::kFCVT_LU_S_RNE, Opcode::kFCVT_LU_S_RTZ, Opcode::kFCVT_LU_S_RDN, + Opcode::kFCVT_LU_S_RUP, Opcode::kFCVT_LU_S_RMM}}}); + expectRoundedFcvtToFFamily( + 0b1101000, 0b00000, + {{{Opcode::kFCVT_S_W_RNE, Opcode::kFCVT_S_W_RTZ, Opcode::kFCVT_S_W_RDN, + Opcode::kFCVT_S_W_RUP, Opcode::kFCVT_S_W_RMM}}}); + expectRoundedFcvtToFFamily( + 0b1101000, 0b00001, + {{{Opcode::kFCVT_S_WU_RNE, Opcode::kFCVT_S_WU_RTZ, Opcode::kFCVT_S_WU_RDN, + Opcode::kFCVT_S_WU_RUP, Opcode::kFCVT_S_WU_RMM}}}); + expectRoundedFcvtToFFamily( + 0b1101000, 0b00010, + {{{Opcode::kFCVT_S_L_RNE, Opcode::kFCVT_S_L_RTZ, Opcode::kFCVT_S_L_RDN, + Opcode::kFCVT_S_L_RUP, Opcode::kFCVT_S_L_RMM}}}); + expectRoundedFcvtToFFamily( + 0b1101000, 0b00011, + {{{Opcode::kFCVT_S_LU_RNE, Opcode::kFCVT_S_LU_RTZ, Opcode::kFCVT_S_LU_RDN, + Opcode::kFCVT_S_LU_RUP, Opcode::kFCVT_S_LU_RMM}}}); +} + +TEST(RV64FDecodeTest, DecodesExpectedOperandOrderForRepresentativeFormats) { + expectDecodeOperands(encodeR(0b0000000, 3, 2, 0, 1), Opcode::kFADD_S_RNE, + std::array{3, 2, 1}); + expectDecodeOperands(encodeR4(0, 4, 3, 2, 0, 1, 0b1000011), + Opcode::kFMADD_S_RNE, + std::array{4, 3, 2, 1}); + expectDecodeOperands(encodeI(0x10, 2, 0b010, 1, 0b0000111), Opcode::kFLW, + std::array{0x10, 2, 1}); + expectDecodeOperands(encodeS(0x10, 3, 2, 0b010, 0b0100111), Opcode::kFSW, + std::array{0x10, 2, 3}); +} + +TEST(RV64FExecutionTest, ExecutesSingleAndDoubleArithmetic) { + auto memory = makePlain(256); + CPU cpu(memory.get()); + cpu.setFRegs(2, boxF32(0x3fc00000)); // 1.5f + cpu.setFRegs(3, boxF32(0x40100000)); // 2.25f + + withSoftfloatRounding(softfloat_round_near_even, [&] { + const float32_t lhs{0x3fc00000}; + const float32_t rhs{0x40100000}; + decodeAndExecute(cpu, encodeR(0b0000000, 3, 2, 0, 1)); + EXPECT_EQ(cpu.getFRegs(1), boxF32(f32_add(lhs, rhs).v)); + decodeAndExecute(cpu, encodeR(0b0000100, 3, 2, 0, 1)); + EXPECT_EQ(cpu.getFRegs(1), boxF32(f32_sub(lhs, rhs).v)); + decodeAndExecute(cpu, encodeR(0b0001000, 3, 2, 0, 1)); + EXPECT_EQ(cpu.getFRegs(1), boxF32(f32_mul(lhs, rhs).v)); + decodeAndExecute(cpu, encodeR(0b0001100, 3, 2, 0, 1)); + EXPECT_EQ(cpu.getFRegs(1), boxF32(f32_div(lhs, rhs).v)); + decodeAndExecute(cpu, encodeR(0b0101100, 0, 3, 0, 1)); + EXPECT_EQ(cpu.getFRegs(1), boxF32(f32_sqrt(rhs).v)); + }); + + cpu.setFRegs(2, 0x3ff8000000000000ULL); // 1.5 + cpu.setFRegs(3, 0x4002000000000000ULL); // 2.25 + + withSoftfloatRounding(softfloat_round_near_even, [&] { + const float64_t lhs{0x3ff8000000000000ULL}; + const float64_t rhs{0x4002000000000000ULL}; + decodeAndExecute(cpu, encodeR(0b0000001, 3, 2, 0, 1)); + EXPECT_EQ(cpu.getFRegs(1), f64_add(lhs, rhs).v); + decodeAndExecute(cpu, encodeR(0b0000101, 3, 2, 0, 1)); + EXPECT_EQ(cpu.getFRegs(1), f64_sub(lhs, rhs).v); + decodeAndExecute(cpu, encodeR(0b0001001, 3, 2, 0, 1)); + EXPECT_EQ(cpu.getFRegs(1), f64_mul(lhs, rhs).v); + decodeAndExecute(cpu, encodeR(0b0001101, 3, 2, 0, 1)); + EXPECT_EQ(cpu.getFRegs(1), f64_div(lhs, rhs).v); + decodeAndExecute(cpu, encodeR(0b0101101, 0, 3, 0, 1)); + EXPECT_EQ(cpu.getFRegs(1), f64_sqrt(rhs).v); + }); +} + +TEST(RV64FExecutionTest, ExecutesMemorySignCompareClassAndMoveInstructions) { + auto memory = makePlain(256); + CPU cpu(memory.get()); + cpu.setXRegs(2, 64U); + memory->write(68, 0x3f800000U); + memory->write(80, 0x4000000000000000ULL); + + decodeAndExecute(cpu, encodeI(4, 2, 0b010, 1, 0b0000111)); + EXPECT_EQ(low32(cpu.getFRegs(1)), 0x3f800000U); + + decodeAndExecute(cpu, encodeI(16, 2, 0b011, 1, 0b0000111)); + EXPECT_EQ(cpu.getFRegs(1), 0x4000000000000000ULL); + + cpu.setFRegs(3, boxF32(0x40400000)); + decodeAndExecute(cpu, encodeS(24, 3, 2, 0b010, 0b0100111)); + EXPECT_EQ(memory->read(88), 0x40400000U); + + cpu.setFRegs(3, 0x4008000000000000ULL); + decodeAndExecute(cpu, encodeS(32, 3, 2, 0b011, 0b0100111)); + EXPECT_EQ(memory->read(96), 0x4008000000000000ULL); + + cpu.setFRegs(2, boxF32(0x3f800000)); + cpu.setFRegs(3, boxF32(0x80000000)); + decodeAndExecute(cpu, encodeR(0b0010000, 3, 2, 0b000, 1)); + EXPECT_EQ(cpu.getFRegs(1), boxF32(0xbf800000)); + decodeAndExecute(cpu, encodeR(0b0010000, 3, 2, 0b001, 1)); + EXPECT_EQ(cpu.getFRegs(1), boxF32(0x3f800000)); + decodeAndExecute(cpu, encodeR(0b0010000, 3, 2, 0b010, 1)); + EXPECT_EQ(cpu.getFRegs(1), boxF32(0xbf800000)); + + cpu.setFRegs(2, boxF32(0x3f800000)); + cpu.setFRegs(3, boxF32(0x40000000)); + decodeAndExecute(cpu, encodeR(0b0010100, 3, 2, 0b000, 1)); + EXPECT_EQ(cpu.getFRegs(1), boxF32(0x3f800000)); + decodeAndExecute(cpu, encodeR(0b0010100, 3, 2, 0b001, 1)); + EXPECT_EQ(cpu.getFRegs(1), boxF32(0x40000000)); + decodeAndExecute(cpu, encodeR(0b1010000, 3, 2, 0b010, 1)); + EXPECT_EQ(cpu.getXRegs(1), 0U); + decodeAndExecute(cpu, encodeR(0b1010000, 3, 2, 0b001, 1)); + EXPECT_EQ(cpu.getXRegs(1), 1U); + decodeAndExecute(cpu, encodeR(0b1010000, 3, 2, 0b000, 1)); + EXPECT_EQ(cpu.getXRegs(1), 1U); + + decodeAndExecute(cpu, encodeR(0b1110000, 0, 2, 0b001, 1)); + EXPECT_EQ(cpu.getXRegs(1), 1U << 6); + cpu.setFRegs(2, 0x7ff8000000000000ULL); + decodeAndExecute(cpu, encodeR(0b1110001, 0, 2, 0b001, 1)); + EXPECT_EQ(cpu.getXRegs(1), 1U << 9); + + cpu.setFRegs(2, boxF32(0xdeadbeef)); + decodeAndExecute(cpu, encodeR(0b1110000, 0, 2, 0b000, 1)); + EXPECT_EQ(cpu.getXRegs(1), 0xdeadbeefU); + + cpu.setXRegs(2, 0x3f800000U); + decodeAndExecute(cpu, encodeR(0b1111000, 0, 2, 0b000, 1)); + EXPECT_EQ(low32(cpu.getFRegs(1)), 0x3f800000U); +} + +TEST(RV64FExecutionTest, ExecutesConversionsRepresentableByCurrentXRegWidth) { + auto memory = makePlain(256); + CPU cpu(memory.get()); + + cpu.setFRegs(2, boxF32(0x40200000)); // 2.5f + decodeAndExecute(cpu, encodeR(0b1100000, 0b00000, 2, 0b001, 1)); + EXPECT_EQ(cpu.getXRegs(1), 2U); + decodeAndExecute(cpu, encodeR(0b1100000, 0b00001, 2, 0b001, 1)); + EXPECT_EQ(cpu.getXRegs(1), 2U); + decodeAndExecute(cpu, encodeR(0b1100000, 0b00010, 2, 0b001, 1)); + EXPECT_EQ(cpu.getXRegs(1), 2U); + decodeAndExecute(cpu, encodeR(0b1100000, 0b00011, 2, 0b001, 1)); + EXPECT_EQ(cpu.getXRegs(1), 2U); + + cpu.setXRegs(2, 3U); + decodeAndExecute(cpu, encodeR(0b1101000, 0b00000, 2, 0b000, 1)); + EXPECT_EQ(low32(cpu.getFRegs(1)), 0x40400000U); + decodeAndExecute(cpu, encodeR(0b1101000, 0b00001, 2, 0b000, 1)); + EXPECT_EQ(low32(cpu.getFRegs(1)), 0x40400000U); + decodeAndExecute(cpu, encodeR(0b1101000, 0b00010, 2, 0b000, 1)); + EXPECT_EQ(low32(cpu.getFRegs(1)), 0x40400000U); + decodeAndExecute(cpu, encodeR(0b1101000, 0b00011, 2, 0b000, 1)); + EXPECT_EQ(low32(cpu.getFRegs(1)), 0x40400000U); +} + +} // namespace