Skip to content

Commit efe1f78

Browse files
Stevengrerv-auditortothtamas28automergerpr-permission-manager[bot]
authored
Add division operations for RISC-V semantics (#146)
Implement signed (`/Word`) and unsigned (`/uWord`) division rules in the RISC-V semantics. Update the handling of special cases for division by zero and signed overflow. Refactor test functions to include comprehensive test cases for both division operations, ensuring compliance with RISC-V specifications. - Added rules for `DIV` and `DIVU` instructions in `riscv.md`. - Updated syntax definitions in `word.md` for division operations. - Introduced new test cases for division in `test_functions.py` to validate expected behavior. --------- Co-authored-by: devops <devops@runtimeverification.com> Co-authored-by: Tamás Tóth <tothtamas28@users.noreply.github.com> Co-authored-by: automergerpr-permission-manager[bot] <190534181+automergerpr-permission-manager[bot]@users.noreply.github.com>
1 parent ddf2811 commit efe1f78

6 files changed

Lines changed: 113 additions & 9 deletions

File tree

package/version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.110
1+
0.1.111

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.110"
7+
version = "0.1.111"
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
@@ -250,6 +250,14 @@ The following instructions behave analogously to their `I`-suffixed counterparts
250250
rule <instrs> MULHSU RD , RS1 , RS2 => .K ...</instrs>
251251
<regs> REGS => writeReg(REGS, RD, readReg(REGS, RS1) *hsuWord readReg(REGS, RS2)) </regs>
252252
```
253+
`DIV` and `DIVU` perform signed and unsigned division, respectively.
254+
```k
255+
rule <instrs> DIV RD , RS1 , RS2 => .K ...</instrs>
256+
<regs> REGS => writeReg(REGS, RD, readReg(REGS, RS1) /Word readReg(REGS, RS2)) </regs>
257+
258+
rule <instrs> DIVU RD , RS1 , RS2 => .K ...</instrs>
259+
<regs> REGS => writeReg(REGS, RD, readReg(REGS, RS1) /uWord readReg(REGS, RS2)) </regs>
260+
```
253261
`SLL`, `SRL`, and `SRA` read their shift amount fom the lowest `log_2(XLEN)` bits of `RS2`.
254262
```k
255263
rule <instrs> SLL RD , RS1 , RS2 => .K ...</instrs>

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ To avoid syntax conflicts, we define the following syntax with `left` associativ
6161
| Int "*hWord" Int [function, total, symbol(mulhWord)]
6262
| Int "*huWord" Int [function, total, symbol(mulhuWord)]
6363
| Int "*hsuWord" Int [function, total, symbol(mulhsuWord)]
64+
| Int "/Word" Int [function, total, symbol(divWord)]
65+
| Int "/uWord" Int [function, total, symbol(divuWord)]
6466
> left:
6567
Int "+Word" Int [function, total, symbol(addWord)]
6668
| Int "-Word" Int [function, total, symbol(subWord)]
@@ -75,7 +77,6 @@ To avoid syntax conflicts, we define the following syntax with `left` associativ
7577
> left:
7678
Int "|Word" Int [function, total, symbol(orWord)]
7779
```
78-
7980
Note that two's complement enables us to use a single addition or subtraction operation for both signed and unsigned values.
8081
```k
8182
rule W1 +Word W2 => chop(W1 +Int W2)
@@ -91,6 +92,28 @@ The value of the upper `XLEN` bits however depends on signedness of the operands
9192
rule W1 *huWord W2 => chop((W1 *Int W2) >>Int XLEN)
9293
rule W1 *hsuWord W2 => chop((Word2SInt(W1) *Int W2) >>Int XLEN)
9394
```
95+
Division operations follow RISC-V specification requirements for handling special cases.
96+
97+
For signed division (`/Word`):
98+
- Division by zero returns -1 (all bits set)
99+
- Signed overflow (most negative number divided by -1) returns the dividend unchanged
100+
- Normal case performs signed division with truncation towards zero
101+
```k
102+
rule _ /Word W2 => chop(-1) requires W2 ==Word 0
103+
rule W1 /Word W2 => W1
104+
requires W1 ==Word 2 ^Int (XLEN -Int 1)
105+
andBool W2 ==Word chop(-1)
106+
rule W1 /Word W2 => chop(Word2SInt(W1) /Int Word2SInt(W2))
107+
requires W2 =/=Word 0
108+
andBool notBool (W1 ==Word chop(2 ^Int (XLEN -Int 1)) andBool W2 ==Word chop(-1))
109+
```
110+
For unsigned division (`/uWord`):
111+
- Division by zero returns 2^XLEN - 1 (all bits set)
112+
- Normal case performs unsigned division with truncation towards zero
113+
```k
114+
rule _ /uWord W2 => chop((2 ^Int XLEN) -Int 1) requires W2 ==Word 0
115+
rule W1 /uWord W2 => chop(W1 /Int W2) requires W2 =/=Word 0
116+
```
94117
Above, the `chop` utility function
95118
```k
96119
syntax Int ::= chop(Int) [function, total]

src/tests/integration/test_functions.py

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def signed(x: int) -> int:
109109
assert all(is_32bit(op1) and is_32bit(op2) for op1, op2 in MUL_TEST_DATA)
110110

111111

112-
def _test_mul(
112+
def _test_binary_op(
113113
definition_dir: Path,
114114
symbol: str,
115115
op1: int,
@@ -125,7 +125,7 @@ def _test_mul(
125125

126126
@pytest.mark.parametrize('op1,op2', MUL_TEST_DATA, ids=count())
127127
def test_mul(definition_dir: Path, op1: int, op2: int) -> None:
128-
_test_mul(
128+
_test_binary_op(
129129
definition_dir=definition_dir,
130130
symbol='LblmulWord',
131131
op1=op1,
@@ -136,7 +136,7 @@ def test_mul(definition_dir: Path, op1: int, op2: int) -> None:
136136

137137
@pytest.mark.parametrize('op1,op2', MUL_TEST_DATA, ids=count())
138138
def test_mulh(definition_dir: Path, op1: int, op2: int) -> None:
139-
_test_mul(
139+
_test_binary_op(
140140
definition_dir=definition_dir,
141141
symbol='LblmulhWord',
142142
op1=op1,
@@ -147,7 +147,7 @@ def test_mulh(definition_dir: Path, op1: int, op2: int) -> None:
147147

148148
@pytest.mark.parametrize('op1,op2', MUL_TEST_DATA, ids=count())
149149
def test_mulhu(definition_dir: Path, op1: int, op2: int) -> None:
150-
_test_mul(
150+
_test_binary_op(
151151
definition_dir=definition_dir,
152152
symbol='LblmulhuWord',
153153
op1=op1,
@@ -158,10 +158,83 @@ def test_mulhu(definition_dir: Path, op1: int, op2: int) -> None:
158158

159159
@pytest.mark.parametrize('op1,op2', MUL_TEST_DATA, ids=count())
160160
def test_mulhsu(definition_dir: Path, op1: int, op2: int) -> None:
161-
_test_mul(
161+
_test_binary_op(
162162
definition_dir=definition_dir,
163163
symbol='LblmulhsuWord',
164164
op1=op1,
165165
op2=op2,
166166
res=chop((signed(op1) * op2) >> 32),
167167
)
168+
169+
170+
DIV_TEST_DATA: Final = (
171+
# Normal division cases
172+
(10, 3),
173+
(100, 7),
174+
(0xFFFFFFFF, 1), # -1 / 1 = -1
175+
(0xFFFFFFFE, 2), # -2 / 2 = -1
176+
(0x80000000, 2), # -2^31 / 2 = -2^30
177+
(15, 4),
178+
(0x7FFFFFFF, 3), # 2^31-1 / 3
179+
# Division by zero cases
180+
(0, 0),
181+
(1, 0),
182+
(0xFFFFFFFF, 0), # -1 / 0
183+
(0x80000000, 0), # -2^31 / 0
184+
(0x7FFFFFFF, 0), # 2^31-1 / 0
185+
# Signed overflow case: -2^31 / -1
186+
(0x80000000, 0xFFFFFFFF), # -2^31 / -1 should return -2^31
187+
# Additional edge cases
188+
(0x80000001, 0xFFFFFFFF), # (-2^31+1) / -1 = 2^31-1
189+
(0x7FFFFFFF, 0xFFFFFFFF), # (2^31-1) / -1 = -(2^31-1)
190+
)
191+
192+
193+
assert all(is_32bit(op1) and is_32bit(op2) for op1, op2 in DIV_TEST_DATA)
194+
195+
196+
def div_expected(op1: int, op2: int) -> int:
197+
# Calculate expected result according to RISC-V specification
198+
if op2 == 0:
199+
# Division by zero returns -1 (all bits set)
200+
expected = 0xFFFFFFFF
201+
elif op1 == 0x80000000 and op2 == 0xFFFFFFFF:
202+
# Signed overflow: -2^31 / -1 returns the dividend (-2^31)
203+
expected = 0x80000000
204+
else:
205+
# Normal signed division with truncation towards zero
206+
expected = chop(signed(op1) // signed(op2))
207+
return expected
208+
209+
210+
@pytest.mark.parametrize('op1,op2', DIV_TEST_DATA, ids=count())
211+
def test_div(definition_dir: Path, op1: int, op2: int) -> None:
212+
_test_binary_op(
213+
definition_dir=definition_dir,
214+
symbol='LbldivWord',
215+
op1=op1,
216+
op2=op2,
217+
res=div_expected(op1, op2),
218+
)
219+
220+
221+
def divu_expected(op1: int, op2: int) -> int:
222+
# Calculate expected result according to RISC-V specification
223+
if op2 == 0:
224+
# Division by zero returns 2^32 - 1 (all bits set)
225+
expected = 0xFFFFFFFF
226+
else:
227+
# Normal unsigned division with truncation towards zero
228+
expected = op1 // op2
229+
return expected
230+
231+
232+
@pytest.mark.parametrize('op1,op2', DIV_TEST_DATA, ids=count())
233+
def test_divu(definition_dir: Path, op1: int, op2: int) -> None:
234+
_test_binary_op(
235+
definition_dir=definition_dir,
236+
symbol='LbldivuWord',
237+
op1=op1,
238+
op2=op2,
239+
res=divu_expected(op1, op2),
240+
)

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)