Compare commits

...

11 Commits

Author SHA1 Message Date
Anton Lydike f8b979b08a fix release workflow 1 year ago
Anton Lydike d102376212 release 2.1.1 1 year ago
KGrykiel 3b89387e0a
Debugger fix (#32)
fixed an issue where debugger had an error when trying to dump registers. It tried to use get() method instead of get_f() method. Also changed format of floating point registers to show up as floats instead of Hex.
1 year ago
Sasha Lopoukhine 07265f26c9
allow for infinite registers in sw/lw instructions (#31) 1 year ago
Alban Dutilleul 801b165e70
fix various semantic mismatchs in RV32F (#27) 1 year ago
Alban Dutilleul 7e34855ad1
format using black (#28) 1 year ago
Anton Lydike 26d7f65cc1 add deployment github workflow 1 year ago
Anton Lydike 2ec134f612 bump version to 2.1.0 1 year ago
Anton Lydike be90879f86
Add support for floats (#22)
Adding a `Float32` datatype is necessary, since python makes no guarantees to the bitwidth of `float` (it's often a double)

Also adding the `RV32F` extension with most operations implemented, and support for floating point registers.
1 year ago
Sasha Lopoukhine 5a23804ad8
add py.typed file for riscemu to declare itself as a typed python package (#21)
* add py.typed to setup.py package_data

* create py.typed file

* move py.typed
1 year ago
Sasha Lopoukhine a217705d1f add a couple of type annotations in parser.py 1 year ago

@ -0,0 +1,22 @@
name: Upload to PyPI
on:
release:
types: [published]
jobs:
deploy:
runs-on: ubuntu-latest
environment: publishing
permissions:
# IMPORTANT: this permission is mandatory for trusted publishing
id-token: write
steps:
# install build package and build dist
- name: Build distribution
run: >-
python3 -m pip install build --user &&
python3 -m build --sdist --wheel --outdir dist/
# retrieve your distributions here
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (riscemu)" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (riscemu)" project-jdk-type="Python SDK" />
</project> </project>

@ -10,7 +10,7 @@
<excludeFolder url="file://$MODULE_DIR$/riscemu.egg-info" /> <excludeFolder url="file://$MODULE_DIR$/riscemu.egg-info" />
<excludeFolder url="file://$MODULE_DIR$/build" /> <excludeFolder url="file://$MODULE_DIR$/build" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="jdk" jdkName="Python 3.10 (riscemu)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

@ -1,14 +1,19 @@
# Changelog # Changelog
## Upcoming 2.0.6 ## 2.1.1
- Bugfix: Fix some errors in the RV32F implementation (thanks @adutilleul)
- Bugfix: Fix how floats are printed in the register view (thanks @KGrykiel)
- Bugfix: Fix missing support for infinite registers in load/store ins (thanks @superlopuh)
## 2.1.0
- Added a very basic libc containing a `crt0.s`, and a few functions - Added a very basic libc containing a `crt0.s`, and a few functions
such as `malloc`, `rand`, and `memcpy`. such as `malloc`, `rand`, and `memcpy`.
- Added a subset of the `mmap2` syscall (code 192) to allocate new memory - Added a subset of the `mmap2` syscall (code 192) to allocate new memory
- Refactored the launching code to improve using riscemu from code - Refactored the launching code to improve using riscemu from code
- Added an option to start with the provided libc: `-o libc`
**Planned:** - Added floating point support (enabled by default). The RV32F extension is now available
- Add a floating point unit
## 2.0.5 ## 2.0.5

@ -0,0 +1,11 @@
main:
li a1, 1084227584
li a2, 1082130432
fcvt.s.wu ft0, a1
fcvt.s.wu ft1, a2
fmul.s ft6, ft0, ft1
print.float ft6
// exit gracefully
addi a0, zero, 0
addi a7, zero, 93
scall // exit with code 0

@ -16,6 +16,7 @@ from .types import (
Program, Program,
InstructionContext, InstructionContext,
Int32, Int32,
Float32,
) )
from .types.exceptions import InvalidAllocationException, MemoryAccessException from .types.exceptions import InvalidAllocationException, MemoryAccessException
@ -187,6 +188,9 @@ class MMU:
def read_int(self, addr: int) -> Int32: def read_int(self, addr: int) -> Int32:
return Int32(self.read(addr, 4)) return Int32(self.read(addr, 4))
def read_float(self, addr: int) -> Float32:
return Float32(self.read(addr, 4))
def translate_address(self, address: T_AbsoluteAddress) -> str: def translate_address(self, address: T_AbsoluteAddress) -> str:
sec = self.get_sec_containing(address) sec = self.get_sec_containing(address)
if not sec: if not sec:

@ -34,4 +34,4 @@ from .parser import tokenize, parse_tokens, AssemblyFileLoader
__author__ = "Anton Lydike <Anton@Lydike.com>" __author__ = "Anton Lydike <Anton@Lydike.com>"
__copyright__ = "Copyright 2023 Anton Lydike" __copyright__ = "Copyright 2023 Anton Lydike"
__version__ = "2.0.5" __version__ = "2.1.1"

@ -0,0 +1,609 @@
"""
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 riscemu.types 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 substraction.
: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 results sign bit is rs2s 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 results sign bit is opposite of rs2s 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 results 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,
)
),
)
def instruction_fcvt_w_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|11000|00 |00000|rs1 |rm |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fcvt.w.s rd,rs1
:Description:
| Convert a floating-point number in floating-point register rs1 to a signed 32-bit in integer register rd.
:Implementation:
| x[rd] = sext(s32_{f32}(f[rs1]))
"""
rd, rs = self.parse_rd_rs(ins)
self.regs.set(rd, Int32(self.regs.get_f(rs).bytes))
def instruction_fcvt_wu_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|11000|00 |00001|rs1 |rm |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fcvt.wu.s rd,rs1
:Description:
| Convert a floating-point number in floating-point register rs1 to a signed 32-bit in unsigned integer register rd.
:Implementation:
| x[rd] = sext(u32_{f32}(f[rs1]))
"""
rd, rs = self.parse_rd_rs(ins)
self.regs.set(rd, UInt32(self.regs.get_f(rs).bytes))
def instruction_fmv_x_w(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|11100|00 |00000|rs1 |000 |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fmv.x.w rd,rs1
:Description:
| Move the single-precision value in floating-point register rs1 represented in IEEE 754-2008 encoding to the lower 32 bits of integer register rd.
:Implementation:
| x[rd] = sext(f[rs1][31:0])
"""
rd, rs = self.parse_rd_rs(ins)
self.regs.set(rd, UInt32(self.regs.get_f(rs).bits))
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)
def instruction_fcvt_s_w(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|11010|00 |00000|rs1 |rm |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fcvt.s.w rd,rs1
:Description:
| Converts a 32-bit signed integer, in integer register rs1 into a floating-point number in floating-point register rd.
:Implementation:
| f[rd] = f32_{s32}(x[rs1])
"""
rd, rs = self.parse_rd_rs(ins)
self.regs.set_f(rd, Float32.from_bytes(self.regs.get(rs).signed().value))
def instruction_fcvt_s_wu(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|11010|00 |00001|rs1 |rm |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fcvt.s.wu rd,rs1
:Description:
| Converts a 32-bit unsigned integer, in integer register rs1 into a floating-point number in floating-point register rd.
:Implementation:
| f[rd] = f32_{u32}(x[rs1])
"""
rd, rs = self.parse_rd_rs(ins)
self.regs.set_f(rd, Float32.from_bytes(self.regs.get(rs).unsigned_value))
def instruction_fmv_w_x(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|11110|00 |00000|rs1 |000 |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fmv.w.x rd,rs1
:Description:
| Move the single-precision value encoded in IEEE 754-2008 standard encoding from the lower 32 bits of integer register rs1 to the floating-point register rd.
:Implementation:
| 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)),
)

@ -6,6 +6,10 @@ class RV_Debug(InstructionSet):
reg = ins.get_reg(0) reg = ins.get_reg(0)
print("register {} contains value {}".format(reg, self.regs.get(reg))) print("register {} contains value {}".format(reg, self.regs.get(reg)))
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_uint(self, ins: Instruction): def instruction_print_uint(self, ins: Instruction):
reg = ins.get_reg(0) reg = ins.get_reg(0)
print( print(
@ -16,7 +20,9 @@ class RV_Debug(InstructionSet):
def instruction_print_hex(self, ins: Instruction): def instruction_print_hex(self, ins: Instruction):
reg = ins.get_reg(0) reg = ins.get_reg(0)
print("register {} contains value {}".format(reg, hex(self.regs.get(reg)))) print(
"register {} contains value {}".format(reg, hex(self.regs.get(reg).value))
)
def instruction_print_uhex(self, ins: Instruction): def instruction_print_uhex(self, ins: Instruction):
reg = ins.get_reg(0) reg = ins.get_reg(0)

@ -10,6 +10,7 @@ from .instruction_set import InstructionSet, Instruction
from .RV32M import RV32M from .RV32M import RV32M
from .RV32I import RV32I from .RV32I import RV32I
from .RV32A import RV32A from .RV32A import RV32A
from .RV32F import RV32F
from .RV_Debug import RV_Debug from .RV_Debug import RV_Debug
InstructionSetDict = {v.__name__: v for v in [RV32I, RV32M, RV32A, RV_Debug]} InstructionSetDict = {v.__name__: v for v in [RV32I, RV32M, RV32A, RV32F, RV_Debug]}

@ -121,7 +121,7 @@ class AssemblyFileLoader(ProgramLoader):
with open(self.source_path, "r") as f: with open(self.source_path, "r") as f:
return parse_tokens(self.filename, tokenize(f)) return parse_tokens(self.filename, tokenize(f))
def parse_io(self, io): def parse_io(self, io: Iterable[str]):
return parse_tokens(self.filename, tokenize(io)) return parse_tokens(self.filename, tokenize(io))
@classmethod @classmethod
@ -139,5 +139,5 @@ class AssemblyFileLoader(ProgramLoader):
return 0.01 return 0.01
@classmethod @classmethod
def get_options(cls, argv: List[str]) -> [List[str], T_ParserOpts]: def get_options(cls, argv: List[str]) -> Tuple[List[str], T_ParserOpts]:
return argv, {} return argv, {}

@ -1,15 +1,15 @@
""" """
RiscEmu (c) 2021-2022 Anton Lydike RiscEmu (c) 2023 Anton Lydike
SPDX-License-Identifier: MIT SPDX-License-Identifier: MIT
""" """
from collections import defaultdict from collections import defaultdict
from typing import Union
from .helpers import * from .helpers import *
if typing.TYPE_CHECKING: from .types import Int32, Float32
from .types import Int32
class Registers: class Registers:
@ -51,6 +51,9 @@ class Registers:
"a5", "a5",
"a6", "a6",
"a7", "a7",
}
float_regs = {
"ft0", "ft0",
"ft1", "ft1",
"ft2", "ft2",
@ -82,11 +85,12 @@ class Registers:
} }
def __init__(self, infinite_regs: bool = False): def __init__(self, infinite_regs: bool = False):
from .types import Int32 self.vals: dict[str, Int32] = defaultdict(UInt32)
self.float_vals: dict[str, Float32] = defaultdict(Float32)
self.vals: defaultdict[str, Int32] = defaultdict(lambda: Int32(0))
self.last_set = None self.last_set = None
self.last_read = None self.last_read = None
self.infinite_regs = infinite_regs self.infinite_regs = infinite_regs
def dump(self, full: bool = False): def dump(self, full: bool = False):
@ -143,6 +147,9 @@ class Registers:
) )
def _reg_repr(self, reg: str, name_len=4, fmt="08X"): def _reg_repr(self, reg: str, name_len=4, fmt="08X"):
if reg in self.float_regs:
txt = "{:{}}={: .3e}".format(reg, name_len, self.get_f(reg, False))
else:
txt = "{:{}}=0x{:{}}".format(reg, name_len, self.get(reg, False), fmt) txt = "{:{}}=0x{:{}}".format(reg, name_len, self.get(reg, False), fmt)
if reg == "fp": if reg == "fp":
reg = "s0" reg = "s0"
@ -152,7 +159,13 @@ class Registers:
return FMT_ORANGE + FMT_UNDERLINE + txt + FMT_NONE return FMT_ORANGE + FMT_UNDERLINE + txt + FMT_NONE
if reg == "zero": if reg == "zero":
return txt return txt
if self.get(reg, False) == 0 and reg not in Registers.named_registers(): should_grayscale_int = (
reg in self.valid_regs
and self.get(reg, False) == 0
and reg not in Registers.named_registers()
)
should_grayscale_float = reg in self.float_regs and self.get_f(reg, False) == 0
if should_grayscale_int or should_grayscale_float:
return FMT_GRAY + txt + FMT_NONE return FMT_GRAY + txt + FMT_NONE
return txt return txt
@ -165,8 +178,6 @@ class Registers:
:return: If the operation was successful :return: If the operation was successful
""" """
from .types import Int32
# remove after refactoring is complete # remove after refactoring is complete
if not isinstance(val, Int32): if not isinstance(val, Int32):
raise RuntimeError( raise RuntimeError(
@ -208,75 +219,17 @@ class Registers:
self.last_read = reg self.last_read = reg
return self.vals[reg] return self.vals[reg]
@staticmethod def get_f(self, reg: str, mark_read: bool = True) -> "Float32":
def all_registers(): if not self.infinite_regs and reg not in self.float_regs:
""" raise RuntimeError("Invalid float register: {}".format(reg))
Return a list of all valid registers if mark_read:
:return: The list self.last_read = reg
""" return self.float_vals[reg]
return [
"zero", def set_f(self, reg: str, val: Union[float, "Float32"]):
"ra", if not self.infinite_regs and reg not in self.float_regs:
"sp", raise RuntimeError("Invalid float register: {}".format(reg))
"gp", self.float_vals[reg] = Float32(val)
"tp",
"s0",
"fp",
"t0",
"t1",
"t2",
"t3",
"t4",
"t5",
"t6",
"s1",
"s2",
"s3",
"s4",
"s5",
"s6",
"s7",
"s8",
"s9",
"s10",
"s11",
"a0",
"a1",
"a2",
"a3",
"a4",
"a5",
"a6",
"a7",
"ft0",
"ft1",
"ft2",
"ft3",
"ft4",
"ft5",
"ft6",
"ft7",
"fs0",
"fs1",
"fs2",
"fs3",
"fs4",
"fs5",
"fs6",
"fs7",
"fs8",
"fs9",
"fs10",
"fs11",
"fa0",
"fa1",
"fa2",
"fa3",
"fa4",
"fa5",
"fa6",
"fa7",
]
@staticmethod @staticmethod
def named_registers(): def named_registers():
@ -287,8 +240,9 @@ class Registers:
return ["zero", "ra", "sp", "gp", "tp", "fp"] return ["zero", "ra", "sp", "gp", "tp", "fp"]
def __repr__(self): def __repr__(self):
return "<Registers{}>".format( return "<Registers[{}]{}>".format(
"float" if self.supports_floats else "nofloat",
"{" "{"
+ ", ".join(self._reg_repr("a{}".format(i), 2, "0x") for i in range(8)) + ", ".join(self._reg_repr("a{}".format(i), 2, "0x") for i in range(8))
+ "}" + "}",
) )

@ -15,7 +15,7 @@ from riscemu.types.exceptions import ParseException
LINE_COMMENT_STARTERS = ("#", ";", "//") LINE_COMMENT_STARTERS = ("#", ";", "//")
WHITESPACE_PATTERN = re.compile(r"\s+") WHITESPACE_PATTERN = re.compile(r"\s+")
MEMORY_ADDRESS_PATTERN = re.compile( MEMORY_ADDRESS_PATTERN = re.compile(
r"^(0[xX][A-f0-9]+|\d+|0b[0-1]+|[A-z0-9_-]+)\(([A-z]+[0-9]{0,2})\)$" r"^(0[xX][A-f0-9]+|\d+|0b[0-1]+|[A-z0-9_-]+)\(([A-z]+[0-9]*)\)$"
) )
REGISTER_NAMES = RISCV_REGS REGISTER_NAMES = RISCV_REGS
@ -88,10 +88,9 @@ def parse_arg(arg: str) -> Iterable[Token]:
mem_match_resul = re.match(MEMORY_ADDRESS_PATTERN, arg) mem_match_resul = re.match(MEMORY_ADDRESS_PATTERN, arg)
if mem_match_resul: if mem_match_resul:
register = mem_match_resul.group(2).lower() register = mem_match_resul.group(2).lower()
if register not in RISCV_REGS: immediate = mem_match_resul.group(1)
raise ParseException(f'"{register}" is not a valid register!')
yield Token(TokenType.ARGUMENT, register) yield Token(TokenType.ARGUMENT, register)
yield Token(TokenType.ARGUMENT, mem_match_resul.group(1)) yield Token(TokenType.ARGUMENT, immediate)
else: else:
yield Token(TokenType.ARGUMENT, arg) yield Token(TokenType.ARGUMENT, arg)
if comma: if comma:

@ -13,6 +13,7 @@ NUMBER_SYMBOL_PATTERN = re.compile(r"^\d+[fb]$")
# base classes # base classes
from .flags import MemoryFlags from .flags import MemoryFlags
from .int32 import UInt32, Int32 from .int32 import UInt32, Int32
from .float32 import Float32
from .instruction import Instruction from .instruction import Instruction
from .instruction_context import InstructionContext from .instruction_context import InstructionContext
from .memory_section import MemorySection from .memory_section import MemorySection
@ -36,4 +37,5 @@ from .exceptions import (
InvalidAllocationException, InvalidAllocationException,
InvalidSyscallException, InvalidSyscallException,
UnimplementedInstruction, UnimplementedInstruction,
INS_NOT_IMPLEMENTED,
) )

@ -0,0 +1,198 @@
import struct
from ctypes import c_float
from typing import Union, Any
bytes_t = bytes
class Float32:
__slots__ = ("_val",)
_val: c_float
@property
def value(self) -> float:
"""
The value represented by this float
"""
return self._val.value
@property
def bytes(self) -> bytes:
"""
The values bit representation (as a bytes object)
"""
return struct.pack("<f", self.value)
@property
def bits(self) -> int:
"""
The values bit representation as an int (for easy bit manipulation)
"""
return int.from_bytes(self.bytes, byteorder="little")
@classmethod
def from_bytes(cls, val: Union[int, bytes_t, bytearray]):
if isinstance(val, int):
val = struct.unpack("!f", struct.pack("!I", val))[0]
return Float32(val)
def __init__(
self, val: Union[float, c_float, "Float32", bytes_t, bytearray, int] = 0
):
if isinstance(val, (float, int)):
self._val = c_float(val)
elif isinstance(val, c_float):
self._val = c_float(val.value)
elif isinstance(val, (bytes, bytearray)):
self._val = c_float(struct.unpack("<f", val)[0])
elif isinstance(val, Float32):
self._val = val._val
else:
raise ValueError(
"Unsupported value passed to Float32: {} ({})".format(
repr(val), type(val)
)
)
def __add__(self, other: Union["Float32", float]):
if isinstance(other, Float32):
other = other.value
return self.__class__(self.value + other)
def __sub__(self, other: Union["Float32", float]):
if isinstance(other, Float32):
other = other.value
return self.__class__(self.value - other)
def __mul__(self, other: Union["Float32", float]):
if isinstance(other, Float32):
other = other.value
return self.__class__(self.value * other)
def __truediv__(self, other: Any):
return self // other
def __floordiv__(self, other: Any):
if isinstance(other, Float32):
other = other.value
return self.__class__(self.value // other)
def __mod__(self, other: Union["Float32", float]):
if isinstance(other, Float32):
other = other.value
return self.__class__(self.value % other)
def __eq__(self, other: object) -> bool:
if isinstance(other, (float, int)):
return self.value == other
elif isinstance(other, Float32):
return self.value == other.value
return False
def __neg__(self):
return self.__class__(-self.value)
def __abs__(self):
return self.__class__(abs(self.value))
def __bytes__(self) -> bytes_t:
return self.bytes
def __repr__(self):
return "{}({})".format(self.__class__.__name__, self.value)
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)
def __gt__(self, other: Any):
if isinstance(other, Float32):
other = other.value
return self.value > other
def __lt__(self, other: Any):
if isinstance(other, Float32):
other = other.value
return self.value < other
def __le__(self, other: Any):
if isinstance(other, Float32):
other = other.value
return self.value <= other
def __ge__(self, other: Any):
if isinstance(other, Float32):
other = other.value
return self.value >= other
def __bool__(self):
return bool(self.value)
def __pow__(self, power, modulo=None):
if modulo is not None:
raise ValueError("Float32 pow with modulo unsupported")
return self.__class__(self.value**power)
# right handed binary operators
def __radd__(self, other: Any):
return self + other
def __rsub__(self, other: Any):
return self.__class__(other) - self
def __rmul__(self, other: Any):
return self * other
def __rtruediv__(self, other: Any):
return self.__class__(other) // self
def __rfloordiv__(self, other: Any):
return self.__class__(other) // self
def __rmod__(self, other: Any):
return self.__class__(other) % self
def __rand__(self, other: Any):
return self.__class__(other) & self
def __ror__(self, other: Any):
return self.__class__(other) | self
def __rxor__(self, other: Any):
return self.__class__(other) ^ self
# bytewise operators:
def __and__(self, other: Union["Float32", float, int]):
if isinstance(other, float):
other = Float32(other)
if isinstance(other, Float32):
other = other.bits
return self.from_bytes(self.bits & other)
def __or__(self, other: Union["Float32", float]):
if isinstance(other, float):
other = Float32(other)
if isinstance(other, Float32):
other = other.bits
return self.from_bytes(self.bits | other)
def __xor__(self, other: Union["Float32", float]):
if isinstance(other, float):
other = Float32(other)
if isinstance(other, Float32):
other = other.bits
return self.from_bytes(self.bits ^ other)
def __lshift__(self, other: int):
return self.from_bytes(self.bits << other)
def __rshift__(self, other: int):
return self.from_bytes(self.bits >> other)

@ -16,7 +16,7 @@ class Int32:
__slots__ = ("_val",) __slots__ = ("_val",)
def __init__( def __init__(
self, val: Union[int, c_int32, c_uint32, "Int32", bytes, bytearray] = 0 self, val: Union[int, c_int32, c_uint32, "Int32", bytes, bytearray, bool] = 0
): ):
if isinstance(val, (bytes, bytearray)): if isinstance(val, (bytes, bytearray)):
signed = len(val) == 4 and self._type == c_int32 signed = len(val) == 4 and self._type == c_int32
@ -27,7 +27,7 @@ class Int32:
self._val = val self._val = val
elif isinstance(val, (c_uint32, c_int32, Int32)): elif isinstance(val, (c_uint32, c_int32, Int32)):
self._val = self.__class__._type(val.value) self._val = self.__class__._type(val.value)
elif isinstance(val, int): elif isinstance(val, (int, bool)):
self._val = self.__class__._type(val) self._val = self.__class__._type(val)
else: else:
raise RuntimeError( raise RuntimeError(

@ -1,4 +1,5 @@
import setuptools import setuptools
from glob import glob
import riscemu import riscemu
@ -32,8 +33,11 @@ setuptools.setup(
"riscemu.types", "riscemu.types",
], ],
package_data={ package_data={
"riscemu": ["libc/*.s"], "riscemu": ["libc/*.s", "py.typed"],
}, },
data_files=[
("libc", glob("libc/*.s")),
],
scripts=["riscemu/tools/riscemu"], scripts=["riscemu/tools/riscemu"],
python_requires=">=3.8", python_requires=">=3.8",
install_requires=["pyelftools~=0.27"], install_requires=["pyelftools~=0.27"],

@ -0,0 +1,25 @@
import math
from riscemu.types import Float32
# 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_random_float_ops():
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_float_from_raw_int_conversion():
assert Float32.from_bytes(1084227584) == Float32(5.0)

@ -0,0 +1,26 @@
import pytest
from riscemu.registers import Registers
from riscemu.types import Float32
def test_float_regs():
r = Registers()
# uninitialized register is zero
assert r.get_f("fs0") == 0
# get/set
val = Float32(3.14)
r.set_f("fs0", val)
assert r.get_f("fs0") == val
def test_unlimited_regs_works():
r = Registers(infinite_regs=True)
r.get("infinite")
r.get_f("finfinite")
def test_unknown_reg_fails():
r = Registers(infinite_regs=False)
with pytest.raises(RuntimeError, match="Invalid register: az1"):
r.get("az1")
Loading…
Cancel
Save