init upload

This commit is contained in:
Luxferre
2026-06-18 14:01:36 +03:00
commit 1783473a6c
6 changed files with 1190 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
CC = riscv64-linux-gnu-gcc
OBJCOPY = riscv64-linux-gnu-objcopy
EMULATOR = ./amach
ASFLAGS = -march=rv32ima -mabi=ilp32 -nostdlib -static -fno-pic -fno-pie -mno-relax
LDFLAGS = -Wl,-Ttext=0x100b0 -Wl,--build-id=none
all: rv32ima_tests.bin
rv32ima_tests.elf: rv32ima_tests.s
$(CC) $(ASFLAGS) $(LDFLAGS) rv32ima_tests.s -o rv32ima_tests.elf
rv32ima_tests.bin: rv32ima_tests.elf
$(OBJCOPY) -O binary rv32ima_tests.elf rv32ima_tests.bin
run: rv32ima_tests.bin
$(EMULATOR) rv32ima_tests.bin
clean:
rm -f rv32ima_tests.elf rv32ima_tests.bin
.PHONY: all run clean
+66
View File
@@ -0,0 +1,66 @@
# A-machine: a minimal RISC-V (RV32IMA) emulator in POSIX AWK
## About
A-machine is a simple and straightforward implementation of the RV32I specification with M (multiplication/division) and A (atomic operations) extenstions. It consists of two files:
- [amach.awk](amach.awk), the emulator core itself that supports .dec files,
- and [amach](amach) shell script which is a two-liner that uses POSIX `od` to convert .bin files into .dec files on the fly and then passes them to the emulator core.
Additionally, A-machine is shipped with a reference test suite that assumes that you have a full RISC-V toolchain installed under the `riscv64-linux-gnu-` prefix (adjustable in the `Makefile`). Just run `make run` to build and run all tests. In case you don't have any toolchain available, a file called `rv32ima_tests.bin` is also included into the repo.
The project is to be considered highly experimental and not production-ready in any way.
## Usage
First, decide where your program needs to be loaded in memory. The default LVA (loading virtual address) is 0x100b0. In most AWK implementations, you need to convert this value to decimal (the default is 65712).
Then, most probably, you'll want to use the provided wrapper:
```sh
[LVA=...] ./amach path/to/program.bin
```
Or you can run the following:
```sh
od -An -v -tu1 path/to/program.bin | POSIXLY_CORRECT=1 awk -f amach.awk [-v LVA=...]
```
But in case you already have a program in the `.dec` format, you can pass it directly to AWK:
```sh
POSIXLY_CORRECT=1 awk -f amach.awk [-v LVA=...] -- path/to/program.dec
```
## Support
Currently, A-machine only supports the **unprivileged** RV32IMA instruction set with a few additional `ecall` syscalls: 63 (sys_read, for fd 0 only), 64 (sys_write, for fd 1 and 2 only) and 93 (sys_exit). For completeness, `fence` instruction is also supported but yields a no-op.
## Plans (from higher to lower priority)
- Implement the C-extension (compressed instruction set).
- Implement a simple RV32IMAC disassembler in POSIX AWK.
- Introduce more popular Linux-compatible syscalls (that make sense to implement in AWK but don't increase the overall project complexity).
- Test for RV32EMC compatibility (that the code built for the "embedded" variant runs here).
- Implement a privileged instruction set minimum (Zicsr), interrupts and traps.
- If time permits, implement a simple RV32IMAC assembler in POSIX AWK, with full pseudo-instruction support.
## FAQ
### What was the rationale of creating a platform emulator in the least suitable language for this purpose?
The main reason is maximum portability. While AWK is clearly tailored for text processing and, in its standard variant, doesn't even has bitwise operations and hexadecimal literals support, it still remains the single most common scripting language present on every POSIX-compatible system, including embedded ones that run [BusyBox](https://busybox.net/) on top of the kernel and might not have anything else from available programming tools at all. In such conditions, an emulation layer written in AWK is far from efficient but still better than nothing.
Apart from that, the A-machine serves more of an educational purpose for the author to learn about the RISC-V ISA and the details of its inner workings.
### Will I be able to run a Linux kernel on A-machine?
While A-Machine creation was partially inspired by the wonderful [mini-rv32ima project](https://github.com/cnlohr/mini-rv32ima) (although not a single line of code had been taken from there), running a (no-MMU) Linux kernel here is not currently possible due to several reasons. First, A-machine needs to support Zicsr and other privileged RV32 extensions to even theoretically be capable of running the kernel. Second, in order to run the same kernel build as mini-rv32ima does, A-machine will need to support the exact same MMIO model and non-standard CSR calls as mini-rv32ima, which already is non-trivial and will indeed require some logic copy-pasting, which the author is trying to avoid.
So, the answer is: no, not anytime soon.
### 64 bits, floats, doubles?
Not in this project. As already mentioned, A-machine, as well as all of its supplementary artifacts, is more of educational value. Stay tuned for the next RISC-V-related emulation projects for something more practical, fast and powerful.
## Credits
Created by Luxferre in 2026, released into the public domain with no warranties.
Executable
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
# A-Machine .bin launcher wrapper
[ -z "$LVA" ] && LVA=65712 # 0x100b0
od -An -v -tu1 "$1" | POSIXLY_CORRECT=1 awk -f amach.awk -v LVA="$LVA"
+357
View File
@@ -0,0 +1,357 @@
#!/usr/bin/env awk -f
# A-Machine: an experimental RISC-V (RV32IMA) emulator in POSIX AWK
# with a very small subset of supported ECALLs
# Accepts a headerless binary previously converted to .dec format
# e.g. with POSIX od: od -An -v -tu1 program.bin > program.dec
# Usage: awk -f amach.awk [-v LVA=...] -- prog.dec
# Or run a .bin directly with:
# od -An -v -tu1 program.bin | awk -f amach.awk [-v LVA=...]
# Created by Luxferre in 2026, released into the public domain
# fatal error reporting function
function trapout(msg) {
cmd = "cat 1>&2"
printf("Fatal: %s\n", msg) | cmd
close(cmd)
exit(1)
}
# helper functions
function setreg(idx, val) {
if(idx > 0) {
val = int(val) % 4294967296
if(val < 0) val += 4294967296
REG[idx] = (val >= 2147483648) ? (val - 4294967296) : val
}
}
function getreg(idx) {return (idx == 0) ? 0 : int(REG[idx])}
function imm_sign_ex(val) {val = int(val); return (val >= 2048) ? (val - 4096) : val}
function imm_sign_ex_b(val) {val = int(val); return (val >= 4096) ? (val - 8192) : val}
function floor(x, i) {
i = int(x)
return (x >= 0 || x == i) ? i : (i - 1)
}
function ord(c, b) {
if(!TGL_ORD["#"]) for(b=0;b<256;b++) TGL_ORD[sprintf("%c", b)] = b
return int(TGL_ORD[c])
}
function bw_and(a, b, v, r) {
v = 1; r = 0
a = int(a)
b = int(b)
if(a < 0) a += 4294967296
if(b < 0) b += 4294967296
while(a > 0 || b > 0) {
if((a%2) == 1 && (b%2) == 1) r += v
a = int(a/2)
b = int(b/2)
v *= 2
}
return int(r)
}
function bw_or(a, b, v, r) {
v = 1; r = 0
a = int(a)
b = int(b)
if(a < 0) a += 4294967296
if(b < 0) b += 4294967296
while(a > 0 || b > 0) {
if((a%2) == 1 || (b%2) == 1) r += v
a = int(a/2)
b = int(b/2)
v *= 2
}
return int(r)
}
function bw_xor(a, b, v, r) {
v = 1; r = 0
a = int(a)
b = int(b)
if(a < 0) a += 4294967296
if(b < 0) b += 4294967296
while(a > 0 || b > 0) {
if((a%2) != (b%2)) r += v
a = int(a/2)
b = int(b/2)
v *= 2
}
return int(r)
}
function read_word(addr, val) {
addr = int(addr) % 4294967296
if(addr < 0) addr += 4294967296
val = MEM[addr] + MEM[(addr+1)%4294967296]*256 + MEM[(addr+2)%4294967296]*65536 + MEM[(addr+3)%4294967296]*16777216
return (val >= 2147483648) ? (val - 4294967296) : val
}
function write_word(addr, val) {
addr = int(addr) % 4294967296
if(addr < 0) addr += 4294967296
val = int(val) % 4294967296
if(val < 0) val += 4294967296
MEM[addr] = val % 256
MEM[(addr+1)%4294967296] = int(val/256) % 256
MEM[(addr+2)%4294967296] = int(val/65536) % 256
MEM[(addr+3)%4294967296] = int(val/16777216) % 256
}
# syscall emulation
function handle_ecall(callnum, a0, a1, a2, a3, a4, a5, i, l, buf, addr) {
if(callnum == 63) { # sys_read
if(a0 == 0) {
getline buf < "/dev/tty"
l = length(buf)
if(l > a2) l = a2
for(i=0;i<l;i++) {
addr = (a1 + i) % 4294967296
MEM[addr] = ord(substr(buf, i+1, 1))
}
setreg(10, i)
}
} else if(callnum == 64) { # sys_write
if(a0 == 1 || a0 == 2) {
for(i=0;i<a2;i++) {
addr = (a1 + i) % 4294967296
buf = int(MEM[addr])
printf("%c", buf)
}
setreg(10, i)
}
} else if(callnum == 93) exit(a0) # sys_exit
else trapout(sprintf("Unimplemented environment call %d at 0x%X", callnum, pc-4))
}
# instruction type executors
function amach_reg_arith(f3, f7, rd, rs1, rs2, r1, r2, ur1, ur2, shamt) {
r1 = getreg(rs1)
r2 = getreg(rs2)
ur1 = (r1 >= 0) ? r1 : (r1 + 4294967296)
ur2 = (r2 >= 0) ? r2 : (r2 + 4294967296)
shamt = ur2 % 32
if(f3 == 0 && f7 == 0) setreg(rd, r1 + r2)
else if(f3 == 0 && f7 == 32) setreg(rd, r1 - r2)
else if(f3 == 4 && f7 == 0) setreg(rd, bw_xor(r1, r2))
else if(f3 == 6 && f7 == 0) setreg(rd, bw_or(r1, r2))
else if(f3 == 7 && f7 == 0) setreg(rd, bw_and(r1, r2))
else if(f3 == 1 && f7 == 0) setreg(rd, (r1 * (2^shamt)) % 4294967296)
else if(f3 == 5 && f7 == 0) setreg(rd, int(ur1 / (2^shamt)))
else if(f3 == 5 && f7 == 32) setreg(rd, floor(r1 / (2^shamt)))
else if(f3 == 2 && f7 == 0) setreg(rd, (r1 < r2) ? 1 : 0)
else if(f3 == 3 && f7 == 0) setreg(rd, (ur1 < ur2) ? 1 : 0)
else if(f3 == 0 && f7 == 1) setreg(rd, (r1 * r2) % 4294967296)
else if(f3 == 1 && f7 == 1) setreg(rd, floor((r1 * r2) / 4294967296))
else if(f3 == 2 && f7 == 1) setreg(rd, floor((r1 * ur2) / 4294967296))
else if(f3 == 3 && f7 == 1) setreg(rd, floor((ur1 * ur2) / 4294967296))
else if(f3 == 4 && f7 == 1) setreg(rd, (r2 == 0) ? -1 : int(r1 / r2))
else if(f3 == 5 && f7 == 1) setreg(rd, (r2 == 0) ? 4294967295 : int(ur1 / ur2))
else if(f3 == 6 && f7 == 1) setreg(rd, (r2 == 0) ? r1 : (r1 % r2))
else if(f3 == 7 && f7 == 1) setreg(rd, (r2 == 0) ? r1 : (ur1 % ur2))
else trapout(sprintf("Illegal instruction at 0x%X", pc-4))
}
function amach_store(f3, rs1, rs2, immval, r1, r2, base) {
r1 = getreg(rs1)
r2 = getreg(rs2)
if(r2 < 0) r2 += 4294967296
base = (r1 + immval) % 4294967296
if(base < 0) base += 4294967296
if(f3 == 0) MEM[base] = r2 % 256
else if(f3 == 1) {
MEM[base] = r2 % 256
MEM[(base + 1)%4294967296] = int(r2/256) % 256
}
else if(f3 == 2) {
MEM[base] = r2 % 256
MEM[(base + 1)%4294967296] = int(r2/256) % 256
MEM[(base + 2)%4294967296] = int(r2/65536) % 256
MEM[(base + 3)%4294967296] = int(r2/16777216) % 256
}
else trapout(sprintf("Illegal instruction at 0x%X", pc-4))
}
function amach_branch(f3, rs1, rs2, immval, r1, r2, ur1, ur2, taken) {
r1 = getreg(rs1)
r2 = getreg(rs2)
ur1 = (r1 >= 0) ? r1 : (r1 + 4294967296)
ur2 = (r2 >= 0) ? r2 : (r2 + 4294967296)
taken = 0
if(f3 == 0) taken = (r1 == r2)
else if(f3 == 1) taken = (r1 != r2)
else if(f3 == 4) taken = (r1 < r2)
else if(f3 == 5) taken = (r1 >= r2)
else if(f3 == 6) taken = (ur1 < ur2)
else if(f3 == 7) taken = (ur1 >= ur2)
else trapout(sprintf("Illegal instruction at 0x%X", pc-4))
if(taken) pc += immval
else pc += 4
}
function amach_imm(opcode, f3, rd, rs1, immval, r1, ur1, base, shamt, val) {
r1 = getreg(rs1)
ur1 = (r1 >= 0) ? r1 : (r1 + 4294967296)
if(opcode == 3) { # load
base = (r1 + immval) % 4294967296
if(base < 0) base += 4294967296
if(f3 == 0) { # lb
val = MEM[base]
setreg(rd, (val >= 128) ? (val - 256) : val)
}
else if(f3 == 1) { # lh
val = MEM[base] + MEM[(base+1)%4294967296]*256
setreg(rd, (val >= 32768) ? (val - 65536) : val)
}
else if(f3 == 2) {
val = MEM[base] + MEM[(base+1)%4294967296]*256 + MEM[(base+2)%4294967296]*65536 + MEM[(base+3)%4294967296]*16777216
setreg(rd, val)
}
else if(f3 == 4) setreg(rd, MEM[base]) # lbu
else if(f3 == 5) setreg(rd, MEM[base] + MEM[(base+1)%4294967296]*256) # lhu
else trapout(sprintf("Illegal instruction at 0x%X", pc-4))
} else if(opcode == 19) { # 0x13, immediate arithmetic
shamt = bw_and(immval, 31)
if(f3 == 0) setreg(rd, r1 + immval)
else if(f3 == 4) setreg(rd, bw_xor(r1, immval))
else if(f3 == 6) setreg(rd, bw_or(r1, immval))
else if(f3 == 7) setreg(rd, bw_and(r1, immval))
else if(f3 == 1) setreg(rd, (r1 * (2^shamt)) % 4294967296) # slli
else if(f3 == 5 && immval < 1024) setreg(rd, int(ur1 / (2^shamt))) # srli
else if(f3 == 5 && immval >= 1024) setreg(rd, floor(r1 / (2^shamt))) # srai
else if(f3 == 2) setreg(rd, (r1 < immval) ? 1 : 0)
else if(f3 == 3) setreg(rd, (ur1 < immval) ? 1 : 0)
else trapout(sprintf("Illegal instruction at 0x%X", pc-4))
} else if(opcode == 103) { # 0x67, JALR
setreg(rd, pc)
pc = r1 + immval
} else if(opcode == 115) { # 0x73, system call / csr
if(immval == 0) handle_ecall(getreg(17), getreg(10), getreg(11), getreg(12), getreg(13), getreg(14), getreg(15))
else trapout(sprintf("Unimplemented external system call at 0x%X", pc-4))
} else trapout(sprintf("Illegal instruction at 0x%X", pc-4))
}
# A-extension
function amach_atomic(f3, f5, aq, rl, rd, rs1, rs2, r1, r2, val, uval, ur2) {
r1 = getreg(rs1)
r2 = getreg(rs2)
ur2 = (r2 >= 0) ? r2 : (r2 + 4294967296)
if(f3 == 2) {
val = read_word(r1)
uval = (val >= 0) ? val : (val + 4294967296)
if(f5 == 0) { # amoadd.w
setreg(rd, val)
write_word(r1, val + r2)
} else if(f5 == 1) { # amoswap.w
setreg(rd, val)
write_word(r1, r2)
} else if(f5 == 2) { # lr.w
setreg(rd, val)
RES_ADDR = r1
} else if(f5 == 3) { # sc.w
if(RES_ADDR == r1) {
write_word(r1, r2)
setreg(rd, 0)
RES_ADDR = -1
} else {
setreg(rd, 1)
}
} else if(f5 == 4) { # amoxor.w
setreg(rd, val)
write_word(r1, bw_xor(val, r2))
} else if(f5 == 8) { # amoor.w
setreg(rd, val)
write_word(r1, bw_or(val, r2))
} else if(f5 == 12) { # amoand.w
setreg(rd, val)
write_word(r1, bw_and(val, r2))
} else if(f5 == 16) { # amomin.w
setreg(rd, val)
write_word(r1, val <= r2 ? val : r2)
} else if(f5 == 20) { # amomax.w
setreg(rd, val)
write_word(r1, val >= r2 ? val : r2)
} else if(f5 == 24) { # amominu.w
setreg(rd, val)
write_word(r1, uval <= ur2 ? val : r2)
} else if(f5 == 28) { # amomaxu.w
setreg(rd, val)
write_word(r1, uval >= ur2 ? val : r2)
} else trapout(sprintf("Unimplemented atomic instruction at 0x%X", pc-4))
} else trapout(sprintf("Illegal atomic instruction at 0x%X", pc-4))
}
# main instruction decoding and execution routine
function amach_exec(instr, opcode, rd, rs1, rs2, imm, funct3, funct7) {
opcode = instr % 128
instr = int(instr / 128)
rd = instr % 32
imm = int(instr / 32)
funct3 = imm % 8
rs1 = int(imm / 8) % 32
rs2 = int(imm / 256) % 32
funct7 = int(imm / 8192)
if(opcode == 51) { # 0x33, register arithmetic, type R
amach_reg_arith(funct3, funct7, rd, rs1, rs2)
} else if(opcode == 47) { # 0x2f, atomic operations, type R
amach_atomic(funct3, int(funct7 / 4), int(funct7 / 2) % 2, funct7 % 2, rd, rs1, rs2)
} else if(opcode == 3 || opcode == 19 || opcode == 103 || opcode == 115) { # type I
amach_imm(opcode, funct3, rd, rs1, imm_sign_ex(rs2 + funct7 * 32))
} else if(opcode == 35) { # 0x23, store, type S
amach_store(funct3, rs1, rs2, imm_sign_ex(rd + funct7 * 32))
} else if(opcode == 99) { # 0x63, branch, type B
imm = rd + (funct7 % 64) * 32 + (rd % 2) * 2047 + int(funct7 / 64) * 4096
pc -= 4
amach_branch(funct3, rs1, rs2, imm_sign_ex_b(imm))
} else if(opcode == 111) { # 0x6F, JAL, type J
setreg(rd, pc)
imm_10_1 = int(rs2 / 2) + (funct7 % 64) * 16
imm_19_12 = funct3 + rs1 * 8
imm = imm_10_1 * 2 + (rs2 % 2) * 2048 + imm_19_12 * 4096 + int(funct7 / 64) * 1048576
if(imm >= 1048576) imm -= 2097152
pc += imm - 4
} else if(opcode == 55) { # 0x37, LUI, type U
setreg(rd, imm * 4096)
} else if(opcode == 23) { # 0x17, AUIPC, type U
setreg(rd, imm * 4096 + pc - 4)
} else if(opcode == 15) { # 0x0F, FENCE, type I / no-op
# no-op
} else trapout(sprintf("Illegal instruction at 0x%X", pc-4))
}
# Initialization section
BEGIN {
split("", MEM) # init the memory array
split("", REG) # init the (integer) registers
REG[0] = 0 # zero register
REG[2] = 2^31 # stack pointer top
RES_ADDR = -1 # reserved address for atomic ops
LVA = int(LVA)
pc = LVA
}
# Decimal memory collection section
/^[[:space:]]*[[:digit:][:space:]]+/ {for(i=1; i<=NF; i++) MEM[pc++] = int($i)}
# Execution section
END {
memsize = pc - LVA
pc = LVA
while(pc > -1) {
instr = MEM[pc++] + 256 * MEM[pc++]
if((instr % 4) == 3) # full instruction
instr += 65536 * MEM[pc++] + 16777216 * MEM[pc++]
amach_exec(instr) # decode and execute
}
}
BIN
View File
Binary file not shown.
+741
View File
@@ -0,0 +1,741 @@
.section .text
.global _start
.macro INC_TESTS
la s10, test_count
lw s11, 0(s10)
addi s11, s11, 1
sw s11, 0(s10)
.endm
.macro ASSERT_EQ reg, expected, msg
li s10, \expected
mv s11, \reg
beq s10, s11, .L_success_\@
# Failure
la a0, .L_msg_\@
mv a1, s11
li a2, \expected
jal report_failure
j .L_end_\@
.L_success_\@:
# Success
la s10, success_count
lw s11, 0(s10)
addi s11, s11, 1
sw s11, 0(s10)
.L_end_\@:
.section .rodata
.L_msg_\@: .string "\msg"
.section .text
.endm
.macro ASSERT_EQ_REG reg, expected_reg, msg
mv s10, \expected_reg
mv s11, \reg
beq s10, s11, .L_success_reg_\@
# Failure
la a0, .L_msg_reg_\@
mv a1, s11
mv a2, s10
jal report_failure
j .L_end_reg_\@
.L_success_reg_\@:
# Success
la s10, success_count
lw s11, 0(s10)
addi s11, s11, 1
sw s11, 0(s10)
.L_end_reg_\@:
.section .rodata
.L_msg_reg_\@: .string "\msg"
.section .text
.endm
.macro TEST_VAL_RI inst, val1, imm, expected, name
INC_TESTS
li t1, \val1
\inst t0, t1, \imm
ASSERT_EQ t0, \expected, \name
.endm
.macro TEST_VAL_RR inst, val1, val2, expected, name
INC_TESTS
li t1, \val1
li t2, \val2
\inst t0, t1, t2
ASSERT_EQ t0, \expected, \name
.endm
_start:
# ==========================================
# RV32I Immediate Arithmetic
# ==========================================
TEST_VAL_RI addi, 10, 20, 30, addi_pos
TEST_VAL_RI addi, 10, -20, -10, addi_neg
TEST_VAL_RI addi, 0x7fffffff, 1, 0x80000000, addi_overflow
TEST_VAL_RI slti, 10, 20, 1, slti_lt
TEST_VAL_RI slti, 20, 10, 0, slti_gt
TEST_VAL_RI slti, -10, 10, 1, slti_neg_lt
TEST_VAL_RI sltiu, 10, 20, 1, sltiu_lt
TEST_VAL_RI sltiu, -10, 10, 0, sltiu_neg_gt
TEST_VAL_RI xori, 0xf0f0f0f0, 0x0ff, 0xf0f0f00f, xori_val
TEST_VAL_RI ori, 0xf0f0f0f0, 0x0ff, 0xf0f0f0ff, ori_val
TEST_VAL_RI andi, 0xf0f0f0f0, 0x0ff, 0x000000f0, andi_val
TEST_VAL_RI slli, 1, 5, 32, slli_val
TEST_VAL_RI slli, 0x80000000, 1, 0, slli_overflow
TEST_VAL_RI srli, 32, 5, 1, srli_val
TEST_VAL_RI srli, 0x80000000, 1, 0x40000000, srli_sign
TEST_VAL_RI srai, 32, 5, 1, srai_pos
TEST_VAL_RI srai, 0x80000000, 1, 0xc0000000, srai_neg
# ==========================================
# RV32I Register-Register Arithmetic
# ==========================================
TEST_VAL_RR add, 10, 20, 30, add_pos
TEST_VAL_RR add, 0x7fffffff, 1, 0x80000000, add_overflow
TEST_VAL_RR sub, 20, 10, 10, sub_pos
TEST_VAL_RR sub, 10, 20, -10, sub_neg
TEST_VAL_RR sll, 1, 5, 32, sll_val
TEST_VAL_RR sll, 1, 37, 32, sll_wrap
TEST_VAL_RR slt, 10, 20, 1, slt_lt
TEST_VAL_RR slt, -10, 10, 1, slt_neg
TEST_VAL_RR sltu, 10, 20, 1, sltu_lt
TEST_VAL_RR sltu, -10, 10, 0, sltu_neg
TEST_VAL_RR xor, 0xf0f0f0f0, 0x0f0f0f0f, 0xffffffff, xor_val
TEST_VAL_RR srl, 32, 5, 1, srl_val
TEST_VAL_RR srl, 32, 37, 1, srl_wrap
TEST_VAL_RR sra, 0x80000000, 1, 0xc0000000, sra_neg
TEST_VAL_RR sra, 0x80000000, 33, 0xc0000000, sra_wrap
TEST_VAL_RR or, 0xf0f0f0f0, 0x0f0f0f0f, 0xffffffff, or_val
TEST_VAL_RR and, 0xf0f0f0f0, 0x0f0f0f0f, 0, and_val
# ==========================================
# RV32I LUI and AUIPC
# ==========================================
INC_TESTS
lui t0, 0x12345
ASSERT_EQ t0, 0x12345000, lui_val
INC_TESTS
1: auipc t0, 0x10
la t1, 1b
sub t0, t0, t1
ASSERT_EQ t0, 0x10000, auipc_val
# ==========================================
# RV32I Jumps
# ==========================================
INC_TESTS
jal t0, 2f
li t1, 0
j 3f
2:
li t1, 1
3:
ASSERT_EQ t1, 1, jal_branch
INC_TESTS
la t2, 2b - 8
ASSERT_EQ_REG t0, t2, jal_link
INC_TESTS
la t1, 4f
jalr t0, t1, 0
li t2, 0
j 5f
4:
li t2, 1
5:
ASSERT_EQ t2, 1, jalr_branch
INC_TESTS
la t3, 4b - 8
ASSERT_EQ_REG t0, t3, jalr_link
INC_TESTS
la t1, 6f - 4
jalr t0, t1, 4
li t2, 0
j 7f
6:
li t2, 1
7:
ASSERT_EQ t2, 1, jalr_offset
# ==========================================
# RV32I Branches
# ==========================================
# BEQ
INC_TESTS
li t1, 10
li t2, 10
beq t1, t2, 1f
li t0, 0
j 2f
1:
li t0, 1
2:
ASSERT_EQ t0, 1, beq_taken
INC_TESTS
li t1, 10
li t2, 20
beq t1, t2, 1f
li t0, 1
j 2f
1:
li t0, 0
2:
ASSERT_EQ t0, 1, beq_not_taken
# BNE
INC_TESTS
li t1, 10
li t2, 20
bne t1, t2, 1f
li t0, 0
j 2f
1:
li t0, 1
2:
ASSERT_EQ t0, 1, bne_taken
INC_TESTS
li t1, 10
li t2, 10
bne t1, t2, 1f
li t0, 1
j 2f
1:
li t0, 0
2:
ASSERT_EQ t0, 1, bne_not_taken
# BLT
INC_TESTS
li t1, -10
li t2, 10
blt t1, t2, 1f
li t0, 0
j 2f
1:
li t0, 1
2:
ASSERT_EQ t0, 1, blt_taken
INC_TESTS
li t1, 10
li t2, -10
blt t1, t2, 1f
li t0, 1
j 2f
1:
li t0, 0
2:
ASSERT_EQ t0, 1, blt_not_taken
# BGE
INC_TESTS
li t1, 10
li t2, -10
bge t1, t2, 1f
li t0, 0
j 2f
1:
li t0, 1
2:
ASSERT_EQ t0, 1, bge_taken
INC_TESTS
li t1, -10
li t2, 10
bge t1, t2, 1f
li t0, 1
j 2f
1:
li t0, 0
2:
ASSERT_EQ t0, 1, bge_not_taken
# BLTU
INC_TESTS
li t1, 10
li t2, -10
bltu t1, t2, 1f
li t0, 0
j 2f
1:
li t0, 1
2:
ASSERT_EQ t0, 1, bltu_taken
INC_TESTS
li t1, -10
li t2, 10
bltu t1, t2, 1f
li t0, 1
j 2f
1:
li t0, 0
2:
ASSERT_EQ t0, 1, bltu_not_taken
# BGEU
INC_TESTS
li t1, -10
li t2, 10
bgeu t1, t2, 1f
li t0, 0
j 2f
1:
li t0, 1
2:
ASSERT_EQ t0, 1, bgeu_taken
INC_TESTS
li t1, 10
li t2, -10
bgeu t1, t2, 1f
li t0, 1
j 2f
1:
li t0, 0
2:
ASSERT_EQ t0, 1, bgeu_not_taken
# ==========================================
# RV32I Loads and Stores
# ==========================================
# Store word and load word/half/byte
la t1, data_buffer
li t2, 0x12345678
sw t2, 0(t1)
INC_TESTS
lw t0, 0(t1)
ASSERT_EQ t0, 0x12345678, load_word
INC_TESTS
lh t0, 0(t1)
ASSERT_EQ t0, 0x5678, load_half
INC_TESTS
lhu t0, 0(t1)
ASSERT_EQ t0, 0x5678, load_half_u
INC_TESTS
lb t0, 0(t1)
ASSERT_EQ t0, 0x78, load_byte
INC_TESTS
lbu t0, 0(t1)
ASSERT_EQ t0, 0x78, load_byte_u
# Store half and byte with sign extension verification
li t2, 0x8000
sh t2, 0(t1)
INC_TESTS
lh t0, 0(t1)
ASSERT_EQ t0, -32768, load_half_neg
INC_TESTS
lhu t0, 0(t1)
ASSERT_EQ t0, 32768, load_half_neg_u
li t2, 0x80
sb t2, 0(t1)
INC_TESTS
lb t0, 0(t1)
ASSERT_EQ t0, -128, load_byte_neg
INC_TESTS
lbu t0, 0(t1)
ASSERT_EQ t0, 128, load_byte_neg_u
# ==========================================
# RV32M Extensions
# ==========================================
TEST_VAL_RR mul, 10, 20, 200, mul_pos
TEST_VAL_RR mul, -10, 20, -200, mul_neg
TEST_VAL_RR mul, 0x10000, 0x10000, 0, mul_overflow
TEST_VAL_RR mulh, 0x10000, 0x10000, 1, mulh_pos
TEST_VAL_RR mulh, -10, 20, -1, mulh_neg
TEST_VAL_RR mulhsu, -10, 20, -1, mulhsu_neg
TEST_VAL_RR mulhu, 0x80000000, 2, 1, mulhu_val
TEST_VAL_RR div, 20, 6, 3, div_pos
TEST_VAL_RR div, -20, 6, -3, div_neg
TEST_VAL_RR div, 20, 0, -1, div_by_zero
TEST_VAL_RR div, -2147483648, -1, -2147483648, div_overflow
TEST_VAL_RR divu, 20, 6, 3, divu_pos
TEST_VAL_RR divu, -20, 6, 715827879, divu_neg
TEST_VAL_RR divu, 20, 0, 4294967295, divu_by_zero
TEST_VAL_RR rem, 20, 6, 2, rem_pos
TEST_VAL_RR rem, -20, 6, -2, rem_neg
TEST_VAL_RR rem, 20, 0, 20, rem_by_zero
TEST_VAL_RR rem, -2147483648, -1, 0, rem_overflow
TEST_VAL_RR remu, 20, 6, 2, remu_pos
TEST_VAL_RR remu, -20, 6, 2, remu_neg
TEST_VAL_RR remu, 20, 0, 20, remu_by_zero
# ==========================================
# RV32A Extensions
# ==========================================
# Load-Reserved / Store-Conditional
la t1, atomic_var
li t2, 42
lr.w t0, (t1)
sc.w t3, t2, (t1)
INC_TESTS
ASSERT_EQ t3, 0, sc_success
INC_TESTS
lw t4, 0(t1)
ASSERT_EQ t4, 42, sc_val
# SC without LR (should fail)
li t2, 99
sc.w t3, t2, (t1)
INC_TESTS
ASSERT_EQ t3, 1, sc_fail_no_lr
# AMOSWAP.W
li t2, 10
sw t2, 0(t1)
li t3, 20
amoswap.w t0, t3, (t1)
INC_TESTS
ASSERT_EQ t0, 10, amoswap_old
INC_TESTS
lw t4, 0(t1)
ASSERT_EQ t4, 20, amoswap_new
# AMOADD.W
li t2, 10
sw t2, 0(t1)
li t3, 20
amoadd.w t0, t3, (t1)
INC_TESTS
ASSERT_EQ t0, 10, amoadd_old
INC_TESTS
lw t4, 0(t1)
ASSERT_EQ t4, 30, amoadd_new
# AMOXOR.W
li t2, 0x12345678
sw t2, 0(t1)
li t3, 0x0f0f0f0f
amoxor.w t0, t3, (t1)
INC_TESTS
ASSERT_EQ t0, 0x12345678, amoxor_old
INC_TESTS
lw t4, 0(t1)
ASSERT_EQ t4, 0x1d3b5977, amoxor_new
# AMOAND.W
li t2, 0x12345678
sw t2, 0(t1)
li t3, 0x0f0f0f0f
amoand.w t0, t3, (t1)
INC_TESTS
ASSERT_EQ t0, 0x12345678, amoand_old
INC_TESTS
lw t4, 0(t1)
ASSERT_EQ t4, 0x02040608, amoand_new
# AMOOR.W
li t2, 0x12345678
sw t2, 0(t1)
li t3, 0x0f0f0f0f
amoor.w t0, t3, (t1)
INC_TESTS
ASSERT_EQ t0, 0x12345678, amoor_old
INC_TESTS
lw t4, 0(t1)
ASSERT_EQ t4, 0x1f3f5f7f, amoor_new
# AMOMIN.W
li t2, -10
sw t2, 0(t1)
li t3, 10
amomin.w t0, t3, (t1)
INC_TESTS
ASSERT_EQ t0, -10, amomin_old1
INC_TESTS
lw t4, 0(t1)
ASSERT_EQ t4, -10, amomin_new1
li t2, 10
sw t2, 0(t1)
li t3, -10
amomin.w t0, t3, (t1)
INC_TESTS
ASSERT_EQ t0, 10, amomin_old2
INC_TESTS
lw t4, 0(t1)
ASSERT_EQ t4, -10, amomin_new2
# AMOMAX.W
li t2, -10
sw t2, 0(t1)
li t3, 10
amomax.w t0, t3, (t1)
INC_TESTS
ASSERT_EQ t0, -10, amomax_old
INC_TESTS
lw t4, 0(t1)
ASSERT_EQ t4, 10, amomax_new
# AMOMINU.W
li t2, -10 # 0xfffffff6
sw t2, 0(t1)
li t3, 10
amominu.w t0, t3, (t1)
INC_TESTS
ASSERT_EQ t0, -10, amominu_old
INC_TESTS
lw t4, 0(t1)
ASSERT_EQ t4, 10, amominu_new
# AMOMAXU.W
li t2, -10 # 0xfffffff6
sw t2, 0(t1)
li t3, 10
amomaxu.w t0, t3, (t1)
INC_TESTS
ASSERT_EQ t0, -10, amomaxu_old
INC_TESTS
lw t4, 0(t1)
ASSERT_EQ t4, -10, amomaxu_new
# ==========================================
# RV32I Fence
# ==========================================
fence
# ==========================================
# Report Results
# ==========================================
# Print Summary
la a0, str_summary_prefix
jal print_string
# Print total test count
la t0, test_count
lw a0, 0(t0)
jal print_decimal
la a0, str_summary_tests
jal print_string
# Print passed count
la t0, success_count
lw a0, 0(t0)
jal print_decimal
la a0, str_summary_passed
jal print_string
# Print failed count
la t0, failure_count
lw a0, 0(t0)
jal print_decimal
la a0, str_summary_failed
jal print_string
la t0, failure_count
lw t1, 0(t0)
bnez t1, report_error
la a0, str_all_passed
jal print_string
li a0, 0
li a7, 93
ecall
report_error:
la a0, str_some_failed
jal print_string
li a0, 1
li a7, 93
ecall
report_failure:
addi sp, sp, -32
sw ra, 28(sp)
sw s0, 24(sp)
sw s1, 20(sp)
sw s2, 16(sp)
mv s0, a0
mv s1, a1
mv s2, a2
la a0, str_fail_prefix
jal print_string
mv a0, s0
jal print_string
la a0, str_expected
jal print_string
mv a0, s2
jal print_hex
la a0, str_got
jal print_string
mv a0, s1
jal print_hex
li a0, 10
jal print_char
la t0, failure_count
lw t1, 0(t0)
addi t1, t1, 1
sw t1, 0(t0)
lw ra, 28(sp)
lw s0, 24(sp)
lw s1, 20(sp)
lw s2, 16(sp)
addi sp, sp, 32
ret
print_string:
addi sp, sp, -16
sw ra, 12(sp)
sw s0, 8(sp)
mv s0, a0
print_string_loop:
lbu a0, 0(s0)
beqz a0, print_string_done
jal print_char
addi s0, s0, 1
j print_string_loop
print_string_done:
lw ra, 12(sp)
lw s0, 8(sp)
addi sp, sp, 16
ret
print_char:
addi sp, sp, -16
sb a0, 0(sp)
li a7, 64
li a0, 1
mv a1, sp
li a2, 1
ecall
addi sp, sp, 16
ret
print_hex:
addi sp, sp, -32
sw ra, 28(sp)
sw s0, 24(sp)
sw s1, 20(sp)
sw s2, 16(sp)
mv s0, a0
li s1, 8
la s2, hex_chars
print_hex_loop:
srli t0, s0, 28
andi t0, t0, 0xF
add t1, s2, t0
lbu a0, 0(t1)
jal print_char
slli s0, s0, 4
addi s1, s1, -1
bnez s1, print_hex_loop
lw ra, 28(sp)
lw s0, 24(sp)
lw s1, 20(sp)
lw s2, 16(sp)
addi sp, sp, 32
ret
print_decimal:
addi sp, sp, -32
sw ra, 28(sp)
sw s0, 24(sp)
sw s1, 20(sp)
mv s0, a0
# Handle 0 case
bnez s0, 1f
li a0, 48 # '0'
jal print_char
j print_decimal_done
1:
addi s1, sp, 8
li t2, 10
2:
divu t0, s0, t2
remu t1, s0, t2
addi t1, t1, 48 # convert to ASCII
sb t1, 0(s1)
addi s1, s1, 1
mv s0, t0
bnez s0, 2b
# Now print digits in reverse order
3:
addi s1, s1, -1
lbu a0, 0(s1)
jal print_char
addi t0, sp, 8
bne s1, t0, 3b
print_decimal_done:
lw ra, 28(sp)
lw s0, 24(sp)
lw s1, 20(sp)
addi sp, sp, 32
ret
.section .data
.align 2
test_count: .word 0
success_count: .word 0
failure_count: .word 0
.align 4
data_buffer: .zero 16
atomic_var: .word 0
.section .rodata
hex_chars: .string "0123456789abcdef"
str_fail_prefix: .string " FAIL: "
str_expected: .string ", expected 0x"
str_got: .string ", got 0x"
str_summary_prefix: .string "Executed "
str_summary_tests: .string " tests: "
str_summary_passed: .string " passed, "
str_summary_failed: .string " failed.\n"
str_all_passed: .string "All tests PASSED!\n"
str_some_failed: .string "Some tests FAILED.\n"