From 20637eed2038e08b0a2fd3186c6900012153298b Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Fri, 6 Oct 2023 20:00:28 +0100 Subject: [PATCH] got it mostly working now --- examples/estimate-cpu-freq.asm | 2 +- riscemu/config.py | 2 +- riscemu/core/__init__.py | 78 ++-- riscemu/core/float.py | 29 +- riscemu/core/registers.py | 2 +- riscemu/debug.py | 2 +- riscemu/instructions/RV32D.py | 20 + riscemu/instructions/RV32F.py | 487 +----------------------- riscemu/instructions/RV_Debug.py | 11 +- riscemu/instructions/__init__.py | 22 +- riscemu/instructions/float_base.py | 388 +++++++++++++++++++ riscemu/instructions/instruction_set.py | 8 +- riscemu/riscemu_main.py | 4 +- test/filecheck/rv32f-conv.asm | 10 +- test/test_RV32F.py | 96 ++++- test/test_float32.py | 25 -- test/test_float_impl.py | 46 +++ test/test_regs.py | 13 +- 18 files changed, 648 insertions(+), 597 deletions(-) create mode 100644 riscemu/instructions/RV32D.py create mode 100644 riscemu/instructions/float_base.py delete mode 100644 test/test_float32.py create mode 100644 test/test_float_impl.py diff --git a/examples/estimate-cpu-freq.asm b/examples/estimate-cpu-freq.asm index b088caa..0b982d5 100644 --- a/examples/estimate-cpu-freq.asm +++ b/examples/estimate-cpu-freq.asm @@ -61,7 +61,7 @@ measure_loop: fcvt.s.w ft1, t0 // ft1 = 1k fdiv.s ft2, ft2, ft1 // ft2 = kins/sec - printf "executed {} instructions in {:.4f} seconds ({:.2f}ki/s)", s0, ft0, ft2 + printf "executed {} instructions in {:.4f32} seconds ({:.2f32}ki/s)", s0, ft0, ft2 mv ra, s4 ret diff --git a/riscemu/config.py b/riscemu/config.py index 2f1ea9b..cef17aa 100644 --- a/riscemu/config.py +++ b/riscemu/config.py @@ -21,7 +21,7 @@ class RunConfig: verbosity: int = 0 slowdown: float = 1 unlimited_registers: bool = False - flen: int = 32 + flen: int = 64 # runtime config use_libc: bool = False ignore_exit_code: bool = False diff --git a/riscemu/core/__init__.py b/riscemu/core/__init__.py index 746aead..c1968d4 100644 --- a/riscemu/core/__init__.py +++ b/riscemu/core/__init__.py @@ -47,43 +47,43 @@ from .usermode_cpu import UserModeCPU __all__ = [ - T_RelativeAddress, - T_AbsoluteAddress, - T_ParserOpts, - NUMBER_SYMBOL_PATTERN, - ParseException, - NumberFormatException, - MemoryAccessException, - OutOfMemoryException, - LinkerException, - LaunchDebuggerException, - RiscemuBaseException, - InvalidRegisterException, - InvalidAllocationException, - InvalidSyscallException, - UnimplementedInstruction, - INS_NOT_IMPLEMENTED, - MemoryFlags, - UInt32, - Int32, - BaseFloat, - Float32, - Float64, - RTClock, - Instruction, - Immediate, - InstructionWithEncoding, - InstructionContext, - MemorySection, - Program, - ProgramLoader, - PrivModes, - MMU, - CSR, - Registers, - CPU, - SimpleInstruction, - InstructionMemorySection, - BinaryDataMemorySection, - UserModeCPU, + "T_RelativeAddress", + "T_AbsoluteAddress", + "T_ParserOpts", + "NUMBER_SYMBOL_PATTERN", + "ParseException", + "NumberFormatException", + "MemoryAccessException", + "OutOfMemoryException", + "LinkerException", + "LaunchDebuggerException", + "RiscemuBaseException", + "InvalidRegisterException", + "InvalidAllocationException", + "InvalidSyscallException", + "UnimplementedInstruction", + "INS_NOT_IMPLEMENTED", + "MemoryFlags", + "UInt32", + "Int32", + "BaseFloat", + "Float32", + "Float64", + "RTClock", + "Instruction", + "Immediate", + "InstructionWithEncoding", + "InstructionContext", + "MemorySection", + "Program", + "ProgramLoader", + "PrivModes", + "MMU", + "CSR", + "Registers", + "CPU", + "SimpleInstruction", + "InstructionMemorySection", + "BinaryDataMemorySection", + "UserModeCPU", ] diff --git a/riscemu/core/float.py b/riscemu/core/float.py index 429d175..a73d30f 100644 --- a/riscemu/core/float.py +++ b/riscemu/core/float.py @@ -30,7 +30,7 @@ def bytes(self) -> bytes: @classmethod def from_bytes(cls, val: Union[bytes_t, bytearray]): - return BaseFloat(val) + return cls(val) def __init__( self, val: Union[float, c_float, "BaseFloat", bytes_t, bytearray, int] = 0 @@ -41,8 +41,7 @@ def __init__( self._val = self._type(val.value) elif isinstance(val, (bytes, bytearray)): self._val = self._type(struct.unpack("<" + self._struct_fmt_str, val)[0]) - self._val = self._type(struct.unpack("<" + self._struct_fmt_str, val)[0]) - elif isinstance(val, BaseFloat): + elif isinstance(val, self.__class__): self._val = val._val else: raise ValueError( @@ -105,9 +104,6 @@ def __repr__(self): def __str__(self): return str(self.value) - def __format__(self, format_spec: str): - return self.value.__format__(format_spec) - def __hash__(self): return hash(self.value) @@ -134,6 +130,12 @@ def __ge__(self, other: Any): def __bool__(self): return bool(self.value) + def __int__(self): + return int(self.value) + + def __float__(self): + return self.value + def __pow__(self, power, modulo=None): if modulo is not None: raise ValueError("Float32 pow with modulo unsupported") @@ -163,9 +165,12 @@ def __rmod__(self, other: Any): def bitcast(cls, f: "BaseFloat") -> "BaseFloat": """ bitcast the struct up or down to another type. + Fills upper bits with zero. Use Float64.bitcast(Float32(...)) to bitcast a f32 to f64 """ + if isinstance(f, cls): + return f return cls.from_bytes((b"\x00\x00\x00\x00\x00\x00\x00\x00" + f.bytes)[-struct.calcsize(cls._struct_fmt_str):]) @classmethod @@ -176,16 +181,18 @@ def flen_to_cls(cls, bits: int) -> type['BaseFloat']: return Float64 raise ValueError(f"Unsupported flen: {bits}") + def __format__(self, spec: str): + if spec[-2:] == '32': + return Float32.bitcast(self).__format__(spec[:-2]) + if spec[-2:] == '64': + return Float64.bitcast(self).__format__(spec[:-2]) + return format(self.value, spec) + class Float32(BaseFloat): _type = c_float _struct_fmt_str = 'f' - @classmethod - def bitcast(cls, f: "BaseFloat") -> "BaseFloat": - if isinstance(f, Float32): - return f - class Float64(BaseFloat): _type = c_double diff --git a/riscemu/core/registers.py b/riscemu/core/registers.py index 46b1f89..7b34efd 100644 --- a/riscemu/core/registers.py +++ b/riscemu/core/registers.py @@ -228,7 +228,7 @@ def get_f(self, reg: str, mark_read: bool = True) -> BaseFloat: def set_f(self, reg: str, val: Union[float, BaseFloat]): if not self.infinite_regs and reg not in self.float_regs: raise RuntimeError("Invalid float register: {}".format(reg)) - self.float_vals[reg] = self._float_type(val) + self.float_vals[reg] = self._float_type.bitcast(val) @staticmethod def named_registers(): diff --git a/riscemu/debug.py b/riscemu/debug.py index 4f43419..5bab0a0 100644 --- a/riscemu/debug.py +++ b/riscemu/debug.py @@ -64,7 +64,7 @@ def cont(verbose=False): def step(): try: - cpu.step() + cpu.step(verbose=True) except LaunchDebuggerException: return diff --git a/riscemu/instructions/RV32D.py b/riscemu/instructions/RV32D.py new file mode 100644 index 0000000..ced3558 --- /dev/null +++ b/riscemu/instructions/RV32D.py @@ -0,0 +1,20 @@ +""" +RiscEmu (c) 2023 Anton Lydike + +SPDX-License-Identifier: MIT + +This file contains copious amounts of docstrings that were all taken +from https://msyksphinz-self.github.io/riscv-isadoc/html/rvfd.html +(all the docstrings on the instruction methods documenting the opcodes +and their function) +""" +from typing import Tuple + +from .instruction_set import InstructionSet, Instruction +from .float_base import FloatArithBase +from riscemu.core import INS_NOT_IMPLEMENTED, Float32, Int32, UInt32, Float64 + + +class RV32D(FloatArithBase[Float64]): + flen = 64 + _float_cls = Float64 diff --git a/riscemu/instructions/RV32F.py b/riscemu/instructions/RV32F.py index 3753aac..a3d4065 100644 --- a/riscemu/instructions/RV32F.py +++ b/riscemu/instructions/RV32F.py @@ -8,331 +8,14 @@ (all the docstrings on the instruction methods documenting the opcodes and their function) """ -from typing import Tuple +from .instruction_set import Instruction +from .float_base import FloatArithBase +from riscemu.core import Float32, Int32, UInt32 -from .instruction_set import InstructionSet, Instruction -from riscemu.core import INS_NOT_IMPLEMENTED, Float32, Int32, UInt32 - -class RV32F(InstructionSet): - def instruction_fmadd_s(self, ins: Instruction): - """ - +-----+-----+-----+-----+-----+-----+-----+---+ - |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| - +-----+-----+-----+-----+-----+-----+-----+---+ - |rs3 |00 |rs2 |rs1 |rm |rd |10000|11 | - +-----+-----+-----+-----+-----+-----+-----+---+ - - - :Format: - | fmadd.s rd,rs1,rs2,rs3 - - :Description: - | Perform single-precision fused multiply addition. - - :Implementation: - | f[rd] = f[rs1]×f[rs2]+f[rs3] - - """ - rd, rs1, rs2, rs3 = self.parse_rd_rs_rs_rs(ins) - self.regs.set_f(rd, rs1 * rs2 + rs3) - - def instruction_fmsub_s(self, ins: Instruction): - """ - +-----+-----+-----+-----+-----+-----+-----+---+ - |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| - +-----+-----+-----+-----+-----+-----+-----+---+ - |rs3 |00 |rs2 |rs1 |rm |rd |10001|11 | - +-----+-----+-----+-----+-----+-----+-----+---+ - - - :Format: - | fmsub.s rd,rs1,rs2,rs3 - - :Description: - | Perform single-precision fused multiply addition. - - :Implementation: - | f[rd] = f[rs1]×f[rs2]-f[rs3] - """ - rd, rs1, rs2, rs3 = self.parse_rd_rs_rs_rs(ins) - self.regs.set_f(rd, rs1 * rs2 - rs3) - - def instruction_fnmsub_s(self, ins: Instruction): - """ - +-----+-----+-----+-----+-----+-----+-----+---+ - |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| - +-----+-----+-----+-----+-----+-----+-----+---+ - |rs3 |00 |rs2 |rs1 |rm |rd |10010|11 | - +-----+-----+-----+-----+-----+-----+-----+---+ - - - :Format: - | fnmsub.s rd,rs1,rs2,rs3 - - :Description: - | Perform single-precision fused multiply addition. - - :Implementation: - | f[rd] = -f[rs1]×f[rs2]+f[rs3] - """ - rd, rs1, rs2, rs3 = self.parse_rd_rs_rs_rs(ins) - self.regs.set_f(rd, -rs1 * rs2 + rs3) - - def instruction_fnmadd_s(self, ins: Instruction): - """ - +-----+-----+-----+-----+-----+-----+-----+---+ - |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| - +-----+-----+-----+-----+-----+-----+-----+---+ - |rs3 |00 |rs2 |rs1 |rm |rd |10011|11 | - +-----+-----+-----+-----+-----+-----+-----+---+ - - - :Format: - | fnmadd.s rd,rs1,rs2,rs3 - - :Description: - | Perform single-precision fused multiply addition. - - :Implementation: - | f[rd] = -f[rs1]×f[rs2]-f[rs3] - """ - rd, rs1, rs2, rs3 = self.parse_rd_rs_rs_rs(ins) - self.regs.set_f(rd, -rs1 * rs2 - rs3) - - def instruction_fadd_s(self, ins: Instruction): - """ - +-----+-----+-----+-----+-----+-----+-----+---+ - |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| - +-----+-----+-----+-----+-----+-----+-----+---+ - |00000|00 |rs2 |rs1 |rm |rd |10100|11 | - +-----+-----+-----+-----+-----+-----+-----+---+ - - - :Format: - | fadd.s rd,rs1,rs2 - - :Description: - | Perform single-precision floating-point addition. - - :Implementation: - | f[rd] = f[rs1] + f[rs2] - """ - rd, rs1, rs2 = self.parse_rd_rs_rs(ins) - self.regs.set_f( - rd, - rs1 + rs2, - ) - - def instruction_fsub_s(self, ins: Instruction): - """ - +-----+-----+-----+-----+-----+-----+-----+---+ - |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| - +-----+-----+-----+-----+-----+-----+-----+---+ - |00001|00 |rs2 |rs1 |rm |rd |10100|11 | - +-----+-----+-----+-----+-----+-----+-----+---+ - - - :Format: - | fsub.s rd,rs1,rs2 - - :Description: - | Perform single-precision floating-point subtraction. - - :Implementation: - | f[rd] = f[rs1] - f[rs2] - """ - rd, rs1, rs2 = self.parse_rd_rs_rs(ins) - self.regs.set_f( - rd, - rs1 - rs2, - ) - - def instruction_fmul_s(self, ins: Instruction): - """ - +-----+-----+-----+-----+-----+-----+-----+---+ - |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| - +-----+-----+-----+-----+-----+-----+-----+---+ - |00010|00 |rs2 |rs1 |rm |rd |10100|11 | - +-----+-----+-----+-----+-----+-----+-----+---+ - - - :Format: - | fmul.s rd,rs1,rs2 - - :Description: - | Perform single-precision floating-point multiplication. - - :Implementation: - | f[rd] = f[rs1] × f[rs2] - """ - rd, rs1, rs2 = self.parse_rd_rs_rs(ins) - self.regs.set_f( - rd, - rs1 * rs2, - ) - - def instruction_fdiv_s(self, ins: Instruction): - """ - +-----+-----+-----+-----+-----+-----+-----+---+ - |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| - +-----+-----+-----+-----+-----+-----+-----+---+ - |00011|00 |rs2 |rs1 |rm |rd |10100|11 | - +-----+-----+-----+-----+-----+-----+-----+---+ - - - :Format: - | fdiv.s rd,rs1,rs2 - - :Description: - | Perform single-precision floating-point division. - - :Implementation: - | f[rd] = f[rs1] / f[rs2] - """ - rd, rs1, rs2 = self.parse_rd_rs_rs(ins) - self.regs.set_f( - rd, - rs1 / rs2, - ) - - def instruction_fsqrt_s(self, ins: Instruction): - """ - +-----+-----+-----+-----+-----+-----+-----+---+ - |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| - +-----+-----+-----+-----+-----+-----+-----+---+ - |01011|00 |00000|rs1 |rm |rd |10100|11 | - +-----+-----+-----+-----+-----+-----+-----+---+ - - - :Format: - | fsqrt.s rd,rs1 - - :Description: - | Perform single-precision square root. - - :Implementation: - | f[rd] = sqrt(f[rs1]) - """ - rd, rs = self.parse_rd_rs(ins) - self.regs.set_f(rd, self.regs.get_f(rs) ** 0.5) - - def instruction_fsgnj_s(self, ins: Instruction): - """ - +-----+-----+-----+-----+-----+-----+-----+---+ - |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| - +-----+-----+-----+-----+-----+-----+-----+---+ - |00100|00 |rs2 |rs1 |000 |rd |10100|11 | - +-----+-----+-----+-----+-----+-----+-----+---+ - - :Format: - | fsgnj.s rd,rs1,rs2 - - :Description: - | Produce a result that takes all bits except the sign bit from rs1. - | The result’s sign bit is rs2’s sign bit. - - :Implementation: - | f[rd] = {f[rs2][31], f[rs1][30:0]} - """ - INS_NOT_IMPLEMENTED(ins) - - def instruction_fsgnjn_s(self, ins: Instruction): - """ - +-----+-----+-----+-----+-----+-----+-----+---+ - |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| - +-----+-----+-----+-----+-----+-----+-----+---+ - |00100|00 |rs2 |rs1 |001 |rd |10100|11 | - +-----+-----+-----+-----+-----+-----+-----+---+ - - - :Format: - | fsgnjn.s rd,rs1,rs2 - - :Description: - | Produce a result that takes all bits except the sign bit from rs1. - | The result’s sign bit is opposite of rs2’s sign bit. - - :Implementation: - | f[rd] = {~f[rs2][31], f[rs1][30:0]} - """ - INS_NOT_IMPLEMENTED(ins) - - def instruction_fsgnjx_s(self, ins: Instruction): - """ - +-----+-----+-----+-----+-----+-----+-----+---+ - |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| - +-----+-----+-----+-----+-----+-----+-----+---+ - |00100|00 |rs2 |rs1 |010 |rd |10100|11 | - +-----+-----+-----+-----+-----+-----+-----+---+ - - :Format: - | fsgnjx.s rd,rs1,rs2 - - :Description: - | Produce a result that takes all bits except the sign bit from rs1. - | The result’s sign bit is XOR of sign bit of rs1 and rs2. - - :Implementation: - | f[rd] = {f[rs1][31] ^ f[rs2][31], f[rs1][30:0]} - """ - INS_NOT_IMPLEMENTED(ins) - - def instruction_fmin_s(self, ins: Instruction): - """ - +-----+-----+-----+-----+-----+-----+-----+---+ - |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| - +-----+-----+-----+-----+-----+-----+-----+---+ - |00101|00 |rs2 |rs1 |000 |rd |10100|11 | - +-----+-----+-----+-----+-----+-----+-----+---+ - - :Format: - | fmin.s rd,rs1,rs2 - - :Description: - | Write the smaller of single precision data in rs1 and rs2 to rd. - - :Implementation: - | f[rd] = min(f[rs1], f[rs2]) - """ - rd, rs1, rs2 = self.parse_rd_rs_rs(ins) - self.regs.set_f( - rd, - Float32( - min( - rs1.value, - rs2.value, - ) - ), - ) - - def instruction_fmax_s(self, ins: Instruction): - """ - +-----+-----+-----+-----+-----+-----+-----+---+ - |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| - +-----+-----+-----+-----+-----+-----+-----+---+ - |00101|00 |rs2 |rs1 |001 |rd |10100|11 | - +-----+-----+-----+-----+-----+-----+-----+---+ - - :Format: - | fmax.s rd,rs1,rs2 - - :Description: - | Write the larger of single precision data in rs1 and rs2 to rd. - - :Implementation: - | f[rd] = max(f[rs1], f[rs2]) - """ - rd, rs1, rs2 = self.parse_rd_rs_rs(ins) - self.regs.set_f( - rd, - Float32( - max( - rs1.value, - rs2.value, - ) - ), - ) +class RV32F(FloatArithBase[Float32]): + flen = 32 + _float_cls = Float32 def instruction_fcvt_w_s(self, ins: Instruction): """ @@ -372,7 +55,7 @@ def instruction_fcvt_wu_s(self, ins: Instruction): | x[rd] = sext(u32_{f32}(f[rs1])) """ rd, rs = self.parse_rd_rs(ins) - self.regs.set(rd, UInt32(self.regs.get_f(rs).value)) + self.regs.set(rd, UInt32(int(self.regs.get_f(rs).value))) def instruction_fmv_x_w(self, ins: Instruction): """ @@ -392,95 +75,7 @@ def instruction_fmv_x_w(self, ins: Instruction): | x[rd] = sext(f[rs1][31:0]) """ rd, rs = self.parse_rd_rs(ins) - self.regs.set(rd, UInt32(self.regs.get_f(rs).bytes)) - - def instruction_feq_s(self, ins: Instruction): - """ - +-----+-----+-----+-----+-----+-----+-----+---+ - |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| - +-----+-----+-----+-----+-----+-----+-----+---+ - |10100|00 |rs2 |rs1 |010 |rd |10100|11 | - +-----+-----+-----+-----+-----+-----+-----+---+ - - :Format: - | feq.s rd,rs1,rs2 - - :Description: - | Performs a quiet equal comparison between floating-point registers rs1 and rs2 and record the Boolean result in integer register rd. - | Only signaling NaN inputs cause an Invalid Operation exception. - | The result is 0 if either operand is NaN. - - :Implementation: - | x[rd] = f[rs1] == f[rs2] - """ - rd, rs1, rs2 = self.parse_rd_rs_rs(ins) - self.regs.set(rd, Int32(rs1 == rs2)) - - def instruction_flt_s(self, ins: Instruction): - """ - +-----+-----+-----+-----+-----+-----+-----+---+ - |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| - +-----+-----+-----+-----+-----+-----+-----+---+ - |10100|00 |rs2 |rs1 |001 |rd |10100|11 | - +-----+-----+-----+-----+-----+-----+-----+---+ - - :Format: - | flt.s rd,rs1,rs2 - - :Description: - | Performs a quiet less comparison between floating-point registers rs1 and rs2 and record the Boolean result in integer register rd. - | Only signaling NaN inputs cause an Invalid Operation exception. - | The result is 0 if either operand is NaN. - - :Implementation: - | x[rd] = f[rs1] < f[rs2] - """ - rd, rs1, rs2 = self.parse_rd_rs_rs(ins) - self.regs.set(rd, Int32(rs1 < rs2)) - - def instruction_fle_s(self, ins: Instruction): - """ - +-----+-----+-----+-----+-----+-----+-----+---+ - |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| - +-----+-----+-----+-----+-----+-----+-----+---+ - |10100|00 |rs2 |rs1 |000 |rd |10100|11 | - +-----+-----+-----+-----+-----+-----+-----+---+ - - :Format: - | fle.s rd,rs1,rs2 - - :Description: - | Performs a quiet less or equal comparison between floating-point registers rs1 and rs2 and record the Boolean result in integer register rd. - | Only signaling NaN inputs cause an Invalid Operation exception. - | The result is 0 if either operand is NaN. - - :Implementation: - | x[rd] = f[rs1] <= f[rs2] - """ - rd, rs1, rs2 = self.parse_rd_rs_rs(ins) - self.regs.set(rd, Int32(rs1 <= rs2)) - - def instruction_fclass_s(self, ins: Instruction): - """ - +-----+-----+-----+-----+-----+-----+-----+---+ - |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| - +-----+-----+-----+-----+-----+-----+-----+---+ - |11100|00 |00000|rs1 |001 |rd |10100|11 | - +-----+-----+-----+-----+-----+-----+-----+---+ - - :Format: - | fclass.s rd,rs1 - - :Description: - | Examines the value in floating-point register rs1 and writes to integer register rd a 10-bit mask that indicates the class of the floating-point number. - | The format of the mask is described in [classify table]_. - | The corresponding bit in rd will be set if the property is true and clear otherwise. - | All other bits in rd are cleared. Note that exactly one bit in rd will be set. - - :Implementation: - | x[rd] = classifys(f[rs1]) - """ - INS_NOT_IMPLEMENTED(ins) + self.regs.set(rd, UInt32(self.regs.get_f(rs).bytes[-4:])) def instruction_fcvt_s_w(self, ins: Instruction): """ @@ -542,68 +137,4 @@ def instruction_fmv_w_x(self, ins: Instruction): | f[rd] = x[rs1][31:0] """ rd, rs = self.parse_rd_rs(ins) - self.regs.set_f(rd, Float32.from_bytes(self.regs.get(rs).unsigned_value)) - - def instruction_flw(self, ins: Instruction): - """ - +-----+-----+-----+-----+-----+-----+-----+---+ - |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| - +-----+-----+-----+-----+-----+-----+-----+---+ - |imm[11:0] |rs1 |010 |rd |00001|11 | - +-----+-----+-----+-----+-----+-----+-----+---+ - - :Format: - | flw rd,offset(rs1) - - :Description: - | Load a single-precision floating-point value from memory into floating-point register rd. - - :Implementation: - | f[rd] = M[x[rs1] + sext(offset)][31:0] - """ - rd, addr = self.parse_mem_ins(ins) - self.regs.set_f(rd, self.mmu.read_float(addr.value)) - - def instruction_fsw(self, ins: Instruction): - """ - +-----+-----+-----+-----+-----+--------+-----+---+ - |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| - +-----+-----+-----+-----+-----+--------+-----+---+ - |imm[11:5] |rs2 |rs1 |010 |imm[4:0]|01001|11 | - +-----+-----+-----+-----+-----+--------+-----+---+ - - :Format: - | fsw rs2,offset(rs1) - - :Description: - | Store a single-precision value from floating-point register rs2 to memory. - - :Implementation: - | M[x[rs1] + sext(offset)] = f[rs2][31:0] - """ - rs, addr = self.parse_mem_ins(ins) - val = self.regs.get_f(rs) - self.mmu.write(addr.value, 4, bytearray(val.bytes)) - - def parse_rd_rs(self, ins: Instruction) -> Tuple[str, str]: - assert len(ins.args) == 2 - return ins.get_reg(0), ins.get_reg(1) - - def parse_rd_rs_rs(self, ins: Instruction) -> Tuple[str, Float32, Float32]: - assert len(ins.args) == 3 - return ( - ins.get_reg(0), - self.regs.get_f(ins.get_reg(1)), - self.regs.get_f(ins.get_reg(2)), - ) - - def parse_rd_rs_rs_rs( - self, ins: Instruction - ) -> Tuple[str, Float32, Float32, Float32]: - assert len(ins.args) == 4 - return ( - ins.get_reg(0), - self.regs.get_f(ins.get_reg(1)), - self.regs.get_f(ins.get_reg(2)), - self.regs.get_f(ins.get_reg(3)), - ) + self.regs.set_f(rd, Float32.from_bytes(self.regs.get(rs).to_bytes())) diff --git a/riscemu/instructions/RV_Debug.py b/riscemu/instructions/RV_Debug.py index a898dcb..e78bebd 100644 --- a/riscemu/instructions/RV_Debug.py +++ b/riscemu/instructions/RV_Debug.py @@ -1,6 +1,7 @@ from typing import Union from .instruction_set import InstructionSet, Instruction +from ..core import BaseFloat, Int32, Float32 class RV_Debug(InstructionSet): @@ -21,6 +22,10 @@ def instruction_print_float(self, ins: Instruction): reg = ins.get_reg(0) print("register {} contains value {}".format(reg, self.regs.get_f(reg).value)) + def instruction_print_float_s(self, ins: Instruction): + reg = ins.get_reg(0) + print("register {} contains value {}".format(reg, Float32.bitcast(self.regs.get_f(reg)).value)) + def instruction_print_uint(self, ins: Instruction): reg = ins.get_reg(0) print( @@ -43,7 +48,7 @@ def instruction_print_uhex(self, ins: Instruction): ) ) - def smart_get_reg(self, reg_name: str) -> Union[int, float]: + def smart_get_reg(self, reg_name: str) -> Union[Int32, BaseFloat]: if reg_name[0] == "f": - return self.regs.get_f(reg_name).value - return self.regs.get(reg_name).value + return self.regs.get_f(reg_name) + return self.regs.get(reg_name) diff --git a/riscemu/instructions/__init__.py b/riscemu/instructions/__init__.py index 51925f0..5714ca3 100644 --- a/riscemu/instructions/__init__.py +++ b/riscemu/instructions/__init__.py @@ -11,21 +11,23 @@ from .RV32I import RV32I from .RV32A import RV32A from .RV32F import RV32F +from .RV32D import RV32D from .RV_Debug import RV_Debug from .Zicsr import Zicsr InstructionSetDict = { - v.__name__: v for v in [RV32I, RV32M, RV32A, RV32F, Zicsr, RV_Debug] + v.__name__: v for v in [RV32I, RV32M, RV32A, RV32F, RV32D, Zicsr, RV_Debug] } __all__ = [ - Instruction, - InstructionSet, - InstructionSetDict, - RV32I, - RV32M, - RV32A, - RV32F, - Zicsr, - RV_Debug, + "Instruction", + "InstructionSet", + "InstructionSetDict", + "RV32I", + "RV32M", + "RV32A", + "RV32F", + "RV32D", + "Zicsr", + "RV_Debug", ] diff --git a/riscemu/instructions/float_base.py b/riscemu/instructions/float_base.py new file mode 100644 index 0000000..71cdbe4 --- /dev/null +++ b/riscemu/instructions/float_base.py @@ -0,0 +1,388 @@ +from typing import ClassVar, Generic, TypeVar, Tuple, Iterable, Callable + +from .instruction_set import InstructionSet, Instruction +from riscemu.core import BaseFloat, CPU, INS_NOT_IMPLEMENTED, UInt32 + +_FloatT = TypeVar('_FloatT', bound=BaseFloat) + + +class FloatArithBase(Generic[_FloatT], InstructionSet): + flen: ClassVar[int] + _float_cls: ClassVar[type[BaseFloat]] + + def __init__(self, cpu: CPU): + assert cpu.regs.flen >= self.flen, "{} implies cpu flen of at least {}".format(self.__class__.__name__, + self.flen) + super().__init__(cpu) + + def base_fmadd(self, ins: Instruction): + """ + :Format: + | fmadd.{s,d,q} rd,rs1,rs2,rs3 + + :Description: + | Perform fused multiply addition. + + :Implementation: + | f[rd] = f[rs1]×f[rs2]+f[rs3] + + """ + rd, rs1, rs2, rs3 = self.parse_rd_rs_rs_rs(ins) + self.regs.set_f(rd, rs1 * rs2 + rs3) + + def base_fmsub(self, ins: Instruction): + """ + :Format: + | fmsub.{s,d,q} rd,rs1,rs2,rs3 + + :Description: + | Perform fused multiply subtraction. + + :Implementation: + | f[rd] = f[rs1]×f[rs2]-f[rs3] + """ + rd, rs1, rs2, rs3 = self.parse_rd_rs_rs_rs(ins) + self.regs.set_f(rd, rs1 * rs2 - rs3) + + def base_fnmsub(self, ins: Instruction): + """ + :Format: + | fnmsub.{s,d,q} rd,rs1,rs2,rs3 + + :Description: + | Perform fused negated multiply addition. + + :Implementation: + | f[rd] = -f[rs1]×f[rs2]+f[rs3] + """ + rd, rs1, rs2, rs3 = self.parse_rd_rs_rs_rs(ins) + self.regs.set_f(rd, -rs1 * rs2 + rs3) + + def base_fnmadd(self, ins: Instruction): + """ + :Format: + | fnmadd.{s,d,q} rd,rs1,rs2,rs3 + + :Description: + | Perform single-precision fused negated multiply addition. + + :Implementation: + | f[rd] = -f[rs1]×f[rs2]-f[rs3] + """ + rd, rs1, rs2, rs3 = self.parse_rd_rs_rs_rs(ins) + self.regs.set_f(rd, -rs1 * rs2 - rs3) + + def base_fadd(self, ins: Instruction): + """ + +-----+-----+-----+-----+-----+-----+-----+---+ + |31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0| + +-----+-----+-----+-----+-----+-----+-----+---+ + |00000|00 |rs2 |rs1 |rm |rd |10100|11 | + +-----+-----+-----+-----+-----+-----+-----+---+ + + + :Format: + | fadd.s rd,rs1,rs2 + + :Description: + | Perform single-precision floating-point addition. + + :Implementation: + | f[rd] = f[rs1] + f[rs2] + """ + rd, rs1, rs2 = self.parse_rd_rs_rs(ins) + self.regs.set_f( + rd, + rs1 + rs2, + ) + + def base_fsub(self, ins: Instruction): + """ + :Format: + | fsub.{s,d,q} rd,rs1,rs2 + + :Description: + | Perform single-precision floating-point subtraction. + + :Implementation: + | f[rd] = f[rs1] - f[rs2] + """ + rd, rs1, rs2 = self.parse_rd_rs_rs(ins) + self.regs.set_f( + rd, + rs1 - rs2, + ) + + def base_fmul(self, ins: Instruction): + """ + :Format: + | fmul.{s,d,q} rd,rs1,rs2 + + :Description: + | Perform floating-point multiplication. + + :Implementation: + | f[rd] = f[rs1] × f[rs2] + """ + rd, rs1, rs2 = self.parse_rd_rs_rs(ins) + self.regs.set_f( + rd, + rs1 * rs2, + ) + + def base_fdiv(self, ins: Instruction): + """ + :Format: + | fdiv.{s,d,q} rd,rs1,rs2 + + :Description: + | Perform floating-point division. + + :Implementation: + | f[rd] = f[rs1] / f[rs2] + """ + rd, rs1, rs2 = self.parse_rd_rs_rs(ins) + self.regs.set_f( + rd, + rs1 / rs2, + ) + + def base_fsqrt(self, ins: Instruction): + """ + :Format: + | fsqrt.{s,d,q} rd,rs1 + + :Description: + | Perform single-precision square root. + + :Implementation: + | f[rd] = sqrt(f[rs1]) + """ + rd, rs = self.parse_rd_rs(ins) + self.regs.set_f(rd, self._float_cls.bitcast(self.regs.get_f(rs)) ** 0.5) + + def base_fsgnj(self, ins: Instruction): + """ + :Format: + | fsgnj.{s,d,q} rd,rs1,rs2 + + :Description: + | Produce a result that takes all bits except the sign bit from rs1. + | The result’s sign bit is rs2’s sign bit. + + :Implementation: + | f[rd] = {f[rs2][31], f[rs1][30:0]} + """ + INS_NOT_IMPLEMENTED(ins) + + def base_fsgnjn(self, ins: Instruction): + """ + :Format: + | fsgnjn.{s,d,q} rd,rs1,rs2 + + :Description: + | Produce a result that takes all bits except the sign bit from rs1. + | The result’s sign bit is opposite of rs2’s sign bit. + + :Implementation: + | f[rd] = {~f[rs2][31], f[rs1][30:0]} + """ + INS_NOT_IMPLEMENTED(ins) + + def base_fsgnjx(self, ins: Instruction): + """ + :Format: + | fsgnjx.{s,d,q} rd,rs1,rs2 + + :Description: + | Produce a result that takes all bits except the sign bit from rs1. + | The result’s sign bit is XOR of sign bit of rs1 and rs2. + + :Implementation: + | f[rd] = {f[rs1][31] ^ f[rs2][31], f[rs1][30:0]} + """ + INS_NOT_IMPLEMENTED(ins) + + def base_fmin(self, ins: Instruction): + """ + :Format: + | fmin.{s,d,q} rd,rs1,rs2 + + :Description: + | Write the smaller of rs1 and rs2 to rd. + + :Implementation: + | f[rd] = min(f[rs1], f[rs2]) + """ + rd, rs1, rs2 = self.parse_rd_rs_rs(ins) + self.regs.set_f( + rd, + min( + rs1, + rs2, + ), + ) + + def base_fmax(self, ins: Instruction): + """ + :Format: + | fmax.{s,d,q} rd,rs1,rs2 + + :Description: + | Write the larger of rs1 and rs2 to rd. + + :Implementation: + | f[rd] = max(f[rs1], f[rs2]) + """ + rd, rs1, rs2 = self.parse_rd_rs_rs(ins) + self.regs.set_f( + rd, + max( + rs1, + rs2, + ), + ) + + def base_feq(self, ins: Instruction): + """ + :Format: + | feq.{s,d,q} rd,rs1,rs2 + + :Description: + | Performs a quiet equal comparison between floating-point registers rs1 and rs2 and record the Boolean result in integer register rd. + | Only signaling NaN inputs cause an Invalid Operation exception. + | The result is 0 if either operand is NaN. + + :Implementation: + | x[rd] = f[rs1] == f[rs2] + """ + rd, rs1, rs2 = self.parse_rd_rs_rs(ins) + self.regs.set(rd, UInt32(rs1 == rs2)) + + def base_flt(self, ins: Instruction): + """ + :Format: + | flt.{s,d,q} rd,rs1,rs2 + + :Description: + | Performs a quiet less comparison between floating-point registers rs1 and rs2 and record the Boolean result in integer register rd. + | Only signaling NaN inputs cause an Invalid Operation exception. + | The result is 0 if either operand is NaN. + + :Implementation: + | x[rd] = f[rs1] < f[rs2] + """ + rd, rs1, rs2 = self.parse_rd_rs_rs(ins) + self.regs.set(rd, UInt32(rs1 < rs2)) + + def base_fle(self, ins: Instruction): + """ + :Format: + | fle.{s,d,q} rd,rs1,rs2 + + :Description: + | Performs a quiet less or equal comparison between floating-point registers rs1 and rs2 and record the Boolean result in integer register rd. + | Only signaling NaN inputs cause an Invalid Operation exception. + | The result is 0 if either operand is NaN. + + :Implementation: + | x[rd] = f[rs1] <= f[rs2] + """ + rd, rs1, rs2 = self.parse_rd_rs_rs(ins) + self.regs.set(rd, UInt32(rs1 <= rs2)) + + def base_fclass(self, ins: Instruction): + """ + :Format: + | fclass.{s,d,q} rd,rs1 + + :Description: + | Examines the value in floating-point register rs1 and writes to integer register rd a 10-bit mask that indicates the class of the floating-point number. + | The format of the mask is described in [classify table]_. + | The corresponding bit in rd will be set if the property is true and clear otherwise. + | All other bits in rd are cleared. Note that exactly one bit in rd will be set. + + :Implementation: + | x[rd] = classifys(f[rs1]) + """ + INS_NOT_IMPLEMENTED(ins) + + def base_load(self, ins: Instruction): + """ + :Format: + | fl{w,d,q} rd,offset(rs1) + + :Description: + | Load a floating-point value from memory into floating-point register rd. + + :Implementation: + | f[rd] = M[x[rs1] + sext(offset)][31:0] + """ + rd, addr = self.parse_mem_ins(ins) + bytes = self.mmu.read(addr.value, self.flen // 8) + self.regs.set_f(rd, self._float_cls(bytes)) + + def base_save(self, ins: Instruction): + """:Format: + | fs{w,d,q} rs2,offset(rs1) + + :Description: + | Store a value from floating-point register rs2 to memory. + + :Implementation: + | M[x[rs1] + sext(offset)] = f[rs2][31:0] + """ + rs, addr = self.parse_mem_ins(ins) + val = self._float_cls.bitcast(self.regs.get_f(rs)) + self.mmu.write(addr.value, self.flen // 8, bytearray(val.bytes)) + + def get_instructions(self) -> Iterable[Tuple[str, Callable[[Instruction], None]]]: + yield from super().get_instructions() + + qual = {32: 's', 64: 'd', 128: 'q'}.get(self.flen) + load_save_qual = {32: 'w', 64: 'd', 128: 'q'}.get(self.flen) + + yield from ( + ('fmadd.' + qual, self.base_fmadd), + ('fmsub.' + qual, self.base_fmsub), + ('fnmsub.' + qual, self.base_fnmsub), + ('fnmadd.' + qual, self.base_fnmadd), + ('fadd.' + qual, self.base_fadd), + ('fsub.' + qual, self.base_fnmadd), + ('fmul.' + qual, self.base_fmul), + ('fdiv.' + qual, self.base_fdiv), + ('fsqrt.' + qual, self.base_fsqrt), + ('fsgnj.' + qual, self.base_fsgnj), + ('fsgnjn.' + qual, self.base_fsgnjn), + ('fsgnjx.' + qual, self.base_fsgnjx), + ('fmin.' + qual, self.base_fmin), + ('fmax.' + qual, self.base_fmax), + ('feq.' + qual, self.base_feq), + ('flt.' + qual, self.base_flt), + ('fle.' + qual, self.base_fle), + ('fl' + load_save_qual, self.base_load), + ('fs' + load_save_qual, self.base_save), + ) + + def parse_rd_rs(self, ins: Instruction) -> Tuple[str, str]: + assert len(ins.args) == 2 + return ins.get_reg(0), ins.get_reg(1) + + def parse_rd_rs_rs(self, ins: Instruction) -> Tuple[str, _FloatT, _FloatT]: + assert len(ins.args) == 3 + return ( + ins.get_reg(0), + self._float_cls.bitcast(self.regs.get_f(ins.get_reg(1))), + self._float_cls.bitcast(self.regs.get_f(ins.get_reg(2))), + ) + + def parse_rd_rs_rs_rs( + self, ins: Instruction + ) -> Tuple[str, _FloatT, _FloatT, _FloatT]: + assert len(ins.args) == 4 + return ( + ins.get_reg(0), + self._float_cls.bitcast(self.regs.get_f(ins.get_reg(1))), + self._float_cls.bitcast(self.regs.get_f(ins.get_reg(2))), + self._float_cls.bitcast(self.regs.get_f(ins.get_reg(3))), + ) diff --git a/riscemu/instructions/instruction_set.py b/riscemu/instructions/instruction_set.py index ec28988..1c56ab6 100644 --- a/riscemu/instructions/instruction_set.py +++ b/riscemu/instructions/instruction_set.py @@ -4,12 +4,12 @@ SPDX-License-Identifier: MIT """ -from typing import Tuple, Callable, Dict, Union +from typing import Tuple, Callable, Dict, Union, Iterable from abc import ABC from ..core.exceptions import ASSERT_LEN -from ..core import Instruction, Int32, UInt32, Immediate, CPU +from ..core import Instruction, Int32, UInt32, Immediate, CPU, Registers class InstructionSet(ABC): @@ -39,7 +39,7 @@ def load(self) -> Dict[str, Callable[["Instruction"], None]]: """ return {name: ins for name, ins in self.get_instructions()} - def get_instructions(self): + def get_instructions(self) -> Iterable[Tuple[str, Callable[[Instruction], None]]]: """ Returns a list of all valid instruction names included in this instruction set @@ -128,7 +128,7 @@ def mmu(self): return self.cpu.mmu @property - def regs(self): + def regs(self) -> Registers: return self.cpu.regs def __repr__(self): diff --git a/riscemu/riscemu_main.py b/riscemu/riscemu_main.py index 381e9ca..5e2ccc0 100644 --- a/riscemu/riscemu_main.py +++ b/riscemu/riscemu_main.py @@ -121,9 +121,9 @@ def register_all_arguments(self, parser: argparse.ArgumentParser): parser.add_argument( "--flen", type=int, - help="hardware FLEN, either 32 or 64", + help="hardware FLEN, either 32 or 64. Defaults to 64", nargs="?", - default=32, + default=64, ) parser.add_argument( diff --git a/test/filecheck/rv32f-conv.asm b/test/filecheck/rv32f-conv.asm index 52cdb7e..1e4f75d 100644 --- a/test/filecheck/rv32f-conv.asm +++ b/test/filecheck/rv32f-conv.asm @@ -7,7 +7,7 @@ main: // test fcvt.s.wu li a1, -2 fcvt.s.wu fa0, a1 - print.float fa0 + print.float.s fa0 // CHECK: register fa0 contains value 4294967296.0 li a1, 2 fcvt.s.wu fa0, a1 @@ -17,11 +17,11 @@ main: // test fcvt.s.w li a1, -2 fcvt.s.w fa0, a1 - print.float fa0 + print.float.s fa0 // CHECK: register fa0 contains value -2.0 li a1, 2 fcvt.s.w fa0, a1 - print.float fa0 + print.float.s fa0 // CHECK-NEXT: register fa0 contains value 2.0 // test fmv.s.x @@ -39,11 +39,11 @@ main: // test fmv.w.x li a1, 1073741824 fmv.w.x fa0, a1 - print.float fa0 + print.float.s fa0 // CHECK-NEXT: register fa0 contains value 2.0 li a1, 3221225472 fmv.w.x fa0, a1 - print.float fa0 + print.float.s fa0 // CHECK-NEXT: register fa0 contains value -2.0 ret diff --git a/test/test_RV32F.py b/test/test_RV32F.py index f50f832..49d6a12 100644 --- a/test/test_RV32F.py +++ b/test/test_RV32F.py @@ -1,12 +1,17 @@ -from typing import Iterable from riscemu.instructions.RV32F import RV32F -from riscemu.core.registers import Registers -from riscemu.core import ProgramLoader, CPU -from riscemu.core.float import BaseFloat -from riscemu.core.int32 import Int32 -from riscemu.core.simple_instruction import SimpleInstruction +from riscemu.core import CPU, Float32, Int32, SimpleInstruction, Registers, BaseFloat +def is_close(a0: float | int | BaseFloat, a1: float | int | BaseFloat): + """ + Compares if two numbers are close to 7 digits. + This should be close enough to catch any real erros but ignore + floating point rounding issues. + """ + diff = abs(float(a0 - a1)) + mag = max(abs(float(a0)), abs(float(a1))) + return (mag / 1e7) > diff + class MockInstruction(SimpleInstruction): ... @@ -16,12 +21,8 @@ class MockRegisters(Registers): class MockCPU(CPU): - def __init__(self): - self.regs = MockRegisters(True) - - @classmethod - def get_loaders(cls) -> "Iterable[type[ProgramLoader]]": - assert False + def __init__(self, flen: int=32): + self.regs = MockRegisters(True, flen) def run(self, verbose: bool = False): assert False @@ -30,7 +31,7 @@ def step(self, verbose: bool = False): assert False -def test_cvt_instructions(): +def test_fcvt_instructions(): cpu = MockCPU() ins = MockInstruction("fcvt.s.w", ("fa0", "a0"), None, None) @@ -39,6 +40,73 @@ def test_cvt_instructions(): assert 42.0 == cpu.regs.get_f("fa0") ins = MockInstruction("fcvt.w.s", ("a1", "fa1"), None, None) - cpu.regs.set_f("fa1", BaseFloat(42.0)) + cpu.regs.set_f("fa1", Float32(42.0)) RV32F(cpu).instruction_fcvt_w_s(ins) assert Int32(42) == cpu.regs.get("a1") + + +def test_single_precision_on_flen64(): + cpu = MockCPU(flen=64) + + cpu.regs.set_f('ft0', Float32(100)) + cpu.regs.set_f('ft1', Float32(3)) + # instruction doing ft2 <- ft1 ft2 + + ins = MockInstruction("", ("ft2", "ft0", "ft1"), None, None) + + # div + RV32F(cpu).base_fdiv(ins) + assert is_close(Float32.bitcast(cpu.regs.get_f('ft2')), (100.0/3)) + + # multiplication + RV32F(cpu).base_fmul(ins) + assert is_close(Float32.bitcast(cpu.regs.get_f('ft2')), (100.0 * 3)) + + # fadd + RV32F(cpu).base_fadd(ins) + assert is_close(Float32.bitcast(cpu.regs.get_f('ft2')), (100.0 + 3)) + + # fsub + RV32F(cpu).base_fsub(ins) + assert is_close(Float32.bitcast(cpu.regs.get_f('ft2')), (100.0 - 3)) + + # fmin + RV32F(cpu).base_fmin(ins) + assert is_close(Float32.bitcast(cpu.regs.get_f('ft2')), min(100.0, 3)) + + # fmax + RV32F(cpu).base_fmax(ins) + assert is_close(Float32.bitcast(cpu.regs.get_f('ft2')), max(100.0, 3)) + +def test_single_precision_on_flen32(): + cpu = MockCPU(flen=32) + + cpu.regs.set_f('ft0', Float32(100)) + cpu.regs.set_f('ft1', Float32(3)) + # instruction doing ft2 <- ft1 ft2 + + ins = MockInstruction("", ("ft2", "ft0", "ft1"), None, None) + + # div + RV32F(cpu).base_fdiv(ins) + assert is_close(Float32.bitcast(cpu.regs.get_f('ft2')), (100.0/3)) + + # multiplication + RV32F(cpu).base_fmul(ins) + assert is_close(Float32.bitcast(cpu.regs.get_f('ft2')), (100.0 * 3)) + + # fadd + RV32F(cpu).base_fadd(ins) + assert is_close(Float32.bitcast(cpu.regs.get_f('ft2')), (100.0 + 3)) + + # fsub + RV32F(cpu).base_fsub(ins) + assert is_close(Float32.bitcast(cpu.regs.get_f('ft2')), (100.0 - 3)) + + # fmin + RV32F(cpu).base_fmin(ins) + assert is_close(Float32.bitcast(cpu.regs.get_f('ft2')), min(100.0, 3)) + + # fmax + RV32F(cpu).base_fmax(ins) + assert is_close(Float32.bitcast(cpu.regs.get_f('ft2')), max(100.0, 3)) diff --git a/test/test_float32.py b/test/test_float32.py deleted file mode 100644 index 35d40fe..0000000 --- a/test/test_float32.py +++ /dev/null @@ -1,25 +0,0 @@ -import math - -from riscemu.core import BaseFloat - -# pi encoded as a 32bit little endian float -PI_BYTES_LE = b"\xdb\x0fI@" - - -def test_float_serialization(): - assert BaseFloat(PI_BYTES_LE) == BaseFloat(math.pi) - assert BaseFloat(math.pi).bytes == PI_BYTES_LE - - -def test_random_float_ops(): - val = BaseFloat(5) - assert val**2 == 25 - assert val // 2 == 2 - assert val * 3 == 15 - assert val - 2 == 3 - assert val * val == 25 - assert BaseFloat(9) ** 0.5 == 3 - - -def test_float_from_raw_int_conversion(): - assert BaseFloat.from_bytes(1084227584) == BaseFloat(5.0) diff --git a/test/test_float_impl.py b/test/test_float_impl.py new file mode 100644 index 0000000..be2b387 --- /dev/null +++ b/test/test_float_impl.py @@ -0,0 +1,46 @@ +import math + +from riscemu.core import Float32, Float64 + +# pi encoded as a 32bit little endian float +PI_BYTES_LE = b"\xdb\x0fI@" + + +def test_float_serialization(): + assert Float32(PI_BYTES_LE) == Float32(math.pi) + assert Float32(math.pi).bytes == PI_BYTES_LE + + +def test_float_bitcast(): + f32_pi = Float32(math.pi) + f64_pi32 = Float64.bitcast(f32_pi) + assert f32_pi.bytes == Float32.bitcast(f64_pi32).bytes + + f64_pi = Float64(math.pi) + f32_pi64 = Float32.bitcast(f64_pi) + assert f64_pi.bytes[-4:] == f32_pi64.bytes + assert Float64.bitcast(f32_pi64).bytes[:4] == b"\x00\x00\x00\x00" + + +def test_random_float_ops32(): + val = Float32(5) + assert val**2 == 25 + assert val // 2 == 2 + assert val * 3 == 15 + assert val - 2 == 3 + assert val * val == 25 + assert Float32(9) ** 0.5 == 3 + + +def test_random_float_ops64(): + val = Float64(5) + assert val**2 == 25 + assert val // 2 == 2 + assert val * 3 == 15 + assert val - 2 == 3 + assert val * val == 25 + assert Float64(9) ** 0.5 == 3 + + +def test_float_from_raw_bytes_conversion(): + assert Float32.from_bytes(b'\x00\x00\xa0@') == Float32(5.0) diff --git a/test/test_regs.py b/test/test_regs.py index 4c00b29..e2a414a 100644 --- a/test/test_regs.py +++ b/test/test_regs.py @@ -1,7 +1,7 @@ import pytest from riscemu.core.registers import Registers -from riscemu.core import BaseFloat +from riscemu.core import Float32 def test_float_regs(): @@ -9,10 +9,19 @@ def test_float_regs(): # uninitialized register is zero assert r.get_f("fs0") == 0 # get/set - val = BaseFloat(3.14) + val = Float32(3.14) r.set_f("fs0", val) assert r.get_f("fs0") == val +def test_float_regs_flen64(): + r = Registers(flen=64) + # uninitialized register is zero + assert r.get_f("fs0") == 0 + # get/set + val = Float32(3.14) + r.set_f("fs0", val) + assert Float32.bitcast(r.get_f("fs0")) == val + def test_unlimited_regs_works(): r = Registers(infinite_regs=True)