Skip to content

Commit 389efd9

Browse files
Add signed and unsigned remainder operations for RISC-V semantics (#160)
Implement rules for `REM` and `REMU` instructions in `riscv.md`, detailing their behavior and special cases. Update syntax definitions in `word.md` to include remainder operations, and provide comprehensive handling for division by zero and overflow scenarios. Adjust test integration to utilize the `rv32em` architecture for relevant tests. Close runtimeverification/zkevm-harness#80 --------- Co-authored-by: devops <devops@runtimeverification.com>
1 parent a8608d9 commit 389efd9

File tree

11 files changed

+141
-5
lines changed

11 files changed

+141
-5
lines changed

package/version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.118
1+
0.1.119

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "kriscv"
7-
version = "0.1.118"
7+
version = "0.1.119"
88
description = "K tooling for the RISC-V architecture"
99
readme = "README.md"
1010
requires-python = "~=3.10"

src/kriscv/kdist/riscv-semantics/riscv.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,14 @@ The following instructions behave analogously to their `I`-suffixed counterparts
258258
rule <instrs> DIVU RD , RS1 , RS2 => .K ...</instrs>
259259
<regs> REGS => writeReg(REGS, RD, readReg(REGS, RS1) /uWord readReg(REGS, RS2)) </regs>
260260
```
261+
`REM` and `REMU` perform signed and unsigned remainder, respectively.
262+
```k
263+
rule <instrs> REM RD , RS1 , RS2 => .K ...</instrs>
264+
<regs> REGS => writeReg(REGS, RD, readReg(REGS, RS1) %Word readReg(REGS, RS2)) </regs>
265+
266+
rule <instrs> REMU RD , RS1 , RS2 => .K ...</instrs>
267+
<regs> REGS => writeReg(REGS, RD, readReg(REGS, RS1) %uWord readReg(REGS, RS2)) </regs>
268+
```
261269
`SLL`, `SRL`, and `SRA` read their shift amount fom the lowest `log_2(XLEN)` bits of `RS2`.
262270
```k
263271
rule <instrs> SLL RD , RS1 , RS2 => .K ...</instrs>

src/kriscv/kdist/riscv-semantics/word.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ To avoid syntax conflicts, we define the following syntax with `left` associativ
6363
| Int "*hsuWord" Int [function, total, symbol(mulhsuWord)]
6464
| Int "/Word" Int [function, total, symbol(divWord)]
6565
| Int "/uWord" Int [function, total, symbol(divuWord)]
66+
| Int "%Word" Int [function, total, symbol(remWord)]
67+
| Int "%uWord" Int [function, total, symbol(remuWord)]
6668
> left:
6769
Int "+Word" Int [function, total, symbol(addWord)]
6870
| Int "-Word" Int [function, total, symbol(subWord)]
@@ -114,6 +116,26 @@ For unsigned division (`/uWord`):
114116
rule _ /uWord W2 => chop((2 ^Int XLEN) -Int 1) requires W2 ==Word 0
115117
rule W1 /uWord W2 => chop(W1 /Int W2) requires W2 =/=Word 0
116118
```
119+
For signed remainder (`%Word`):
120+
- Division by zero returns the dividend
121+
- Signed overflow (most negative number divided by -1) returns 0
122+
- Normal case performs signed remainder with truncation towards zero
123+
```k
124+
rule W1 %Word W2 => W1 requires W2 ==Word 0
125+
rule W1 %Word W2 => 0
126+
requires W1 ==Word 2 ^Int (XLEN -Int 1)
127+
andBool W2 ==Word chop(-1)
128+
rule W1 %Word W2 => chop(Word2SInt(W1) %Int Word2SInt(W2))
129+
requires W2 =/=Word 0
130+
andBool notBool (W1 ==Word chop(2 ^Int (XLEN -Int 1)) andBool W2 ==Word chop(-1))
131+
```
132+
For unsigned remainder (`%uWord`):
133+
- Division by zero returns the dividend
134+
- Normal case performs unsigned remainder with truncation towards zero
135+
```k
136+
rule W1 %uWord W2 => W1 requires W2 ==Word 0
137+
rule W1 %uWord W2 => chop(W1 %Int W2) requires W2 =/=Word 0
138+
```
117139
Above, the `chop` utility function
118140
```k
119141
syntax Int ::= chop(Int) [function, total]

src/tests/integration/test_functions.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,3 +238,50 @@ def test_divu(definition_dir: Path, op1: int, op2: int) -> None:
238238
op2=op2,
239239
res=divu_expected(op1, op2),
240240
)
241+
242+
243+
def rem_expected(op1: int, op2: int) -> int:
244+
# Calculate expected result according to RISC-V specification for signed remainder
245+
if op2 == 0:
246+
# Division by zero returns the dividend
247+
expected = op1
248+
elif op1 == 0x80000000 and op2 == 0xFFFFFFFF:
249+
# Signed overflow: -2^31 % -1 returns 0
250+
expected = 0
251+
else:
252+
# Normal signed remainder with truncation towards zero
253+
expected = chop(signed(op1) % signed(op2))
254+
return expected
255+
256+
257+
@pytest.mark.parametrize('op1,op2', DIV_TEST_DATA, ids=count())
258+
def test_rem(definition_dir: Path, op1: int, op2: int) -> None:
259+
_test_binary_op(
260+
definition_dir=definition_dir,
261+
symbol='LblremWord',
262+
op1=op1,
263+
op2=op2,
264+
res=rem_expected(op1, op2),
265+
)
266+
267+
268+
def remu_expected(op1: int, op2: int) -> int:
269+
# Calculate expected result according to RISC-V specification for unsigned remainder
270+
if op2 == 0:
271+
# Division by zero returns the dividend
272+
expected = op1
273+
else:
274+
# Normal unsigned remainder with truncation towards zero
275+
expected = op1 % op2
276+
return expected
277+
278+
279+
@pytest.mark.parametrize('op1,op2', DIV_TEST_DATA, ids=count())
280+
def test_remu(definition_dir: Path, op1: int, op2: int) -> None:
281+
_test_binary_op(
282+
definition_dir=definition_dir,
283+
symbol='LblremuWord',
284+
op1=op1,
285+
op2=op2,
286+
res=remu_expected(op1, op2),
287+
)

src/tests/integration/test_integration.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,14 @@ def _test_simple(tools: Tools, elf_file: Path, assert_file: Path, final_config_o
123123
)
124124
def test_simple(asm_file: Path, save_final_config: bool, temp_dir: Path) -> None:
125125
elf_file = Path(temp_dir) / (asm_file.stem + '.elf')
126+
# Use rv32em for tests that require M extension (mul/div/rem instructions)
127+
arch = 'rv32em' if asm_file.stem in ['rem', 'remu'] else 'rv32e'
126128
compile_cmd = [
127129
'riscv64-unknown-elf-gcc',
128130
'-nostdlib',
129131
'-nostartfiles',
130132
'-static',
131-
'-march=rv32e',
133+
f'-march={arch}',
132134
'-mabi=ilp32e',
133135
'-mno-relax',
134136
'-mlittle-endian',

tests/simple/rem.S

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#include "simple.h"
2+
3+
START_TEXT
4+
// Test basic signed remainder operations
5+
li x1, 17 // x1 = 17
6+
li x2, 5 // x2 = 5
7+
rem x3, x1, x2 // x3 = 17 % 5 = 2
8+
9+
// Test negative dividend
10+
li x4, -17 // x4 = -17
11+
rem x5, x4, x2 // x5 = -17 % 5 = -2
12+
13+
// Test negative divisor
14+
li x6, -5 // x6 = -5
15+
rem x7, x1, x6 // x7 = 17 % -5 = 2
16+
17+
// Test both negative
18+
rem x8, x4, x6 // x8 = -17 % -5 = -2
19+
20+
// Test division by zero (should return dividend)
21+
li x9, 0 // x9 = 0
22+
rem x10, x1, x9 // x10 = 17 % 0 = 17
23+
24+
// Test overflow case (most negative number % -1)
25+
li x11, 0x80000000 // x11 = most negative number
26+
li x12, -1 // x12 = -1
27+
rem x13, x11, x12 // x13 = most negative % -1 = 0
28+
END_TEXT

tests/simple/rem.S.assert

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
regs: {3: 2, 5: -2, 7: 2, 8: -2, 10: 17, 13: 0}

tests/simple/remu.S

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#include "simple.h"
2+
3+
START_TEXT
4+
// Test basic unsigned remainder operations
5+
li x1, 17 // x1 = 17
6+
li x2, 5 // x2 = 5
7+
remu x3, x1, x2 // x3 = 17 % 5 = 2
8+
9+
// Test large unsigned values
10+
li x4, 0xFFFFFFFF // x4 = 0xFFFFFFFF (large unsigned)
11+
li x5, 10 // x5 = 10
12+
remu x6, x4, x5 // x6 = 0xFFFFFFFF % 10 = 5
13+
14+
// Test division by zero (should return dividend)
15+
li x7, 0 // x7 = 0
16+
remu x8, x1, x7 // x8 = 17 % 0 = 17
17+
18+
// Test large divisor
19+
li x9, 0x80000000 // x9 = 0x80000000
20+
li x10, 0x40000000 // x10 = 0x40000000
21+
remu x11, x9, x10 // x11 = 0x80000000 % 0x40000000 = 0
22+
23+
// Test remainder equals dividend
24+
li x12, 100 // x12 = 100
25+
li x13, 200 // x13 = 200
26+
remu x14, x12, x13 // x14 = 100 % 200 = 100
27+
END_TEXT

tests/simple/remu.S.assert

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
regs: {3: 2, 6: 5, 8: 17, 11: 0, 14: 100}

0 commit comments

Comments
 (0)