diff --git a/Makefile b/Makefile index 130bb27..c5746db 100644 --- a/Makefile +++ b/Makefile @@ -2,21 +2,21 @@ CC = riscv64-linux-gnu-gcc OBJCOPY = riscv64-linux-gnu-objcopy EMULATOR = ./amach -ASFLAGS = -march=rv32ima -mabi=ilp32 -nostdlib -s -static -fno-pic -fno-pie -mno-relax +ASFLAGS = -march=rv32imac -mabi=ilp32 -nostdlib -s -static -fno-pic -fno-pie -mno-relax LDFLAGS = -Wl,-Ttext=0x100b0 -Wl,--build-id=none -all: rv32ima_tests.bin +all: rv32imac_tests.bin -rv32ima_tests.elf: rv32ima_tests.s - $(CC) $(ASFLAGS) $(LDFLAGS) rv32ima_tests.s -o rv32ima_tests.elf +rv32imac_tests.elf: rv32imac_tests.s + $(CC) $(ASFLAGS) $(LDFLAGS) rv32imac_tests.s -o rv32imac_tests.elf -rv32ima_tests.bin: rv32ima_tests.elf - $(OBJCOPY) -O binary rv32ima_tests.elf rv32ima_tests.bin +rv32imac_tests.bin: rv32imac_tests.elf + $(OBJCOPY) -O binary rv32imac_tests.elf rv32imac_tests.bin -run: rv32ima_tests.bin - $(EMULATOR) rv32ima_tests.bin +run: rv32imac_tests.bin + $(EMULATOR) rv32imac_tests.bin clean: - rm -f rv32ima_tests.elf rv32ima_tests.bin + rm -f rv32imac_tests.elf rv32imac_tests.bin .PHONY: all run clean diff --git a/README.md b/README.md index b18bc1c..359c27b 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -# A-machine: a minimal RISC-V (RV32IMA) emulator in POSIX AWK +# A-machine: a minimal RISC-V (RV32IMAC) 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: +A-machine is a simple and straightforward implementation of the RV32I specification with M (multiplication/division), A (atomic operations), and C (compressed instructions) extensions. 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. +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 `rv32imac_tests.bin` is also included into the repo. The project is to be considered highly experimental and not production-ready in any way. @@ -32,11 +32,10 @@ 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. +Currently, A-machine only supports the **unprivileged** RV32IMAC 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). diff --git a/amach.awk b/amach.awk index 00c66a2..02ecbd9 100644 --- a/amach.awk +++ b/amach.awk @@ -1,5 +1,5 @@ #!/usr/bin/env awk -f -# A-Machine: an experimental RISC-V (RV32IMA) emulator in POSIX AWK +# A-Machine: an experimental RISC-V (RV32IMAC) 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 @@ -275,8 +275,148 @@ function amach_atomic(f3, f5, aq, rl, rd, rs1, rs2, r1, r2, val, uval, ur2) { } else trapout(sprintf("Illegal atomic instruction at 0x%X", pc-4)) } +# C-extension (compressed instructions) handler +function amach_comp(instr, op, f3, b12, r11_7, r9_7, r6_2, r4_2, shamt, imm, base, val) { + op = instr % 4 + f3 = int(instr / 8192) % 8 + b12 = int(instr / 4096) % 2 + r11_7 = int(instr / 128) % 32 + r9_7 = (int(instr / 128) % 8) + 8 + r6_2 = int(instr / 4) % 32 + r4_2 = (int(instr / 4) % 8) + 8 + shamt = r6_2 + b12 * 32 + + if (op == 0) { + if (f3 == 0) { # C.ADDI4SPN + imm = (int(instr / 128) % 16) * 64 + (int(instr / 2048) % 4) * 16 + (int(instr / 32) % 2) * 8 + (int(instr / 64) % 2) * 4 + if (imm == 0) trapout(sprintf("Illegal instruction C.ADDI4SPN at 0x%X", pc-2)) + setreg(r4_2, getreg(2) + imm) + } else if (f3 == 2) { # C.LW + imm = (int(instr / 32) % 2) * 64 + (int(instr / 1024) % 8) * 8 + (int(instr / 64) % 2) * 4 + base = (getreg(r9_7) + imm) % 4294967296 + if (base < 0) base += 4294967296 + val = MEM[base] + MEM[(base+1)%4294967296]*256 + MEM[(base+2)%4294967296]*65536 + MEM[(base+3)%4294967296]*16777216 + setreg(r4_2, val) + } else if (f3 == 6) { # C.SW + imm = (int(instr / 32) % 2) * 64 + (int(instr / 1024) % 8) * 8 + (int(instr / 64) % 2) * 4 + base = (getreg(r9_7) + imm) % 4294967296 + if (base < 0) base += 4294967296 + val = getreg(r4_2) + if (val < 0) val += 4294967296 + MEM[base] = val % 256 + MEM[(base+1)%4294967296] = int(val/256) % 256 + MEM[(base+2)%4294967296] = int(val/65536) % 256 + MEM[(base+3)%4294967296] = int(val/16777216) % 256 + } else trapout(sprintf("Illegal instruction at 0x%X", pc-2)) + } else if (op == 1) { + if (f3 == 0) { # C.NOP / C.ADDI + imm = r6_2 + b12 * 32 + if (imm >= 32) imm -= 64 + setreg(r11_7, getreg(r11_7) + imm) + } else if (f3 == 1 || f3 == 5) { # C.JAL / C.J + imm = b12 * 2048 + (int(instr / 256) % 2) * 1024 + (int(instr / 512) % 4) * 256 + (int(instr / 64) % 2) * 128 + (int(instr / 128) % 2) * 64 + (int(instr / 4) % 2) * 32 + (int(instr / 2048) % 2) * 16 + (int(instr / 8) % 8) * 2 + if (imm >= 2048) imm -= 4096 + if (f3 == 1) setreg(1, pc) + pc = (pc - 2) + imm + } else if (f3 == 2) { # C.LI + imm = r6_2 + b12 * 32 + if (imm >= 32) imm -= 64 + setreg(r11_7, imm) + } else if (f3 == 3) { # C.ADDI16SP / C.LUI + if (r11_7 == 2) { # C.ADDI16SP + imm = b12 * 512 + (int(instr / 8) % 4) * 128 + (int(instr / 32) % 2) * 64 + (int(instr / 4) % 2) * 32 + (int(instr / 64) % 2) * 16 + if (imm >= 512) imm -= 1024 + if (imm == 0) trapout(sprintf("Illegal instruction C.ADDI16SP at 0x%X", pc-2)) + setreg(2, getreg(2) + imm) + } else if (r11_7 != 0) { # C.LUI + imm = r6_2 + b12 * 32 + if (imm >= 32) imm -= 64 + imm = imm * 4096 + if (imm == 0) trapout(sprintf("Illegal instruction C.LUI at 0x%X", pc-2)) + setreg(r11_7, imm) + } + } else if (f3 == 4) { + val = int(instr / 1024) % 4 + if (val == 0) { # C.SRLI + if (shamt >= 32) trapout(sprintf("Illegal shift amount %d at 0x%X", shamt, pc-2)) + imm = getreg(r9_7) + if (imm < 0) imm += 4294967296 + setreg(r9_7, int(imm / (2^shamt))) + } else if (val == 1) { # C.SRAI + if (shamt >= 32) trapout(sprintf("Illegal shift amount %d at 0x%X", shamt, pc-2)) + setreg(r9_7, floor(getreg(r9_7) / (2^shamt))) + } else if (val == 2) { # C.ANDI + imm = r6_2 + b12 * 32 + if (imm >= 32) imm -= 64 + setreg(r9_7, bw_and(getreg(r9_7), imm)) + } else if (val == 3) { # C.SUB, C.XOR, C.OR, C.AND + imm = int(instr / 32) % 4 + if (b12 != 0) trapout(sprintf("Illegal register-register instruction at 0x%X", pc-2)) + if (imm == 0) setreg(r9_7, getreg(r9_7) - getreg(r4_2)) + else if (imm == 1) setreg(r9_7, bw_xor(getreg(r9_7), getreg(r4_2))) + else if (imm == 2) setreg(r9_7, bw_or(getreg(r9_7), getreg(r4_2))) + else if (imm == 3) setreg(r9_7, bw_and(getreg(r9_7), getreg(r4_2))) + } + } else if (f3 == 6 || f3 == 7) { # C.BEQZ / C.BNEZ + imm = b12 * 256 + (int(instr / 32) % 4) * 64 + (int(instr / 4) % 2) * 32 + (int(instr / 1024) % 4) * 8 + (int(instr / 8) % 4) * 2 + if (imm >= 256) imm -= 512 + val = (f3 == 6) ? (getreg(r9_7) == 0) : (getreg(r9_7) != 0) + pc = val ? (pc - 2) + imm : pc + } + } else if (op == 2) { + if (f3 == 0) { # C.SLLI + if (shamt >= 32) trapout(sprintf("Illegal shift amount %d at 0x%X", shamt, pc-2)) + if (r11_7 != 0) { + setreg(r11_7, (getreg(r11_7) * (2^shamt)) % 4294967296) + } + } else if (f3 == 2) { # C.LWSP + if (r11_7 == 0) trapout(sprintf("Illegal instruction C.LWSP with rd=0 at 0x%X", pc-2)) + imm = (int(instr / 4) % 4) * 64 + b12 * 32 + (int(instr / 16) % 8) * 4 + base = (getreg(2) + imm) % 4294967296 + if (base < 0) base += 4294967296 + val = MEM[base] + MEM[(base+1)%4294967296]*256 + MEM[(base+2)%4294967296]*65536 + MEM[(base+3)%4294967296]*16777216 + setreg(r11_7, val) + } else if (f3 == 4) { # C.JR, C.MV, C.JALR, C.ADD + if (b12 == 0) { + if (r6_2 == 0) { # C.JR + if (r11_7 == 0) trapout(sprintf("Illegal instruction C.JR with rs1=0 at 0x%X", pc-2)) + pc = getreg(r11_7) + } else { # C.MV + setreg(r11_7, getreg(r6_2)) + } + } else { + if (r6_2 == 0) { + if (r11_7 == 0) { # C.EBREAK + trapout(sprintf("EBREAK at 0x%X", pc-2)) + } else { # C.JALR + imm = getreg(r11_7) + setreg(1, pc) + pc = imm + } + } else { # C.ADD + setreg(r11_7, getreg(r11_7) + getreg(r6_2)) + } + } + } else if (f3 == 6) { # C.SWSP + imm = (int(instr / 128) % 4) * 64 + (int(instr / 512) % 16) * 4 + base = (getreg(2) + imm) % 4294967296 + if (base < 0) base += 4294967296 + val = getreg(r6_2) + if (val < 0) val += 4294967296 + MEM[base] = val % 256 + MEM[(base+1)%4294967296] = int(val/256) % 256 + MEM[(base+2)%4294967296] = int(val/65536) % 256 + MEM[(base+3)%4294967296] = int(val/16777216) % 256 + } else trapout(sprintf("Illegal instruction at 0x%X", pc-2)) + } else trapout(sprintf("Illegal instruction at 0x%X", pc-2)) +} + # main instruction decoding and execution routine function amach_exec(instr, opcode, rd, rs1, rs2, imm, funct3, funct7) { + if ((instr % 4) != 3) { + amach_comp(instr) + return + } opcode = instr % 128 instr = int(instr / 128) rd = instr % 32 diff --git a/rv32ima_tests.bin b/rv32ima_tests.bin deleted file mode 100755 index e8889b7..0000000 Binary files a/rv32ima_tests.bin and /dev/null differ diff --git a/rv32imac_tests.bin b/rv32imac_tests.bin new file mode 100755 index 0000000..1b576fa Binary files /dev/null and b/rv32imac_tests.bin differ diff --git a/rv32ima_tests.s b/rv32imac_tests.s similarity index 81% rename from rv32ima_tests.s rename to rv32imac_tests.s index 95bca4c..ecd3059 100644 --- a/rv32ima_tests.s +++ b/rv32imac_tests.s @@ -1,4 +1,5 @@ .section .text +.option norvc .global _start .macro INC_TESTS @@ -530,6 +531,229 @@ _start: # ========================================== fence + # ========================================== + # RV32C Compressed Instruction Tests + # ========================================== + .option rvc + + # 1. c.addi4spn + INC_TESTS + mv s0, sp + c.addi4spn a0, sp, 40 + sub a0, a0, s0 + ASSERT_EQ a0, 40, c_addi4spn_val + + # 2. c.lw and c.sw + INC_TESTS + la a1, data_buffer + li a0, 0x76543210 + sw a0, 4(a1) + c.lw a2, 4(a1) + ASSERT_EQ a2, 0x76543210, c_lw_val + + INC_TESTS + li a0, 0x11223344 + c.sw a0, 8(a1) + lw a2, 8(a1) + ASSERT_EQ a2, 0x11223344, c_sw_val + + # 3. c.nop + INC_TESTS + c.nop + li a0, 1 + ASSERT_EQ a0, 1, c_nop_val + + # 4. c.addi + INC_TESTS + li a0, 10 + c.addi a0, -3 + ASSERT_EQ a0, 7, c_addi_val + + # 5. c.jal + INC_TESTS +3: + c.jal 1f + li a0, 0 + j 2f +1: + li a0, 1 +2: + ASSERT_EQ a0, 1, c_jal_val + + INC_TESTS + la a1, 3b + 2 + ASSERT_EQ_REG ra, a1, c_jal_link + + # 6. c.li + INC_TESTS + c.li a0, -15 + ASSERT_EQ a0, -15, c_li_val + + # 7. c.addi16sp + INC_TESTS + mv s0, sp + c.addi16sp sp, -32 + sub a0, s0, sp + ASSERT_EQ a0, 32, c_addi16sp_val + mv sp, s0 + + # 8. c.lui + INC_TESTS + c.lui a0, 15 + ASSERT_EQ a0, 0xf000, c_lui_val + + # 9. c.srli, c.srai, c.andi + INC_TESTS + li a0, 0x80 + c.srli a0, 3 + ASSERT_EQ a0, 0x10, c_srli_val + + INC_TESTS + li a0, -16 + c.srai a0, 2 + ASSERT_EQ a0, -4, c_srai_val + + INC_TESTS + li a0, 15 + c.andi a0, 9 + ASSERT_EQ a0, 9, c_andi_val + + # 10. c.sub, c.xor, c.or, c.and + INC_TESTS + li a0, 15 + li a1, 5 + c.sub a0, a1 + ASSERT_EQ a0, 10, c_sub_val + + INC_TESTS + li a0, 5 + li a1, 3 + c.xor a0, a1 + ASSERT_EQ a0, 6, c_xor_val + + INC_TESTS + li a0, 5 + li a1, 3 + c.or a0, a1 + ASSERT_EQ a0, 7, c_or_val + + INC_TESTS + li a0, 5 + li a1, 3 + c.and a0, a1 + ASSERT_EQ a0, 1, c_and_val + + # 11. c.j + INC_TESTS + c.j 1f + li a0, 0 + j 2f +1: + li a0, 1 +2: + ASSERT_EQ a0, 1, c_j_val + + # 12. c.beqz, c.bnez + INC_TESTS + li a0, 0 + c.beqz a0, 1f + li a1, 0 + j 2f +1: + li a1, 1 +2: + ASSERT_EQ a1, 1, c_beqz_taken + + INC_TESTS + li a0, 1 + c.beqz a0, 1f + li a1, 0 + j 2f +1: + li a1, 1 +2: + ASSERT_EQ a1, 0, c_beqz_not_taken + + INC_TESTS + li a0, 1 + c.bnez a0, 1f + li a1, 0 + j 2f +1: + li a1, 1 +2: + ASSERT_EQ a1, 1, c_bnez_taken + + INC_TESTS + li a0, 0 + c.bnez a0, 1f + li a1, 0 + j 2f +1: + li a1, 1 +2: + ASSERT_EQ a1, 0, c_bnez_not_taken + + # 13. c.slli + INC_TESTS + li a0, 5 + c.slli a0, 2 + ASSERT_EQ a0, 20, c_slli_val + + # 14. c.lwsp, c.swsp + INC_TESTS + mv s0, sp + c.addi16sp sp, -32 + li a0, 0x11223344 + sw a0, 16(sp) + c.lwsp a1, 16(sp) + ASSERT_EQ a1, 0x11223344, c_lwsp_val + + INC_TESTS + li a0, 0x55667788 + c.swsp a0, 20(sp) + lw a1, 20(sp) + ASSERT_EQ a1, 0x55667788, c_swsp_val + mv sp, s0 + + # 15. c.jr, c.mv, c.jalr, c.add + INC_TESTS + la a0, 1f + c.jr a0 + li a1, 0 + j 2f +1: + li a1, 1 +2: + ASSERT_EQ a1, 1, c_jr_val + + INC_TESTS + li a1, 42 + c.mv a0, a1 + ASSERT_EQ a0, 42, c_mv_val + + INC_TESTS + la a0, 1f +3: + c.jalr a0 + li a1, 0 + j 2f +1: + li a1, 1 +2: + ASSERT_EQ a1, 1, c_jalr_val + INC_TESTS + la a2, 3b + 2 + ASSERT_EQ_REG ra, a2, c_jalr_link + + INC_TESTS + li a0, 10 + li a1, 20 + c.add a0, a1 + ASSERT_EQ a0, 30, c_add_val + + .option norvc + # ========================================== # Report Results # ==========================================