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..af16461 100644 --- a/cmake/dependencies.cmake +++ b/cmake/dependencies.cmake @@ -21,3 +21,19 @@ CPMAddPackage( GIT_TAG 12.0.0 EXCLUDE_FROM_ALL True SYSTEM True) + +# PROPOSAL: +# 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..cf4939f --- /dev/null +++ b/cmake/softfloat.cmake @@ -0,0 +1,63 @@ +# softfloat: floating point arithmetic library +CPMAddPackage( + NAME softfloat + GITHUB_REPOSITORY ucb-bar/berkeley-softfloat-3 + GIT_TAG master + 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 +) + +# Select platform-specific primitives based on system +if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64") + file(GLOB SOFTFLOAT_PLATFORM_SRC + ${softfloat_SOURCE_DIR}/source/8086-SSE/*.c + ) + set(SOFTFLOAT_PLATFORM_DIR ${softfloat_SOURCE_DIR}/source/8086-SSE) +else() + file(GLOB SOFTFLOAT_PLATFORM_SRC + ${softfloat_SOURCE_DIR}/source/generic/*.c + ) + set(SOFTFLOAT_PLATFORM_DIR ${softfloat_SOURCE_DIR}/source/generic) +endif() + +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..812a192 100644 --- a/code_gen/cpp_gen.rb +++ b/code_gen/cpp_gen.rb @@ -1,4 +1,5 @@ require 'Utility/helper_cpp' +require 'Utility/fp_helper_cpp' # frozen_string_literal: true # Semantics Generator: Converts IR to C++ code @@ -6,6 +7,24 @@ module CodeGen class CppGenerator attr_reader :emitter, :mapping + # PROPOSAL: + # Add RM (rounding modes) map that converts + # RV rounding modes into softfloat rounding modes. + # They actually are exactly the same, but we can generalize this idea later. + # Maybe should move it to other module + # 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 + def initialize(emitter, mapping = {}) @emitter = emitter @mapping = mapping @@ -22,6 +41,292 @@ def binary_operation(emitter, operation, op_str) emitter.emit_line("#{dst} = #{src1} #{op_str} #{src2};") end + # PROPOSAL: + # Add FP emitter helpers + # 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 + + # Currently not supported + if rm == 7 # DYN mode - read from fcsr at runtime + @emitter.emit_line('assert(0 && "CSR is currently not supported\n");') + @emitter.emit_line("uint_fast8_t #{tmp_var} = softfloat_roundingMode;") + # @emitter.emit_line('softfloat_roundingMode = cpu.getFCSR_RM();') + else + rm_const = SOFTFLOAT_RM_MAP[rm] + @emitter.emit_line("uint_fast8_t #{tmp_var} = softfloat_roundingMode;") + @emitter.emit_line("softfloat_roundingMode = #{rm_const};") + end + 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 separetely, 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' + @emitter.emit_line("#{Utility::INT_INFO[dst_type]} #{tr} = #{opname}(#{ts}, #{rounding_mode}, #{exact_arg});") + @emitter.emit_line("#{dst} = (uint64_t)#{tr};") + 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;") + 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;") + end + + is_min = opname.include?('min') + + @emitter.emit_line('{') + # return number if one of them is NaN, if both are NaN, return second + @emitter.emit_line(" if (_a_nan && _b_nan) #{tr} = #{t2};") + @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 + + # 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) @@ -45,6 +350,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 +410,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] @@ -136,6 +443,211 @@ def generate_statement(operation) false_val = @mapping[operation[:oprnds][3][:name]] || operation[:oprnds][3][:name] @emitter.emit_line("#{dst} = #{cond} ? #{true_val} : #{false_val};") + + # PROPOSAL: + # 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], + 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], + 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, 2], + 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, 2], + 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('{') + @emitter.emit_line("uint32_t _v = #{src};") + @emitter.emit_line('uint32_t _sign = _v >> 31;') + @emitter.emit_line('uint32_t _exp = (_v >> 23) & 0xFF;') + @emitter.emit_line('uint32_t _frac = _v & 0x7FFFFF;') + @emitter.emit_line('') + @emitter.emit_line('if (_exp == 0xFF) {') + @emitter.emit_line(" if (_frac == 0) { #{dst} = _sign ? (1u << 0) : (1u << 7); }") + @emitter.emit_line(" else if (_frac & (1u << 22)) { #{dst} = (1u << 9); }") + @emitter.emit_line(" else { #{dst} = (1u << 8); }") + @emitter.emit_line('}') + @emitter.emit_line('else if (_exp == 0) {') + @emitter.emit_line(" if (_frac == 0) { #{dst} = _sign ? (1u << 3) : (1u << 4); }") + @emitter.emit_line(" else { #{dst} = _sign ? (1u << 2) : (1u << 5); }") + @emitter.emit_line('}') + @emitter.emit_line('else {') + @emitter.emit_line(" #{dst} = _sign ? (1u << 1) : (1u << 6);") + @emitter.emit_line('}') + @emitter.emit_line('}') + + when :f64_classify + dst, src = map_n_operands(operation, 2) + @emitter.emit_line('{') + @emitter.emit_line("uint64_t v = #{src};") + @emitter.emit_line('uint64_t sign = v >> 63;') + @emitter.emit_line('uint64_t exp = (v >> 52) & 0x7FF;') + @emitter.emit_line('uint64_t frac = v & 0xFFFFFFFFFFFFFULL;') + @emitter.emit_line('uint32_t r = 0;') + @emitter.emit_line('') + @emitter.emit_line('if (exp == 0x7FF) {') + @emitter.emit_line(' if (frac == 0) r = sign ? (1u << 0) : (1u << 7);') + @emitter.emit_line(' else r = (frac & (1ULL << 51)) ? (1u << 9) : (1u << 8);') + @emitter.emit_line('}') + @emitter.emit_line('else if (exp == 0) {') + @emitter.emit_line(' if (frac == 0) r = sign ? (1u << 3) : (1u << 4);') + @emitter.emit_line(' else r = sign ? (1u << 2) : (1u << 5);') + @emitter.emit_line('}') + @emitter.emit_line('else {') + @emitter.emit_line(' r = sign ? (1u << 1) : (1u << 6);') + @emitter.emit_line('}') + @emitter.emit_line('') + @emitter.emit_line("#{dst} = r;") + @emitter.emit_line('}') + + else raise "Unknown statement type: #{operation[:name]}, terminating program" end end end diff --git a/lib/ADL/builder.rb b/lib/ADL/builder.rb index 83d0313..3a81935 100644 --- a/lib/ADL/builder.rb +++ b/lib/ADL/builder.rb @@ -1,252 +1,270 @@ -require_relative "scope" -require "Utility/type" +# PROPOSAL: +# Autoformatter works on file save, so I had no choice) +# (autoformatted) +require_relative 'scope' +require 'Utility/type' module SimInfra - class IrStmt - attr_reader :name, :oprnds, :attrs - def initialize(name, oprnds, attrs) - @name = name; @oprnds = oprnds; @attrs = attrs; - end + class IrStmt + attr_reader :name, :oprnds, :attrs - def to_h - { - name: @name, - oprnds: @oprnds.map { |o| - if o.class == Var || o.class == Constant - o.to_h - else - o - end - }, - attrs: @attrs, - } - end + def initialize(name, oprnds, attrs) + @name = name + @oprnds = oprnds + @attrs = attrs + end - def self.from_h(h) - IrStmt.new(h[:name], h[:oprnds], h[:attrs]) - end + def to_h + { + name: @name, + oprnds: @oprnds.map do |o| + if [Var, Constant].include?(o.class) + o.to_h + else + o + end + end, + attrs: @attrs + } end + + def self.from_h(h) + IrStmt.new(h[:name], h[:oprnds], h[:attrs]) + end + end end # Basics -module SimInfra - def assert(condition, msg = nil); raise msg if !condition; end +module SimInfra + def assert(condition, msg = nil) + raise msg unless condition + end - @@instructions = [] - @@interface_functions = [] + @@instructions = [] + @@interface_functions = [] - def self.interface_functions - @@interface_functions - end + def self.interface_functions + @@interface_functions + end - class InstructionInfo - attr_accessor :name, :fields, :frmt, :map, :code, :map_code_blocks, :asm_str, :XLEN, :feature - def initialize(name, feature) - @name = name; - @map_code_blocks = {} - @feature = feature - end + class InstructionInfo + attr_accessor :name, :fields, :frmt, :map, :code, :map_code_blocks, :asm_str, :XLEN, :feature - def to_h - { - name: @name, - fields: @fields.map { |f| f.to_h }, - frmt: @frmt, - XLEN: @XLEN, - asm_str: @asm_str, - code: @code.to_h, - map: @map.to_h, - feature: @feature, - } - end + def initialize(name, feature) + @name = name + @map_code_blocks = {} + @feature = feature + end - def self.from_h(h) - info = InstructionInfo.new(h[:name], h[:feature]) - info.fields = h[:fields].map { |f| Field.from_h(f) } - info.frmt = h[:frmt] - info.XLEN = h[:XLEN] - info.asm_str = h[:asm_str] - info.code = Scope.new(nil) - info.code.instance_variable_set(:@tree, h[:code][:tree].map { |s| IrStmt.from_h(s) }) - info.map = Scope.new(nil) - info.map.instance_variable_set(:@tree, h[:map][:tree].map { |s| IrStmt.from_h(s) }) - info - end + def to_h + { + name: @name, + fields: @fields.map { |f| f.to_h }, + frmt: @frmt, + XLEN: @XLEN, + asm_str: @asm_str, + code: @code.to_h, + map: @map.to_h, + feature: @feature + } + end + + def self.from_h(h) + info = InstructionInfo.new(h[:name], h[:feature]) + info.fields = h[:fields].map { |f| Field.from_h(f) } + info.frmt = h[:frmt] + info.XLEN = h[:XLEN] + info.asm_str = h[:asm_str] + info.code = Scope.new(nil) + info.code.instance_variable_set(:@tree, h[:code][:tree].map { |s| IrStmt.from_h(s) }) + info.map = Scope.new(nil) + info.map.instance_variable_set(:@tree, h[:map][:tree].map { |s| IrStmt.from_h(s) }) + info end + end - class InstructionInfoBuilder - include SimInfra + 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) + @@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]})] in_stmt.concat(in_s) return stmt :#{func[:name]}, in_stmt end - " - else - @info.code.instance_eval "def #{func[:name]}(*args) + ", __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 - " - end - end - - @info.map = Scope.new(nil) + ", __FILE__, __LINE__ - 4 end + end - def encoding(frmt, fields, *args) - @info.fields = fields - @info.frmt= frmt - map args - - sum_bits = 0 - for f in fields - 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" - end - attr_reader :info + @info.map = Scope.new(nil) end - def Instruction(name, &block) - module_name = caller[0].split('\'')[1].split(':')[1][0..-2] - - bldr = InstructionInfoBuilder.new(name, module_name.to_sym) - bldr.instance_eval &block - @@instructions << bldr.info - nil # only for debugging in IRB + def encoding(frmt, fields, *args) + @info.fields = fields + @info.frmt = frmt + map args + + sum_bits = 0 + for f in fields + sum_bits += Utility.get_type(f.value.type).bitsize + end + @info.XLEN = sum_bits / 8 + @info.code.instance_eval "def xlen(); return #{@info.XLEN}; end", __FILE__, __LINE__ end + attr_reader :info + end - class InterfaceBuilder - include SimInfra + def Instruction(name, &block) + module_name = caller[0].split('\'')[1].split(':')[1][0..-2] - def function(name, output_types = [], input_types = []) - @@interface_functions << {:name => name, :return_types => output_types, :argument_types => input_types} - end - end + bldr = InstructionInfoBuilder.new(name, module_name.to_sym) + bldr.instance_eval(&block) + @@instructions << bldr.info + nil # only for debugging in IRB + end - def Interface(&blck) - bldr = InterfaceBuilder.new() - - bldr.instance_eval &blck + class InterfaceBuilder + include SimInfra + + def function(name, output_types = [], input_types = []) + @@interface_functions << { name: name, return_types: output_types, argument_types: input_types } end + end - class RegisterFileInfo - attr_accessor :name, :regs - def initialize(name) - @name = name; @regs = [] - end + def Interface(&blck) + bldr = InterfaceBuilder.new - def to_h - { - name: @name, - regs: @regs.map(&:to_h), - } - end + bldr.instance_eval(&blck) + end - def self.from_h(h) - rf = new(h[:name]) - rf.regs = h[:regs].map { |r| Register.from_h(r) } - rf - end + class RegisterFileInfo + attr_accessor :name, :regs + + def initialize(name) + @name = name + @regs = [] end - class Register - attr_reader :name, :size, :attrs - def initialize(name, size, attrs) - @name = name; @size = size; @attrs = attrs - end + def to_h + { + name: @name, + regs: @regs.map(&:to_h) + } + end - def to_h - { - name: @name, - size: @size, - attrs: @attrs, - } - end + def self.from_h(h) + rf = new(h[:name]) + rf.regs = h[:regs].map { |r| Register.from_h(r) } + rf + end + end - def self.from_h(h) - new(h[:name], h[:size], h[:attrs]) - end + class Register + attr_reader :name, :size, :attrs + + def initialize(name, size, attrs) + @name = name + @size = size + @attrs = attrs end - @@regfiles = [] - class RegisterFileBuilder - def initialize(name) - @info = RegisterFileInfo.new(name) - @info.regs = [] - end - attr_reader :info + def to_h + { + name: @name, + size: @size, + attrs: @attrs + } end - def RegisterFile(name, &block) - bldr = RegisterFileBuilder.new(name) - bldr.instance_eval &block - @@regfiles << bldr.info - nil + def self.from_h(h) + new(h[:name], h[:size], h[:attrs]) end + end - def RegFiles() - @@regfiles + @@regfiles = [] + class RegisterFileBuilder + def initialize(name) + @info = RegisterFileInfo.new(name) + @info.regs = [] end + attr_reader :info + end + + def RegisterFile(name, &block) + bldr = RegisterFileBuilder.new(name) + bldr.instance_eval(&block) + @@regfiles << bldr.info + nil + end + + def RegFiles + @@regfiles + end end # * generate precise fields module SimInfra - class RegisterFileBuilder - def r32(sym, *args) - @info.regs << Register.new(sym, 32, args[0] ? [args[0]] : []) - end + class RegisterFileBuilder + def r32(sym, *args) + @info.regs << Register.new(sym, 32, args[0] ? [args[0]] : []) + end - def zero() - :zero - end + def f64(sym, *args) + @info.regs << Register.new(sym, 64, args[0] ? [args[0]] : []) + end - def pc() - :pc - end + def zero + :zero end - class InstructionInfoBuilder - def code(&block) - if !@info.map_code_blocks.empty? - @info.fields.each { |f| - @info.map.method(f.value.name, f.value.type) - } - end - @info.map_code_blocks.each do |k, v| - @info.map.instance_eval v[1] - end - @info.map_code_blocks.each do |k, v| - @info.code.method(k, v[0], @info.map.vars[k].regset) - end - for regfile in @@regfiles - for reg in regfile.regs - @info.code.method(reg.name, ('r' + reg.size.to_s).to_sym) - end - end - @info.code.instance_eval &block - end + def pc + :pc + end + end - def map(blocks) - if (!blocks.nil? && !blocks.empty?) - for blck in blocks - @info.map_code_blocks[blck[0]] = [blck[1], blck[2]] - end - end + class InstructionInfoBuilder + def code(&block) + unless @info.map_code_blocks.empty? + @info.fields.each do |f| + @info.map.method(f.value.name, f.value.type) end + end + @info.map_code_blocks.each do |k, v| + @info.map.instance_eval v[1] + end + @info.map_code_blocks.each do |k, v| + @info.code.method(k, v[0], @info.map.vars[k].regset) + end + for regfile in @@regfiles + for reg in regfile.regs + @info.code.method(reg.name, ('r' + reg.size.to_s).to_sym) + end + end + @info.code.instance_eval(&block) + end + + def map(blocks) + return unless !blocks.nil? && !blocks.empty? - def asm(&block) - @info.asm_str = instance_eval &block + for blck in blocks + @info.map_code_blocks[blck[0]] = [blck[1], blck[2]] end end + + def asm(&block) + @info.asm_str = instance_eval(&block) + end + end end diff --git a/lib/ADL/scope.rb b/lib/ADL/scope.rb index 57d4643..80aed1e 100644 --- a/lib/ADL/scope.rb +++ b/lib/ADL/scope.rb @@ -59,34 +59,121 @@ def resolve_const(what) Constant.new(self, "const_#{next_counter}", what) if what.class == Integer end - def binOp(a, b, op) + # PROPOSAL: + # Make those helpers accept optional attrs argument, + # which is now used to pass rounding mode for some fp instructions. + # Other pseudo-operands may appear in other RV modules, + # so it is a necessary addition imo. + 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) + # PROPOSAL: + # Add floatig point operations + # 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) def select(p, a, b) a = resolve_const(a) @@ -153,7 +240,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 +252,13 @@ 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 + # PROPOSAL: + # add switch case to support FRegs + 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..d25fb14 100644 --- a/lib/ADL/value.rb +++ b/lib/ADL/value.rb @@ -1,50 +1,61 @@ +# PROPOSAL: +# autoformat again module SimInfra - # Value class is a super class of Variable or Constant. - class Value - # If value is nil then Value represents a variable. - attr_reader :name, :type, :value - def initialize(name, type, value_num) - @name = name; @type = type; @value = value_num - end - def inspect - if @value.nil? - "#{@type}:#{@name}"; - else - "#{@value.to_s(2)}"; - end - end - - def to_h - { - name: @name, - type: @type, - value: @value, - } - end - - def self.from_h(h) - Value.new(h[:name], h[:type], h[:value]) - end + # Value class is a super class of Variable or Constant. + class Value + # If value is nil then Value represents a variable. + attr_reader :name, :type, :value + + def initialize(name, type, value_num) + @name = name + @type = type + @value = value_num + end + + def inspect + if @value.nil? + "#{@type}:#{@name}" + else + "#{@value.to_s(2)}" + end + end + + def to_h + { + name: @name, + type: @type, + value: @value + } + end + + def self.from_h(h) + Value.new(h[:name], h[:type], h[:value]) + end + end + + class Constant + attr_reader :scope, :name, :type, :value + + def initialize(scope, name, value) + @const = value + @scope = scope + @type = :iconst + @value = value + end + + def let(other) = raise('Assign to constant') + def inspect = "#{@name}:#{@type} (#{@scope.object_id}) {=#{@const}}" + + def to_h + { + name: @name, + type: @type, + value: @value + } end - class Constant - attr_reader :scope, :name, :type, :value - def initialize(scope, name, value); - @const = value; @scope = scope; @type = :iconst; @value = value - end - def let(other); raise "Assign to constant"; end - def inspect; "#{@name}:#{@type} (#{@scope.object_id}) {=#{@const}}"; end - - def to_h - { - name: @name, - type: @type, - value: @value, - } - end - - def self.from_h(h, scope) - Constant.new(scope, h[:name], h[:value]) - end + def self.from_h(h, scope) + Constant.new(scope, h[:name], h[:value]) end + end end diff --git a/lib/ADL/var.rb b/lib/ADL/var.rb index 671f258..876b46d 100644 --- a/lib/ADL/var.rb +++ b/lib/ADL/var.rb @@ -55,6 +55,8 @@ def ==(other) = @scope.eq(self, other) def !=(other) = @scope.ne(self, other) def [](r, l) = @scope.extract(self, r, l) + # PROPOSAL: add f regs + 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/32I.rb b/lib/Target/RISC-V/32I.rb index 6a67a7b..a1cc076 100644 --- a/lib/Target/RISC-V/32I.rb +++ b/lib/Target/RISC-V/32I.rb @@ -1,292 +1,298 @@ -require_relative "encoding" -require_relative "../../ADL/base" -require_relative "../../ADL/builder" +# PROPOSAL: autoformat +require_relative 'encoding' +require_relative '../../ADL/base' +require_relative '../../ADL/builder' module RV32I - include SimInfra - extend SimInfra - - Interface { - function :sysCall - } - - RegisterFile(:XRegs) { - r32 :x0, zero - r32 :x1 - r32 :x2 - r32 :x3 - r32 :x4 - r32 :x5 - r32 :x6 - r32 :x7 - r32 :x8 - r32 :x9 - r32 :x10 - r32 :x11 - r32 :x12 - r32 :x13 - r32 :x14 - r32 :x15 - r32 :x16 - r32 :x17 - r32 :x18 - r32 :x19 - r32 :x20 - r32 :x21 - r32 :x22 - r32 :x23 - r32 :x24 - r32 :x25 - r32 :x26 - r32 :x27 - r32 :x28 - r32 :x29 - r32 :x30 - r32 :x31 - r32 :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[]= imm + pc } - } - - 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 } - } - - 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 } - } - - Instruction(:sra) { - encoding *format_r(0b0110011, 0b101, 0b0100000) - asm { "sra {rd}, {rs1}, {rs2}" } - code { rd[]= rs1.s >> rs2.s } - } - - 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).b32 } - } - - Instruction(:sltiu) { - encoding *format_i(0b0010011, 0b011) - asm { "sltiu {rd}, {rs1}, {imm}" } - code { rd[]= (rs1.u < imm.u).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_shift(0b0010011, 0b001, 0b00000) - asm { "slli {rd}, {rs1}, {imm}" } - code { rd[]= rs1 << imm } - } - - Instruction(:srli) { - encoding *format_i_shift(0b0010011, 0b101, 0b00000) - asm { "srli {rd}, {rs1}, {imm}" } - code { rd[]= rs1 >> imm } - } - - Instruction(:srai) { - encoding *format_i_shift(0b0010011, 0b101, 0b01000) - asm { "srai {rd}, {rs1}, {imm}" } - code { rd[]= rs1.s >> imm } - } - - 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, :b32, 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 } - } - - 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] } - } - - 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(: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 { } - } + include SimInfra + extend SimInfra + + Interface do + function :sysCall + end + + RegisterFile(:XRegs) do + r32 :x0, zero + r32 :x1 + r32 :x2 + r32 :x3 + r32 :x4 + r32 :x5 + r32 :x6 + r32 :x7 + r32 :x8 + r32 :x9 + r32 :x10 + r32 :x11 + r32 :x12 + r32 :x13 + r32 :x14 + r32 :x15 + r32 :x16 + r32 :x17 + r32 :x18 + r32 :x19 + r32 :x20 + r32 :x21 + r32 :x22 + r32 :x23 + r32 :x24 + r32 :x25 + r32 :x26 + r32 :x27 + r32 :x28 + r32 :x29 + r32 :x30 + r32 :x31 + r32 :pc, pc + end + + Instruction(:lui) do + encoding(*format_u(0b0110111)) + asm { 'lui {rd}, {imm}' } + code { rd[] = imm } + end + + Instruction(:auipc) do + encoding(*format_u(0b0010111)) + asm { 'auipc {rd}, {imm}' } + code { rd[] = imm + pc } + end + + Instruction(:add) do + encoding(*format_r(0b0110011, 0b000, 0b0000000)) + asm { 'add {rd}, {rs1}, {rs2}' } + code { rd[] = rs1.u + rs2.u } + end + + Instruction(:sub) do + encoding(*format_r(0b0110011, 0b000, 0b0100000)) + asm { 'sub {rd}, {rs1}, {rs2}' } + code { rd[] = rs1.u - rs2.u } + end + + Instruction(:sll) do + encoding(*format_r(0b0110011, 0b001, 0b0000000)) + asm { 'sll {rd}, {rs1}, {rs2}' } + code { rd[] = rs1.u << rs2.u } + end + + Instruction(:slt) do + encoding(*format_r(0b0110011, 0b010, 0b0000000)) + asm { 'slt {rd}, {rs1}, {rs2}' } + code { rd[] = (rs1.s < rs2.s).b32 } + end + + Instruction(:sltu) do + encoding(*format_r(0b0110011, 0b011, 0b0000000)) + asm { 'sltu {rd}, {rs1}, {rs2}' } + code { rd[] = (rs1.u < rs2.u).b32 } + end + + Instruction(:xor) do + encoding(*format_r(0b0110011, 0b100, 0b0000000)) + asm { 'xor {rd}, {rs1}, {rs2}' } + code { rd[] = rs1 ^ rs2 } + end + + Instruction(:srl) do + encoding(*format_r(0b0110011, 0b101, 0b0000000)) + asm { 'srl {rd}, {rs1}, {rs2}' } + code { rd[] = rs1.u >> rs2.u } + end + + Instruction(:sra) do + encoding(*format_r(0b0110011, 0b101, 0b0100000)) + asm { 'sra {rd}, {rs1}, {rs2}' } + code { rd[] = rs1.s >> rs2.s } + end + + Instruction(:or) do + encoding(*format_r(0b0110011, 0b110, 0b0000000)) + asm { 'or {rd}, {rs1}, {rs2}' } + code { rd[] = rs1 | rs2 } + end + + Instruction(:and) do + encoding(*format_r(0b0110011, 0b111, 0b0000000)) + asm { 'and {rd}, {rs1}, {rs2}' } + code { rd[] = rs1 & rs2 } + end + + Instruction(:addi) do + encoding(*format_i(0b0010011, 0b000)) + asm { 'addi {rd}, {rs1}, {imm}' } + code { rd[] = rs1 + imm } + end + + Instruction(:slti) do + encoding(*format_i(0b0010011, 0b010)) + asm { 'slti {rd}, {rs1}, {imm}' } + code { rd[] = (rs1.s < imm).b32 } + end + + Instruction(:sltiu) do + encoding(*format_i(0b0010011, 0b011)) + asm { 'sltiu {rd}, {rs1}, {imm}' } + code { rd[] = (rs1.u < imm.u).b32 } + end + + Instruction(:xori) do + encoding(*format_i(0b0010011, 0b100)) + asm { 'xori {rd}, {rs1}, {imm}' } + code { rd[] = rs1 ^ imm } + end + + Instruction(:ori) do + encoding(*format_i(0b0010011, 0b110)) + asm { 'ori {rd}, {rs1}, {imm}' } + code { rd[] = rs1 | imm } + end + + Instruction(:andi) do + encoding(*format_i(0b0010011, 0b111)) + asm { 'andi {rd}, {rs1}, {imm}' } + code { rd[] = rs1 & imm } + end + + Instruction(:slli) do + encoding(*format_i_shift(0b0010011, 0b001, 0b00000)) + asm { 'slli {rd}, {rs1}, {imm}' } + code { rd[] = rs1 << imm } + end + + Instruction(:srli) do + encoding(*format_i_shift(0b0010011, 0b101, 0b00000)) + asm { 'srli {rd}, {rs1}, {imm}' } + code { rd[] = rs1 >> imm } + end + + Instruction(:srai) do + encoding(*format_i_shift(0b0010011, 0b101, 0b01000)) + asm { 'srai {rd}, {rs1}, {imm}' } + code { rd[] = rs1.s >> imm } + end + + Instruction(:beq) do + encoding(*format_b(0b1100011, 0b000)) + asm { 'beq {rs1}, {rs2}, {imm}' } + code { branch(select(rs1 == rs2, pc + imm, pc + xlen)) } + end + + Instruction(:bne) do + encoding(*format_b(0b1100011, 0b001)) + asm { 'bne {rs1}, {rs2}, {imm}' } + code { branch(select(rs1 != rs2, pc + imm, pc + xlen)) } + end + + Instruction(:blt) do + encoding(*format_b(0b1100011, 0b100)) + asm { 'blt {rs1}, {rs2}, {imm}' } + code { branch(select(rs1.s < rs2.s, pc + imm, pc + xlen)) } + end + + Instruction(:bge) do + encoding(*format_b(0b1100011, 0b101)) + asm { 'bge {rs1}, {rs2}, {imm}' } + code { branch(select(rs1.s >= rs2.s, pc + imm, pc + xlen)) } + end + + Instruction(:bltu) do + encoding(*format_b(0b1100011, 0b110)) + asm { 'bltu {rs1}, {rs2}, {imm}' } + code { branch(select(rs1.u < rs2.u, pc + imm, pc + xlen)) } + end + + Instruction(:bgeu) do + encoding(*format_b(0b1100011, 0b111)) + asm { 'bgeu {rs1}, {rs2}, {imm}' } + code { branch(select(rs1.u >= rs2.u, pc + imm, pc + xlen)) } + end + + Instruction(:jal) do + encoding(*format_j(0b1101111)) + asm { 'jal {rd}, {imm}' } + code do + rd[] = pc + xlen + branch(pc + imm) + end + end + + Instruction(:jalr) do + encoding(*format_i(0b1100111, 0b000)) + asm { 'jalr {rd}, {rs1}, {imm}' } + code do + let :t, :b32, pc + xlen + branch((rs1 + imm) & (~1)) + rd[] = t + end + end + + Instruction(:sb) do + encoding(*format_s(0b0100011, 0b000)) + asm { 'sb {rs2}, {imm}({rs1})' } + code { mem[rs1 + imm] = rs2[7, 0] } + end + + Instruction(:sh) do + encoding(*format_s(0b0100011, 0b001)) + asm { 'sh {rs2}, {imm}({rs1})' } + code { mem[rs1 + imm] = rs2[15, 0] } + end + + Instruction(:sw) do + encoding(*format_s(0b0100011, 0b010)) + asm { 'sw {rs2}, {imm}({rs1})' } + code { mem[rs1 + imm] = rs2 } + end + + Instruction(:lb) do + encoding(*format_i(0b0000011, 0b000)) + asm { 'lb {rd}, {imm}({rs1})' } + code { rd[] = mem[rs1 + imm, :b8].s32 } + end + + Instruction(:lh) do + encoding(*format_i(0b0000011, 0b001)) + asm { 'lh {rd}, {imm}({rs1})' } + code { rd[] = mem[rs1 + imm, :b16].s32 } + end + + Instruction(:lw) do + encoding(*format_i(0b0000011, 0b010)) + asm { 'lw {rd}, {imm}({rs1})' } + code { rd[] = mem[rs1 + imm, :b32] } + end + + Instruction(:lbu) do + encoding(*format_i(0b0000011, 0b100)) + asm { 'lbu {rd}, {imm}({rs1})' } + code { rd[] = mem[rs1 + imm, :b8].u32 } + end + + Instruction(:lhu) do + encoding(*format_i(0b0000011, 0b101)) + asm { 'lhu {rd}, {imm}({rs1})' } + code { rd[] = mem[rs1 + imm, :b16].u32 } + end + + Instruction(:ecall) do + encoding :E, [field(:c, 31, 0, 0b1110011)] + asm { 'ecall' } + code { sysCall } + end + + Instruction(:ebreak) do + encoding :E, [field(:c, 31, 0, 0b100000000000001110011)] + asm { 'ebreak' } + code {} + end + + Instruction(:fence) do + encoding :E, + [field(:c1, 31, 28, 0b0000), field(:c2, 27, 24), field(:c3, 23, 20), + field(:c4, 19, 0, 0b00000000000000001111)] + asm { 'fence' } + code {} + end end diff --git a/lib/Target/RISC-V/32ILoops.rb b/lib/Target/RISC-V/32ILoops.rb index 07b0b06..9f38175 100644 --- a/lib/Target/RISC-V/32ILoops.rb +++ b/lib/Target/RISC-V/32ILoops.rb @@ -1,194 +1,196 @@ -require_relative "encoding" -require_relative "../../Generic/base" -require_relative "../../Generic/builder" +# PROPOSAL: autoformat +require_relative 'encoding' +require_relative '../../Generic/base' +require_relative '../../Generic/builder' module Ops - - class Add; def self.op(a, b); a.u + b.u; end; end - class Sub; def self.op(a, b); a.u - b.u; end; end - class Sll; def self.op(a, b); a.u << b.u; end; end - class Slt; def self.op(a, b); (a.s < b.s).b; end; end - class Sltu; def self.op(a, b); (a.u < b.u).b; end; end - class Xor; def self.op(a, b); a ^ b; end; end - class Srl; def self.op(a, b); a.u >> b.u; end; end - class Sra; def self.op(a, b); a.s >> b.u; end; end - class Or; def self.op(a, b); a | b; end; end - class And; def self.op(a, b); a & b; end; end - class Load8; def self.op(rs1, imm); mem[rs1 + imm, :b8].s32; end; end - class Load16; def self.op(rs1, imm); mem[rs1 + imm, :b16].s32; end; end - class Load32; def self.op(rs1, imm); mem[rs1 + imm, :b32]; end; end - class Load8U; def self.op(rs1, imm); mem[rs1 + imm, :b8].u32; end; end - class Load16U; def self.op(rs1, imm); mem[rs1 + imm, :b16].u32; end; end - + class Add; def self.op(a, b) = a.u + b.u; end + class Sub; def self.op(a, b) = a.u - b.u; end + class Sll; def self.op(a, b) = a.u.<<(b.u); end + class Slt; def self.op(a, b) = (a.s < b.s).b; end + class Sltu; def self.op(a, b) = (a.u < b.u).b; end + class Xor; def self.op(a, b) = a.^(b); end + class Srl; def self.op(a, b) = a.u.>>(b.u); end + class Sra; def self.op(a, b) = a.s.>>(b.u); end + class Or; def self.op(a, b) = a.|(b); end + class And; def self.op(a, b) = a.&(b); end + class Load8; def self.op(rs1, imm) = mem[rs1 + imm, :b8].s32; end + class Load16; def self.op(rs1, imm) = mem[rs1 + imm, :b16].s32; end + class Load32; def self.op(rs1, imm) = mem.[](rs1 + imm, :b32); end + class Load8U; def self.op(rs1, imm) = mem[rs1 + imm, :b8].u32; end + class Load16U; def self.op(rs1, imm) = mem[rs1 + imm, :b16].u32; end end module RV32I - include SimInfra - extend SimInfra - - RegisterFile(:XRegs) { - r32 :x0, zero - for x in (1..31); r32 "x" + x.to_s; end - r32 :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[]= imm + pc } - } - - TABLE_R_FORMAT_INSTRUCTIONS = [ - [:add, format_r(0b0110011, 0b000, 0b0000000), Ops::Add], - [:sub, format_r(0b0110011, 0b000, 0b0100000), Ops::Sub], - [:sll, format_r(0b0110011, 0b001, 0b0000000), Ops::Sll], - [:slt, format_r(0b0110011, 0b010, 0b0000000), Ops::Slt], - [:sltu, format_r(0b0110011, 0b011, 0b0000000), Ops::Sltu], - [:xor, format_r(0b0110011, 0b100, 0b0000000), Ops::Xor], - [:srl, format_r(0b0110011, 0b101, 0b0000000), Ops::Srl], - [:sra, format_r(0b0110011, 0b101, 0b0100000), Ops::Sra], - [:or, format_r(0b0110011, 0b110, 0b0000000), Ops::Or], - [:and, format_r(0b0110011, 0b111, 0b0000000), Ops::And], - ] - - for insn in TABLE_R_FORMAT_INSTRUCTIONS - Instruction(insn[0]) { - encoding *insn[1] - asm { insn[0].to_s + "{rd}, {rs1}, {rs2}" } - code { rd[]= insn[2].op(rs1, rs2) } - } + include SimInfra + extend SimInfra + + RegisterFile(:XRegs) do + r32 :x0, zero + for x in (1..31); r32 'x' + x.to_s; end + r32 :pc + end + + Instruction(:lui) do + encoding(*format_u(0b0110111)) + asm { 'lui {rd}, {imm}' } + code { rd[] = imm } + end + + Instruction(:auipc) do + encoding(*format_u(0b0010111)) + asm { 'auipc {rd}, {imm}' } + code { rd[] = imm + pc } + end + + TABLE_R_FORMAT_INSTRUCTIONS = [ + [:add, format_r(0b0110011, 0b000, 0b0000000), Ops::Add], + [:sub, format_r(0b0110011, 0b000, 0b0100000), Ops::Sub], + [:sll, format_r(0b0110011, 0b001, 0b0000000), Ops::Sll], + [:slt, format_r(0b0110011, 0b010, 0b0000000), Ops::Slt], + [:sltu, format_r(0b0110011, 0b011, 0b0000000), Ops::Sltu], + [:xor, format_r(0b0110011, 0b100, 0b0000000), Ops::Xor], + [:srl, format_r(0b0110011, 0b101, 0b0000000), Ops::Srl], + [:sra, format_r(0b0110011, 0b101, 0b0100000), Ops::Sra], + [:or, format_r(0b0110011, 0b110, 0b0000000), Ops::Or], + [:and, format_r(0b0110011, 0b111, 0b0000000), Ops::And] + ] + + for insn in TABLE_R_FORMAT_INSTRUCTIONS + Instruction(insn[0]) do + encoding(*insn[1]) + asm { insn[0].to_s + '{rd}, {rs1}, {rs2}' } + code { rd[] = insn[2].op(rs1, rs2) } end - - TABLE_I_FORMAT_INSTRUCTIONS = [ - [:addi, format_i(0b0010011, 0b000), Ops::Add], - [:xori, format_i(0b0010011, 0b100), Ops::Xor], - [:ori, format_i(0b0010011, 0b110), Ops::Or], - [:andi, format_i(0b0010011, 0b111), Ops::And], - ] - - for insn in TABLE_I_FORMAT_INSTRUCTIONS - Instruction(insn[0]) { - encoding *insn[1] - asm { insn[0].to_s + "{rd}, {rs1}, {imm}" } - code { rd[]= insn[2].op(rs1, imm) } - } + end + + TABLE_I_FORMAT_INSTRUCTIONS = [ + [:addi, format_i(0b0010011, 0b000), Ops::Add], + [:xori, format_i(0b0010011, 0b100), Ops::Xor], + [:ori, format_i(0b0010011, 0b110), Ops::Or], + [:andi, format_i(0b0010011, 0b111), Ops::And] + ] + + for insn in TABLE_I_FORMAT_INSTRUCTIONS + Instruction(insn[0]) do + encoding(*insn[1]) + asm { insn[0].to_s + '{rd}, {rs1}, {imm}' } + code { rd[] = insn[2].op(rs1, imm) } end - - 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 < rs2, pc + imm, pc + xlen)) } - } - - Instruction(:bge) { - encoding *format_b(0b1100011, 0b101) - asm { "bge {rs1}, {rs2}, {imm}" } - code { branch(select(rs1 > rs2, 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(0b1101111, 0b000) - asm { "jalr {rd}, {rs1}, {imm}" } - code { - let :t, :b32, 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 } - } - - Instruction(:lb) { - encoding *format_i(0b0100011, 0b000) - asm { "lb {rd}, {imm}({rs1})" } - code { rd[]= mem[rs1 + imm, :b8].s32 } - } - - Instruction(:lh) { - encoding *format_i(0b0100011, 0b001) - asm { "lh {rd}, {imm}({rs1})" } - code { rd[]= mem[rs1 + imm, :b16].s32 } - } - - Instruction(:lw) { - encoding *format_i(0b0100011, 0b010) - asm { "lw {rd}, {imm}({rs1})" } - code { rd[]= mem[rs1 + imm, :b32] } - } - - Instruction(:lbu) { - encoding *format_i(0b0100011, 0b100) - asm { "lbu {rd}, {imm}({rs1})" } - code { rd[]= mem[rs1 + imm, :b8].u32 } - } - - Instruction(:lhu) { - encoding *format_i(0b0100011, 0b101) - asm { "lhu {rd}, {imm}({rs1})" } - code { rd[]= mem[rs1 + imm, :b16].u32 } - } - - Instruction(:ecall) { - encoding :E, [field(:c, 31, 0, 0b1110011)] - asm { "ecall" } - code { } - } - - Instruction(:ebreak) { - encoding :E, [field(:c, 31, 0, 0b100000000000001110011)] - asm { "ebreak" } - code { } - } + end + + Instruction(:beq) do + encoding(*format_b(0b1100011, 0b000)) + asm { 'beq {rs1}, {rs2}, {imm}' } + code { branch(select(rs1 == rs2, pc + imm, pc + xlen)) } + end + + Instruction(:bne) do + encoding(*format_b(0b1100011, 0b001)) + asm { 'bne {rs1}, {rs2}, {imm}' } + code { branch(select(rs1 != rs2, pc + imm, pc + xlen)) } + end + + Instruction(:blt) do + encoding(*format_b(0b1100011, 0b100)) + asm { 'blt {rs1}, {rs2}, {imm}' } + code { branch(select(rs1 < rs2, pc + imm, pc + xlen)) } + end + + Instruction(:bge) do + encoding(*format_b(0b1100011, 0b101)) + asm { 'bge {rs1}, {rs2}, {imm}' } + code { branch(select(rs1 > rs2, pc + imm, pc + xlen)) } + end + + Instruction(:bltu) do + encoding(*format_b(0b1100011, 0b110)) + asm { 'bltu {rs1}, {rs2}, {imm}' } + code { branch(select(rs1.u < rs2.u, pc + imm, pc + xlen)) } + end + + Instruction(:bgeu) do + encoding(*format_b(0b1100011, 0b111)) + asm { 'bgeu {rs1}, {rs2}, {imm}' } + code { branch(select(rs1.u > rs2.u, pc + imm, pc + xlen)) } + end + + Instruction(:jal) do + encoding(*format_j(0b1101111)) + asm { 'jal {rd}, {imm}' } + code do + rd[] = pc + xlen + branch(pc + imm) + end + end + + Instruction(:jalr) do + encoding(*format_i(0b1101111, 0b000)) + asm { 'jalr {rd}, {rs1}, {imm}' } + code do + let :t, :b32, pc + xlen + branch((rs1 + imm) & (~1)) + rd[] = t + end + end + + Instruction(:sb) do + encoding(*format_s(0b0100011, 0b000)) + asm { 'sb {rs2}, {imm}({rs1})' } + code { mem[rs1 + imm] = rs2[7, 0] } + end + + Instruction(:sh) do + encoding(*format_s(0b0100011, 0b001)) + asm { 'sh {rs2}, {imm}({rs1})' } + code { mem[rs1 + imm] = rs2[15, 0] } + end + + Instruction(:sw) do + encoding(*format_s(0b0100011, 0b010)) + asm { 'sw {rs2}, {imm}({rs1})' } + code { mem[rs1 + imm] = rs2 } + end + + Instruction(:lb) do + encoding(*format_i(0b0100011, 0b000)) + asm { 'lb {rd}, {imm}({rs1})' } + code { rd[] = mem[rs1 + imm, :b8].s32 } + end + + Instruction(:lh) do + encoding(*format_i(0b0100011, 0b001)) + asm { 'lh {rd}, {imm}({rs1})' } + code { rd[] = mem[rs1 + imm, :b16].s32 } + end + + Instruction(:lw) do + encoding(*format_i(0b0100011, 0b010)) + asm { 'lw {rd}, {imm}({rs1})' } + code { rd[] = mem[rs1 + imm, :b32] } + end + + Instruction(:lbu) do + encoding(*format_i(0b0100011, 0b100)) + asm { 'lbu {rd}, {imm}({rs1})' } + code { rd[] = mem[rs1 + imm, :b8].u32 } + end + + Instruction(:lhu) do + encoding(*format_i(0b0100011, 0b101)) + asm { 'lhu {rd}, {imm}({rs1})' } + code { rd[] = mem[rs1 + imm, :b16].u32 } + end + + Instruction(:ecall) do + encoding :E, [field(:c, 31, 0, 0b1110011)] + asm { 'ecall' } + code {} + end + + Instruction(:ebreak) do + encoding :E, [field(:c, 31, 0, 0b100000000000001110011)] + asm { 'ebreak' } + code {} + end end diff --git a/lib/Target/RISC-V/64F.rb b/lib/Target/RISC-V/64F.rb new file mode 100644 index 0000000..45884e0 --- /dev/null +++ b/lib/Target/RISC-V/64F.rb @@ -0,0 +1,438 @@ +# PROPOSAL: +# Add rv64f isa description +require_relative '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] + 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/encoding.rb b/lib/Target/RISC-V/encoding.rb index 5ff0310..2b0dcbb 100644 --- a/lib/Target/RISC-V/encoding.rb +++ b/lib/Target/RISC-V/encoding.rb @@ -1,109 +1,239 @@ -require_relative "../../ADL/base" +# PROPOSAL: autoformat +require_relative '../../ADL/base' module SimInfra - def u_imm(imm) - return imm, :s32, "let :#{imm}, [:op], :s32, f_#{imm}.s << 12" - end + def u_imm(imm) + [imm, :s32, "let :#{imm}, [:op], :s32, f_#{imm}.s << 12"] + end - def i_imm(imm) - return imm, :s32, "let :#{imm}, [:op], :s32, f_#{imm}.s32" - end + def i_imm(imm) + [imm, :s32, "let :#{imm}, [:op], :s32, f_#{imm}.s32"] + end - def is_imm(imm) - return imm, :s32, "let :#{imm}, [:op], :s32, f_imm4_0" - end + def is_imm(imm) + [imm, :s32, "let :#{imm}, [:op], :s32, f_imm4_0"] + end - def j_imm(imm) - return imm, :s32, "let :#{imm}, [:op], :s32, (f_imm20.b21 << 20 | f_imm19_12.b21 << 12 | f_imm11.b21 << 11 | f_imm10_1.b21 << 1).s32" - end + def j_imm(imm) + [imm, :s32, + "let :#{imm}, [:op], :s32, (f_imm20.b21 << 20 | f_imm19_12.b21 << 12 | f_imm11.b21 << 11 | f_imm10_1.b21 << 1).s32"] + end - def b_imm(imm) - return imm, :s32, "let :#{imm}, [:op], :s32, (f_imm12.b13 << 12 | f_imm11.b13 << 11 | f_imm10_5.b13 << 5 | f_imm4_1.b13 << 1).s32" - end + def b_imm(imm) + [imm, :s32, + "let :#{imm}, [:op], :s32, (f_imm12.b13 << 12 | f_imm11.b13 << 11 | f_imm10_5.b13 << 5 | f_imm4_1.b13 << 1).s32"] + end - def s_imm(imm) - return imm, :s32, "let :#{imm}, [:op], :s32, (f_imm11_5.b12 << 5 | f_imm4_0.b12).s32" - end + def s_imm(imm) + [imm, :s32, "let :#{imm}, [:op], :s32, (f_imm11_5.b12 << 5 | f_imm4_0.b12).s32"] + end - def xreg(name) - return name, :r32, "let :#{name}, :XRegs, [:op], :r32, f_#{name}" - end + def xreg(name) + [name, :r32, "let :#{name}, :XRegs, [:op], :r32, f_#{name}"] + end + + def freg(name) + [name, :f64, "let :#{name}, :FRegs, [:op], :f64, f_#{name}"] + end end module SimInfra - def format_u(opcode) - return :U, [ - field(:f_opcode, 6, 0, opcode), - field(:f_rd, 11, 7), - field(:f_imm, 31, 12), - ], xreg(:rd), u_imm(:imm) - end - - def format_r(opcode, funct3, funct7) - return :R, [ - field(:f_opcode, 6, 0, opcode), - field(:f_rd, 11, 7), - field(:f_funct3, 14, 12, funct3), - field(:f_rs1, 19, 15), - field(:f_rs2, 24, 20), - field(:f_funct7, 31, 25, funct7), - ], xreg(:rs2), xreg(:rs1), xreg(:rd) - end - - def format_i(opcode, funct3) - return :I, [ - field(:f_opcode, 6, 0, opcode), - field(:f_rd, 11, 7), - field(:f_funct3, 14, 12, funct3), - field(:f_rs1, 19, 15), - field(:f_imm, 31, 20), - ], i_imm(:imm), xreg(:rs1), xreg(:rd) - end - - def format_i_shift(opcode, func3, sopcode) - return :srai, [ - field(:f_opcode, 6, 0, opcode), - field(:func3, 14, 12, func3), - field(:f_imm4_0, 24, 20), - field(:f_rd, 11, 7), - field(:f_rs1, 19, 15), - field(:f_temp, 26, 25, 0b01), - field(:f_sopcode, 31, 27, sopcode), - ], is_imm(:imm), xreg(:rs1), xreg(:rd) - end - - def format_b(opcode, funct3) - return :B, [ - field(:f_opcode, 6, 0, opcode), - field(:f_funct3, 14, 12, funct3), - field(:f_rs1, 19, 15), - field(:f_rs2, 24, 20), - field(:f_imm4_1, 11, 8), - field(:f_imm10_5, 30, 25), - field(:f_imm11, 7, 7), - field(:f_imm12, 31, 31), - ], b_imm(:imm), xreg(:rs1), xreg(:rs2) - end - - def format_j(opcode) - return :J, [ - field(:f_opcode, 6, 0, opcode), - field(:f_rd, 11, 7), - field(:f_imm20, 31, 31), - field(:f_imm19_12, 19, 12), - field(:f_imm11, 20, 20), - field(:f_imm10_1, 30, 21), - ], j_imm(:imm), xreg(:rd) - end - - def format_s(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_rs2, 24, 20), - field(:f_imm11_5, 31, 25), - ], s_imm(:imm), xreg(:rs1), xreg(:rs2) - end + def format_u(opcode) + [:U, [ + field(:f_opcode, 6, 0, opcode), + field(:f_rd, 11, 7), + field(:f_imm, 31, 12) + ], xreg(:rd), u_imm(:imm)] + end + + def format_r(opcode, funct3, funct7) + [:R, [ + field(:f_opcode, 6, 0, opcode), + field(:f_rd, 11, 7), + field(:f_funct3, 14, 12, funct3), + field(:f_rs1, 19, 15), + field(:f_rs2, 24, 20), + field(:f_funct7, 31, 25, funct7) + ], xreg(:rs2), xreg(:rs1), xreg(:rd)] + end + + # PROPOSAL: + # Add FP encoding formats. + def format_r4_fp(opcode, funct2, rm) + [: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) + [: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) + [: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) + [: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) + [: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) + [: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) + [: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) + [: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) + [: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(opcode, funct3) + [:I, [ + field(:f_opcode, 6, 0, opcode), + field(:f_rd, 11, 7), + field(:f_funct3, 14, 12, funct3), + field(:f_rs1, 19, 15), + field(:f_imm, 31, 20) + ], i_imm(:imm), xreg(:rs1), xreg(:rd)] + end + + def format_i_fp(opcode, funct3) + [: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_i_shift(opcode, func3, sopcode) + [:srai, [ + field(:f_opcode, 6, 0, opcode), + field(:func3, 14, 12, func3), + field(:f_imm4_0, 24, 20), + field(:f_rd, 11, 7), + field(:f_rs1, 19, 15), + field(:f_temp, 26, 25, 0b01), + field(:f_sopcode, 31, 27, sopcode) + ], is_imm(:imm), xreg(:rs1), xreg(:rd)] + end + + def format_b(opcode, funct3) + [:B, [ + field(:f_opcode, 6, 0, opcode), + field(:f_funct3, 14, 12, funct3), + field(:f_rs1, 19, 15), + field(:f_rs2, 24, 20), + field(:f_imm4_1, 11, 8), + field(:f_imm10_5, 30, 25), + field(:f_imm11, 7, 7), + field(:f_imm12, 31, 31) + ], b_imm(:imm), xreg(:rs1), xreg(:rs2)] + end + + def format_j(opcode) + [:J, [ + field(:f_opcode, 6, 0, opcode), + field(:f_rd, 11, 7), + field(:f_imm20, 31, 31), + field(:f_imm19_12, 19, 12), + field(:f_imm11, 20, 20), + field(:f_imm10_1, 30, 21) + ], j_imm(:imm), xreg(:rd)] + end + + def format_s(opcode, func3) + [: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_rs2, 24, 20), + field(:f_imm11_5, 31, 25) + ], s_imm(:imm), xreg(:rs1), xreg(:rs2)] + end + + def format_s_fp(opcode, func3) + [: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..600d489 --- /dev/null +++ b/lib/Utility/fp_helper_cpp.rb @@ -0,0 +1,29 @@ +# PROPOSAL: +# Add utility methods for FP operatiosn to support typing of softfloat. +# float32_t and float64_t are actually C structures that contain a +# uint32_t / uint64_t raw bytes memories. +# Utility methods for FP instructions generation +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..4efe81e 100644 --- a/lib/Utility/type.rb +++ b/lib/Utility/type.rb @@ -1,43 +1,47 @@ +# PROPOSAL: autoformat module Utility - # The instances of the Type class are immutable. - # Only one instance of a particular type is ever created - # and this instance is linked to one symbol. If two types - # are equal is a matter of doing a trivial symbol comparison. - class Type - attr_reader :name, :bitsize, :typeof - def initialize(name, bitsize, typeof) - @name = name; @bitsize = bitsize; @typeof = typeof; @sym = name.to_sym - end + # The instances of the Type class are immutable. + # Only one instance of a particular type is ever created + # and this instance is linked to one symbol. If two types + # are equal is a matter of doing a trivial symbol comparison. + class Type + attr_reader :name, :bitsize, :typeof + + def initialize(name, bitsize, typeof) + @name = name + @bitsize = bitsize + @typeof = typeof + @sym = name.to_sym end + end - $ExistingTypes = {} + $ExistingTypes = {} - # Returns the Type object associated with the symbol. - # Currently supported types: signed/unsigned integral, bit type, register type - # (like pointer in C++/C) - # If symbol is not [usbr][1-9][0-9]* the behaviour is undefined - # - # get_type :b33 -> # - # get_type :i32 -> # - # get_type :u64 -> # - def get_type(sym) - if $ExistingTypes.has_key?(sym) - return $ExistingTypes[sym] - end - sym_str = sym.to_s - $ExistingTypes[sym] = Type.new(sym_str, sym_str.scan(/\d+/).last.to_i, sym_str[0].to_sym) - return $ExistingTypes[sym] - end + # Returns the Type object associated with the symbol. + # Currently supported types: signed/unsigned integral, bit type, register type + # (like pointer in C++/C) + # If symbol is not [usbr][1-9][0-9]* the behaviour is undefined + # + # get_type :b33 -> # + # get_type :i32 -> # + # get_type :u64 -> # + def get_type(sym) + return $ExistingTypes[sym] if $ExistingTypes.has_key?(sym) + + sym_str = sym.to_s + $ExistingTypes[sym] = Type.new(sym_str, sym_str.scan(/\d+/).last.to_i, sym_str[0].to_sym) + $ExistingTypes[sym] + end - # 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 + # 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) + get_type(sym1).typeof == get_type(sym2).typeof + end -private_constant :Type -module_function :get_type, :equal_typeof + private_constant :Type + module_function :get_type, :equal_typeof end diff --git a/lib/ir_gen.rb b/lib/ir_gen.rb index cde48e4..cb99d3d 100644 --- a/lib/ir_gen.rb +++ b/lib/ir_gen.rb @@ -4,7 +4,7 @@ require 'ADL/base' require 'ADL/builder' require 'Target/RISC-V/32I' - +require 'Target/RISC-V/64F' require 'yaml' yaml_data = SimInfra.serialize diff --git a/ser2ruby/base2ruby.rb b/ser2ruby/base2ruby.rb index b119979..54cec6d 100644 --- a/ser2ruby/base2ruby.rb +++ b/ser2ruby/base2ruby.rb @@ -1,4 +1,5 @@ -require "Utility/gen_emitter" +# PROPOSAL: autoformat +require 'Utility/gen_emitter' # Helper methods for Intermediate Representation module IRHelper @@ -55,12 +56,11 @@ 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 - -"module #{isa_name.upcase} + "module #{isa_name.upcase} #{regfiles} #{instructions} @@ -69,10 +69,9 @@ def ir2ruby(ir) end end - require 'yaml' yaml_data = YAML.load_file('sim_lib/generated/IR.yaml') -yaml_data[:isa_name] = "RISCV" +yaml_data[:isa_name] = 'RISCV' puts IRHelper.ir2ruby(yaml_data) diff --git a/sim_gen/CMakeLists.txt b/sim_gen/CMakeLists.txt index db5540a..9bea144 100644 --- a/sim_gen/CMakeLists.txt +++ b/sim_gen/CMakeLists.txt @@ -41,6 +41,6 @@ add_executable(sim ${PROTEA_SIMGEN_OUTPUT_SOURCES} ${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..d1ce2d8 100644 --- a/sim_gen/CPUState/cpu_state.rb +++ b/sim_gen/CPUState/cpu_state.rb @@ -1,3 +1,4 @@ +# PROPOSAL: autoformat # frozen_string_literal: true require 'lib/Utility/gen_emitter' @@ -132,10 +133,10 @@ def generate_do_exit_func def generate_dump_func(regfiles) emitter = Utility::GenEmitter.new - emitter.emit_line("void CPU::dump(std::ostream &ost) const {") + emitter.emit_line('void CPU::dump(std::ostream &ost) const {') emitter.increase_indent - emitter.emit_line("fmt::println(ost, \"---CPU STATE DUMP---\");") + emitter.emit_line('fmt::println(ost, "---CPU STATE DUMP---");') regfiles.each do |regfile| regfile[:regs].each do |register| emitter.emit_line("fmt::print(ost, \"X[{:02}] = {:#010x} \", effIdx, get#{regfile[:name]}());") @@ -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]) @@ -165,7 +166,7 @@ def generate_cpu_state(input_ir) increase_icount_func = Helper.increase_icount_func base_type = Utility::HelperCpp.gen_type input_ir[:regfiles][0][:regs][0][:size] -"#ifndef GENERATED_#{input_ir[:isa_name].upcase}_CPUSTATE_HH_INCLUDED + "#ifndef GENERATED_#{input_ir[:isa_name].upcase}_CPUSTATE_HH_INCLUDED #define GENERATED_#{input_ir[:isa_name].upcase}_CPUSTATE_HH_INCLUDED #include \"memory.hh\" diff --git a/sim_gen/Decoders/decoder.rb b/sim_gen/Decoders/decoder.rb index 4d2689f..eb32869 100644 --- a/sim_gen/Decoders/decoder.rb +++ b/sim_gen/Decoders/decoder.rb @@ -3,257 +3,253 @@ 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) + + # PROPOSAL: + # Split ranges can contain bits that are fixed for one instruction + # format and non-fixed for others. + # e.g. in R4 FP instructions 31:27 are frs3, while FP R-format uses + # 31-24 as a fixed funct7. + # The solution is to check relevant mask, instead of skipping + # instruction, if separ_mask != insn_mask: + 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 +297,18 @@ def generate_decoder(input_ir) namespace prot::decoder { using namespace isa; -std::optional decode(const isa::Word raw_insn) { +// PROPOSAL: +// Temporary: the current RV64 work still reuses RV32 instruction width, +// because isa::Word type is deducted from biggest reg size from regfile, which +// is 64 bit (FP regs). That conflicts with 32I that is already implemented. +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/ISA/isa.rb b/sim_gen/ISA/isa.rb index bed0076..4ac9920 100644 --- a/sim_gen/ISA/isa.rb +++ b/sim_gen/ISA/isa.rb @@ -1,105 +1,107 @@ +# PROPOSAL: autoformat module SimGen - module ISA - module Helper - module_function - def find_max_reg(regfiles) - max_reg_size = 0 - regfiles.each do |regfile| - regfile[:regs].each do |reg| - if reg[:size] > max_reg_size - max_reg_size = reg[:size] - end - end - end - max_reg_size - end - - def find_max_operands(instructions) - max_operands = 0 - max_size = 0 - instructions.each do |insn| - operands_count = 0 - insn[:map][:tree].each do |node| - if node[:name] == :new_var && !node[:attrs].nil? && node[:attrs].include?(:op) - operands_count += 1 - max_size = Utility.get_type(node[:oprnds][0][:type]).bitsize if Utility.get_type(node[:oprnds][0][:type]).bitsize > max_size - end - end - max_operands = operands_count if operands_count > max_operands - end - [max_operands, max_size] - end - - def generate_fields_struct(max_operands, max_size) - emitter = Utility::GenEmitter.new - for i in 0...max_operands - emitter.emit_line("uint#{max_size}_t operand#{i};") - end - emitter.increase_indent_all(2) - emitter + module ISA + module Helper + module_function + + def find_max_reg(regfiles) + max_reg_size = 0 + regfiles.each do |regfile| + regfile[:regs].each do |reg| + max_reg_size = reg[:size] if reg[:size] > max_reg_size + end + end + max_reg_size + end + + def find_max_operands(instructions) + max_operands = 0 + max_size = 0 + instructions.each do |insn| + operands_count = 0 + insn[:map][:tree].each do |node| + next unless node[:name] == :new_var && !node[:attrs].nil? && node[:attrs].include?(:op) + + operands_count += 1 + if Utility.get_type(node[:oprnds][0][:type]).bitsize > max_size + max_size = Utility.get_type(node[:oprnds][0][:type]).bitsize end + end + max_operands = operands_count if operands_count > max_operands + end + [max_operands, max_size] + end - def generate_instruction_struct(input_ir) - max_operands, max_size = find_max_operands(input_ir[:instructions]) - fields_struct = generate_fields_struct(max_operands, max_size) -"struct Instruction { + def generate_fields_struct(max_operands, max_size) + emitter = Utility::GenEmitter.new + for i in 0...max_operands + emitter.emit_line("uint#{max_size}_t operand#{i};") + end + emitter.increase_indent_all(2) + emitter + end + + def generate_instruction_struct(input_ir) + max_operands, max_size = find_max_operands(input_ir[:instructions]) + fields_struct = generate_fields_struct(max_operands, max_size) + "struct Instruction { Opcode m_opc; -#{fields_struct.to_s} +#{fields_struct} };" - end + end - def get_addr_type(instructions) - instructions.each { |insn| - insn[:code][:tree].each { |node| - return node[:oprnds][0][:type] if node[:name] == :writeMem || node[:name] == :readMem - } - } - end - - def is_terminator_instruction(insn) - insn[:code][:tree].each { |node| - return true if node[:name] == :branch - } - false - end + def get_addr_type(instructions) + instructions.each do |insn| + insn[:code][:tree].each do |node| + return node[:oprnds][0][:type] if %i[writeMem readMem].include?(node[:name]) + end + end + end - def generate_is_terminator_function(input_ir) - emitter = Utility::GenEmitter.new - emitter.emit_line("inline constexpr bool isTerminator(Opcode opc) {") - emitter.increase_indent - emitter.emit_line("switch (opc) {") - emitter.increase_indent - input_ir[:instructions].each do |insn| - emitter.emit_line("case Opcode::k#{insn[:name].to_s.upcase}:") if is_terminator_instruction(insn) - end - emitter.emit_line("return true;") - emitter.decrease_indent - emitter.emit_line("default:") - emitter.increase_indent - emitter.emit_line("return false;") - emitter.decrease_indent - emitter.emit_line("}") - emitter.decrease_indent - emitter.emit_line("}") - emitter - end + def is_terminator_instruction(insn) + insn[:code][:tree].each do |node| + return true if node[:name] == :branch + end + false + end + + def generate_is_terminator_function(input_ir) + emitter = Utility::GenEmitter.new + emitter.emit_line('inline constexpr bool isTerminator(Opcode opc) {') + emitter.increase_indent + emitter.emit_line('switch (opc) {') + emitter.increase_indent + input_ir[:instructions].each do |insn| + emitter.emit_line("case Opcode::k#{insn[:name].to_s.upcase}:") if is_terminator_instruction(insn) end + emitter.emit_line('return true;') + emitter.decrease_indent + emitter.emit_line('default:') + emitter.increase_indent + emitter.emit_line('return false;') + emitter.decrease_indent + emitter.emit_line('}') + emitter.decrease_indent + emitter.emit_line('}') + emitter + end end + end end module SimGen - module ISA - module Header - module_function - - def generate_isa_header(input_ir) - type = Helper.get_addr_type input_ir[:instructions] - type_str = Utility::HelperCpp::gen_type type - - instruction_struct = Helper.generate_instruction_struct(input_ir) - is_terminator_function = Helper.generate_is_terminator_function(input_ir) - max_xlen = SimGen::Helper::find_max_xlen(input_ir[:regfiles]) -"#ifndef GENERATED_#{input_ir[:isa_name].upcase}_ISA_HH_INCLUDED + module ISA + module Header + module_function + + def generate_isa_header(input_ir) + type = Helper.get_addr_type input_ir[:instructions] + type_str = Utility::HelperCpp.gen_type type + + instruction_struct = Helper.generate_instruction_struct(input_ir) + is_terminator_function = Helper.generate_is_terminator_function(input_ir) + max_xlen = SimGen::Helper.find_max_xlen(input_ir[:regfiles]) + "#ifndef GENERATED_#{input_ir[:isa_name].upcase}_ISA_HH_INCLUDED #define GENERATED_#{input_ir[:isa_name].upcase}_ISA_HH_INCLUDED #include @@ -112,9 +114,9 @@ def generate_isa_header(input_ir) #{input_ir[:instructions].map { |insn| " k#{insn[:name].to_s.upcase}," }.join("\n")} }; -#{instruction_struct.to_s} +#{instruction_struct} -#{is_terminator_function.to_s} +#{is_terminator_function} inline constexpr std::size_t getILen(Opcode opc) { switch (opc) { @@ -126,7 +128,7 @@ def generate_isa_header(input_ir) } // namespace prot::isa #endif // GENERATED_#{input_ir[:isa_name].upcase}_ISA_HH_INCLUDED" - end - end + end end + end end 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..8602b8b --- /dev/null +++ b/tests/rv64f/rv64f_tests.cc @@ -0,0 +1,410 @@ +// PROPOSAL: +// Add unit tests for rv64f. + +#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