Skip to content

Commit

Permalink
Merge branch 'master' into feature-m-instruction-set
Browse files Browse the repository at this point in the history
  • Loading branch information
AntonLydike authored May 14, 2024
2 parents 96e8680 + 5e758a2 commit 8db605b
Show file tree
Hide file tree
Showing 12 changed files with 424 additions and 145 deletions.
23 changes: 23 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Thank you for opening a PR to riscemu, your contribution is highly appreciated!

We've put together two checklists below to make sure the review and merge process goes as smoothly as possible. Please make sure that you complete the writing checklist before opening the PR. Please feel free to delete it once you completed it.

The pre-merge checklist is here so that you can see roughly what is expected of a PR to be merged. Not every step is always required, but checking all the boxes makes it almost certain that your PR will be merged swiftly. Feel free to leave it in the PR text, or delete it, it's up to you!

Good luck with the PR!


**PR Writing Checklist:**

- [ ] If I'm referencing an Issue, I put the issue number in the PR name
- [ ] I wrote a paragraph explaining my changes and the motivation
- [ ] If this PR is not ready yet for review, I mark it as draft
- [ ] Delete this checklist before opening the PR


**Pre-Merge Checklist:**

- [ ] I added an entry to the `CHANGELOG.md`
- [ ] I added a test that covers my change
- [ ] I ran the pre-commit formatting hooks
- [ ] CI Passes
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## Upcoming:

- Feature: Canonicalize register names when parsing, converting e.g. `x0 -> zero` or `fp -> s0`.
- Feature: Added support for `fcvt.d.w[u]` and `fcvt.w[u].d` instructions
- BugFix: Fixed that registers were treated as UInt32s instead of Int32 (this may have caused subtle bugs before)
- Feature: Added the remainder of the `M` extension
- BugFix: Fixed a bug in the overflow behavior of `mulh`
- BugFix: Fix faulty length assertion in `jalr`

## 2.2.5

- BugFix: Fix missed import in core.simple_instruction
Expand Down
245 changes: 115 additions & 130 deletions poetry.lock

Large diffs are not rendered by default.

13 changes: 9 additions & 4 deletions riscemu/core/float.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import struct
import warnings
from ctypes import c_float, c_double
from typing import Union, Any, ClassVar, Type
from abc import ABC
Expand Down Expand Up @@ -130,9 +131,6 @@ 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

Expand Down Expand Up @@ -190,7 +188,14 @@ def __format__(self, spec: str):
return Float32.bitcast(self).__format__(spec[:-2])
if spec[-2:] == "64":
return Float64.bitcast(self).__format__(spec[:-2])
return format(self.value, spec)
return f

def __int__(self):
warnings.warn(
"Float32.__int__ is deprecated due its inability to handle infities and NaNs correctly! Use {Int32,UInt32).from_float instead!",
DeprecationWarning,
)
return int(self.value)


class Float32(BaseFloat):
Expand Down
36 changes: 32 additions & 4 deletions riscemu/core/int32.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from typing import Any, Union
import math

from typing import Any, Union, ClassVar
from ctypes import c_int32, c_uint32


Expand All @@ -15,6 +17,9 @@ class Int32:
_type = c_int32
__slots__ = ("_val",)

MIN_VALUE: ClassVar[int] = -(2**31)
MAX_VALUE: ClassVar[int] = 2**31 - 1

def __init__(
self, val: Union[int, c_int32, c_uint32, "Int32", bytes, bytearray, bool] = 0
):
Expand Down Expand Up @@ -205,9 +210,7 @@ def signed(self) -> "Int32":
Convert to a signed representation. See :class:Int32
:return:
"""
if self.__class__ == Int32:
return self
return Int32(self)
return self

@property
def unsigned_value(self):
Expand Down Expand Up @@ -255,6 +258,24 @@ def sign_extend(cls, data: Union[bytes, bytearray, int], bits: int):
data = (data & (2 ** (bits - 1) - 1)) - 2 ** (bits - 1)
return cls(data)

@classmethod
def from_float(cls, number: float) -> "Int32":
"""
Convert a floating point number to an instance of Int32, handling overflows and NaN according to RISC-V spec.
- Valid values and infinities are clamped between MIN_VALUE and MAX_VALUE
- NaN is treated as -inf
"""
if math.isnan(number):
number = cls.MIN_VALUE
elif number > cls.MAX_VALUE:
number = cls.MAX_VALUE
elif number < cls.MIN_VALUE:
number = cls.MIN_VALUE
else:
number = int(number)
return cls(number)


class UInt32(Int32):
"""
Expand All @@ -263,6 +284,9 @@ class UInt32(Int32):

_type = c_uint32

MIN_VALUE: ClassVar[int] = 0
MAX_VALUE: ClassVar[int] = 2**32 - 1

def unsigned(self) -> "UInt32":
"""
Return a new instance representing the same bytes, but signed
Expand All @@ -284,3 +308,7 @@ def shift_right_logical(self, amount: Union["Int32", int]) -> "UInt32":
if isinstance(amount, Int32):
amount = amount.value
return UInt32(self.value >> amount)

def signed(self) -> "Int32":
# Overflow is handled natively by the underlying c_uint32 to c_int32 conversion!
return Int32(self._val)
2 changes: 1 addition & 1 deletion riscemu/core/registers.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def set(self, reg: str, val: Int32, mark_set: bool = True) -> bool:
if not self.infinite_regs and reg not in self.valid_regs:
raise RuntimeError("Invalid register: {}".format(reg))

self.vals[reg] = val.unsigned()
self.vals[reg] = val.signed()
return True

def get(self, reg: str, mark_read: bool = True) -> Int32:
Expand Down
80 changes: 80 additions & 0 deletions riscemu/instructions/RV32D.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,83 @@
class RV32D(FloatArithBase[Float64]):
flen = 64
_float_cls = Float64

def instruction_fcvt_d_w(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|11010|01 |00000|rs1 |rm |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fcvt.d.w rd,rs1
:Description:
| Converts a 32-bit signed integer, in integer register rs1 into a double-precision floating-point number in floating-point register rd.
:Implementation:
| x[rd] = sext(s32_{f64}(f[rs1]))
"""
rd, rs = self.parse_rd_rs(ins)
self.regs.set_f(rd, Float64(self.regs.get(rs).value))

def instruction_fcvt_d_wu(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|11010|01 |00001|rs1 |rm |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fcvt.d.wu rd,rs1
:Description:
| Converts a 32-bit unsigned integer, in integer register rs1 into a double-precision floating-point number in floating-point register rd.
:Implementation:
| f[rd] = f64_{u32}(x[rs1])
"""
rd, rs = self.parse_rd_rs(ins)
self.regs.set_f(rd, Float64(self.regs.get(rs).unsigned_value))

def instruction_fcvt_w_d(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|11000|01 |00000|rs1 |rm |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fcvt.w.d rd,rs1
:Description:
| Converts a double-precision floating-point number in floating-point register rs1 to a signed 32-bit integer, in integer register rd.
:Implementation:
| x[rd] = sext(s32_{f64}(f[rs1]))
"""
rd, rs = self.parse_rd_rs(ins)
self.regs.set(rd, Int32.from_float(self.regs.get_f(rs).value))

def instruction_fcvt_wu_d(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|11000|01 |00001|rs1 |rm |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fcvt.wu.d rd,rs1
:Description:
| Converts a double-precision floating-point number in floating-point register rs1 to a unsigned 32-bit integer, in integer register rd.
:Implementation:
| x[rd] = sext(u32f64(f[rs1]))
"""
rd, rs = self.parse_rd_rs(ins)
self.regs.set(rd, UInt32.from_float(self.regs.get_f(rs).value))
6 changes: 3 additions & 3 deletions riscemu/instructions/RV32F.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def instruction_fcvt_w_s(self, ins: Instruction):
| x[rd] = sext(s32_{f32}(f[rs1]))
"""
rd, rs = self.parse_rd_rs(ins)
self.regs.set(rd, Int32(int(self.regs.get_f(rs).value)))
self.regs.set(rd, Int32.from_float(self.regs.get_f(rs).value))

def instruction_fcvt_wu_s(self, ins: Instruction):
"""
Expand All @@ -55,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(int(self.regs.get_f(rs).value)))
self.regs.set(rd, UInt32.from_float((self.regs.get_f(rs).value)))

def instruction_fmv_x_w(self, ins: Instruction):
"""
Expand Down Expand Up @@ -95,7 +95,7 @@ def instruction_fcvt_s_w(self, ins: Instruction):
| f[rd] = f32_{s32}(x[rs1])
"""
rd, rs = self.parse_rd_rs(ins)
self.regs.set_f(rd, Float32(self.regs.get(rs).signed().value))
self.regs.set_f(rd, Float32(self.regs.get(rs).value))

def instruction_fcvt_s_wu(self, ins: Instruction):
"""
Expand Down
97 changes: 96 additions & 1 deletion riscemu/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,74 @@
ParseException,
)

REG_NAME_CANONICALIZER = {
"fp": "s0",
"x0": "zero",
"x1": "ra",
"x2": "sp",
"x3": "gp",
"x4": "tp",
"x5": "t0",
"x6": "t1",
"x7": "t2",
"x8": "s0",
"x9": "s1",
"x10": "a0",
"x11": "a1",
"x12": "a2",
"x13": "a3",
"x14": "a4",
"x15": "a5",
"x16": "a6",
"x17": "a7",
"x18": "s2",
"x19": "s3",
"x20": "s4",
"x21": "s5",
"x22": "s6",
"x23": "s7",
"x24": "s8",
"x25": "s9",
"x26": "s10",
"x27": "s11",
"x28": "t3",
"x29": "t4",
"x30": "t5",
"x31": "t6",
"f0": "ft0",
"f1": "ft1",
"f2": "ft2",
"f3": "ft3",
"f4": "ft4",
"f5": "ft5",
"f6": "ft6",
"f7": "ft7",
"f8": "fs0",
"f9": "fs1",
"f10": "fa0",
"f11": "fa1",
"f12": "fa2",
"f13": "fa3",
"f14": "fa4",
"f15": "fa5",
"f16": "fa6",
"f17": "fa7",
"f18": "fs2",
"f19": "fs3",
"f20": "fs4",
"f21": "fs5",
"f22": "fs6",
"f23": "fs7",
"f24": "fs8",
"f25": "fs9",
"f26": "fs10",
"f27": "fs11",
"f28": "ft8",
"f29": "ft9",
"f30": "ft10",
"f31": "ft11",
}


def parse_instruction(token: Token, args: Tuple[str], context: ParseContext):
if context.section is None:
Expand All @@ -28,7 +96,10 @@ def parse_instruction(token: Token, args: Tuple[str], context: ParseContext):
"{} {} encountered in invalid context: {}".format(token, args, context)
)
ins = SimpleInstruction(
token.value, args, context.context, context.current_address()
token.value,
parse_instruction_arguments(args),
context.context,
context.current_address(),
)
context.section.data.append(ins)

Expand Down Expand Up @@ -112,6 +183,30 @@ def take_arguments(tokens: Peekable[Token]) -> Iterable[str]:
# raise ParseException("Expected newline, instead got {}".format(tokens.peek()))


def parse_instruction_arguments(args: Tuple[str]) -> Tuple[str]:
"""
Parses argument Tuples of instructions. In this process canonicalize register names
:param args: A tuple of an instructions arguments
:return: A tuple of an instructions parsed arguments
"""
return tuple(canonicalize_register_names(args))


def canonicalize_register_names(args: Tuple[str]) -> Iterable[str]:
"""
Translate register indices names to ABI names. Leaves other arguments unchanged.
Examples:
"x0" -> "zero"
"x5" -> "t0"
"x8" -> "s0"
"fp" -> "s0"
:param args: A tuple of an instructions arguments
:return: An iterator over the arguments of an instruction, but with canonicalized register names
"""
for arg in args:
yield REG_NAME_CANONICALIZER.get(arg, arg)


class AssemblyFileLoader(ProgramLoader):
"""
This class loads assembly files written by hand. It understands some assembler
Expand Down
15 changes: 15 additions & 0 deletions test/filecheck/hello-world-register-indices.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// RUN: python3 -m riscemu -v %s | filecheck %s
.data
msg: .ascii "Hello world\n"
.text
addi x10, x0, 1 ; print to stdout
addi x11, x0, msg ; load msg address
addi x12, x0, 12 ; write 12 bytes
addi x17, x0, SCALL_WRITE ; write syscall code
scall
addi x10, x0, 0 ; set exit code to 0
addi x17, x0, SCALL_EXIT ; exit syscall code
scall

// CHECK: Hello world
// CHECK: [CPU] Program exited with code 0
Loading

0 comments on commit 8db605b

Please sign in to comment.