summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--assembly/trap-test/Makefile24
l---------assembly/trap-test/rvcontroller.ld1
-rw-r--r--assembly/trap-test/trap-test.S65
-rwxr-xr-xassembly/trap-test/trap-test.elfbin0 -> 5596 bytes
-rw-r--r--assembly/trap-test/trap-test.hex13
-rw-r--r--assembly/trap-test/trap-test.obin0 -> 1708 bytes
-rw-r--r--rvcontroller.lua191
7 files changed, 271 insertions, 23 deletions
diff --git a/assembly/trap-test/Makefile b/assembly/trap-test/Makefile
new file mode 100644
index 0000000..8833502
--- /dev/null
+++ b/assembly/trap-test/Makefile
@@ -0,0 +1,24 @@
+MARCH ?= rv32imacbzicntr_zicond_zicsr_zifencei_zihintpause_zilsd_zclsd_zabha_zacas_zbkb_zbkx_zcb_zcmp_zcmt
+
+.PHONY: all dump load clean
+
+all: trap-test.hex
+
+trap-test.o: trap-test.S
+ riscv32-none-elf-as -I../rvcontroller-libraries -march=${MARCH} -o trap-test.o trap-test.S
+
+trap-test.elf: trap-test.o
+ riscv32-none-elf-ld -T rvcontroller.ld --no-warn-rwx-segments -o trap-test.elf trap-test.o
+
+dump: trap-test.elf
+ riscv32-none-elf-objdump -d trap-test.elf
+
+trap-test.hex: trap-test.elf
+ riscv32-none-elf-objcopy -O ihex trap-test.elf trap-test.hex
+
+load: trap-test.hex
+ bash -c "wl-copy < trap-test.hex"
+
+clean:
+ rm -f trap-test.hex trap-test.elf trap-test.o
+
diff --git a/assembly/trap-test/rvcontroller.ld b/assembly/trap-test/rvcontroller.ld
new file mode 120000
index 0000000..bc01402
--- /dev/null
+++ b/assembly/trap-test/rvcontroller.ld
@@ -0,0 +1 @@
+../../rvcontroller.ld \ No newline at end of file
diff --git a/assembly/trap-test/trap-test.S b/assembly/trap-test/trap-test.S
new file mode 100644
index 0000000..ec928a7
--- /dev/null
+++ b/assembly/trap-test/trap-test.S
@@ -0,0 +1,65 @@
+la t0,handler
+csrw mtvec,t0 # Set trap handler address and mode (0/direct)
+
+li t0,0xdeadbeef # No RAM here
+lw t1,0(t0) # This should give a load access fault, which should be handled
+
+li a7,4 # Print string
+la a0,readvalmsg # "Read value "
+ecall
+
+li a7,1 # Print integer
+mv a0,t1 # Get the value read earlier
+ecall
+
+li a7,11 # Print character
+li a0,'\n'
+ecall
+
+li a7,10 # Exit program
+ecall
+
+readvalmsg: .asciz "Read value "
+
+.balign 4
+handler:
+# IMPORTANT NOTE: This handler is for demonstration purposes only and clobbers a0/a1/a7/t1!
+# Its "handling" is also roughly the equivalent of Visual Basic's infamous "on error resume next"
+li a7,4 # Print string
+la a0,trapmsg # "Got trap "
+ecall
+
+li a7,1 # Print integer
+csrr a0,mcause # Read trap cause
+ecall
+
+li a7,11 # Print character
+li a0,'\n'
+ecall
+
+li a7,4 # Print string
+la a0,mepcmsg # "at "
+ecall
+
+li a7,1 # Print integer
+csrr a0,mepc # Read faulting address
+ecall
+
+li a7,11 # Print character
+li a0,'\n'
+ecall
+
+csrr a0,mepc # Read faulting address again
+lw a1,0(a0) # Read faulting instruction into a1
+li a7,0x00000003
+andn a1,a7,a1 # Invert it and AND with 0x3 (gives 0 if last two bits were 11, nonzero otherwise)
+addi a0,a0,2 # Move fault address 2 bytes forward (all instructions are at least this long)
+bnez a1,handler_compressed # If it was compressed, that's all the movement needed
+addi a0,a0,2 # Otherwise it needs to go two more bytes forward
+handler_compressed:
+csrw mepc,a0 # Write the adjusted return address back
+li t1,1234 # Fake the load having succeeded
+mret # And go back there
+
+trapmsg: .asciz "Got trap "
+mepcmsg: .asciz "at "
diff --git a/assembly/trap-test/trap-test.elf b/assembly/trap-test/trap-test.elf
new file mode 100755
index 0000000..aa5bda6
--- /dev/null
+++ b/assembly/trap-test/trap-test.elf
Binary files differ
diff --git a/assembly/trap-test/trap-test.hex b/assembly/trap-test/trap-test.hex
new file mode 100644
index 0000000..fe2b395
--- /dev/null
+++ b/assembly/trap-test/trap-test.hex
@@ -0,0 +1,13 @@
+:10000000970200009382820473905230B7C2ADDE33
+:100010009382F2EE03A30200914817050000130536
+:1000200025027300000085481A8573000000AD4862
+:10003000294573000000A9487300000052656164FF
+:100040002076616C75652000914817050000130546
+:10005000C505730000008548732520347300000037
+:10006000AD482945730000009148170500001305AD
+:100070006504730000008548732510347300000088
+:10008000AD48294573000000732510340C418D489C
+:10009000B3F5B840090591E1090573101534130350
+:1000A000204D73002030476F7420747261702000FF
+:0600B00061742000010054
+:00000001FF
diff --git a/assembly/trap-test/trap-test.o b/assembly/trap-test/trap-test.o
new file mode 100644
index 0000000..0796ad9
--- /dev/null
+++ b/assembly/trap-test/trap-test.o
Binary files differ
diff --git a/rvcontroller.lua b/rvcontroller.lua
index 1e60474..1acb1f1 100644
--- a/rvcontroller.lua
+++ b/rvcontroller.lua
@@ -206,6 +206,46 @@ local CLOCK_SPEED = 20 --Clock speed in Hz when not in lightweight mode
local INSTRUCTIONS_PER_CLOCK = 50 --Maximum number of instructions that will be run on each clock cycle
local STDOUT_TO_TERMINAL = false --Whether to show standard output on the Luacontroller terminal in addition to the screen
+local traptypes = {
+ --Not all of these are implemented, some are impossible on RVController
+ [0x0] = "Inst addr misaligned",
+ [0x1] = "Inst access fault",
+ [0x2] = "Illegal instruction",
+ [0x3] = "Breakpoint",
+ [0x4] = "Load addr misaligned",
+ [0x5] = "Load access fault",
+ [0x6] = "St/AMO addr misalign",
+ [0x7] = "St/AMO access fault",
+ [0x8] = "U-mode env call",
+ [0x9] = "S-mode env call",
+ --0xa = reserved
+ [0xb] = "M-mode env call",
+ [0xc] = "Instr page fault",
+ [0xd] = "Load page fault",
+ --0xe = reserved
+ [0xf] = "St/AMO page fault",
+ [0x10] = "Double trap",
+ --0x11 = reserved
+ [0x12] = "Software check",
+ [0x13] = "Hardware error",
+ --0x14-0x80000000 = reserved/custom
+ [0x8000001] = "Supv soft interrupt",
+ --0x80000002 = reserved
+ [0x8000003] = "Mach soft interrupt",
+ --0x80000004 = reserved
+ [0x8000005] = "Supv timer interrupt",
+ --0x80000006 = reserved
+ [0x8000007] = "Mach timer interrupt",
+ --0x80000008 = reserved
+ [0x8000009] = "Supv ext interrupt",
+ --0x8000000a = reserved
+ [0x800000b] = "Mach ext interrupt",
+ --0x8000000c = reserved
+ [0x800000d] = "Count over interrupt",
+ --0x8000000e-0xffffffff = reserved/platform
+}
+
+local fault --non-nil values are for deferred faults that will trap after the instruction finishes
local function implodebits(bits,count,signed)
local negative = false
@@ -281,6 +321,7 @@ end
local function getreg(reg)
if reg > 15 and not mem.isa.i then
digiline_send("monitordisp",string.format("Attempted read from\nregister x%d\nin E mode\nPC: %08X",reg,mem.registers.pc))
+ fault = 2
return 0
end
return reg == 0 and 0 or mem.registers[reg]
@@ -289,6 +330,7 @@ end
local function setreg(reg,val)
if reg > 15 and not mem.isa.i then
digiline_send("monitordisp",string.format("Attempted write to\nregister x%d\nin E mode\nPC: %08X",reg,mem.registers.pc))
+ fault = 2
return
end
if val < 0 then val = val + (2^32) end
@@ -298,9 +340,8 @@ end
local function readram(address,bytes,instfetch)
local bigendian = mem.bigendian and not instfetch --Instruction fetches must always be little-endian
if address > RAM_SIZE-1 and not (address >= mem.csr[0x801] and address <= mem.csr[0x801] + 1) then
- digiline_send("monitordisp",string.format("Out-of-bounds\nmemory read\nAddress: %08X\nPC: %08X\nSystem halted",address,mem.registers.pc))
- mem.running = false
- return 0
+ fault = instfetch and 1 or 5
+ return instfetch and 0x13 or 0 --0x13 = addi x0,x0,0
end
local out = 0
for i=0,bytes-1 do
@@ -341,8 +382,7 @@ end
local function writeram(address,data,bytes,fake)
if address > RAM_SIZE-1 and not fake and not (address >= mem.csr[0x801] and address <= mem.csr[0x801] + 1) then
- digiline_send("monitordisp",string.format("Out-of-bounds\nmemory write\nAddress: %08X\nPC: %08X\nSystem halted",address,mem.registers.pc))
- mem.running = false
+ fault = 7
return
end
if mem.reservationset then
@@ -453,7 +493,8 @@ end
local function readcsr(address)
if not mem.csr[address] then
- digiline_send("monitordisp",string.format("W: Attempted read\nfrom unknown\nCSR 0x%03X\nPC: %08X",address,mem.registers.pc))
+ digiline_send("monitordisp",string.format("Attempted read\nfrom unknown\nCSR 0x%03X\nPC: %08X",address,mem.registers.pc))
+ fault = 2
return 0
end
return mem.csr[address]
@@ -498,14 +539,45 @@ local function writecsr(address,data)
writeram(data,0,1,true) --Invalidate reservation set at new address
end
return
+ elseif address == 0x305 then
+ --mtvec
+ mem.trapmode = data%4
+ mem.trapbase = data-mem.trapmode
end
if not mem.csr[address] then
- digiline_send("monitordisp",string.format("W: Attempted write\nto unknown\nCSR 0x%03X\nPC: %08X",address,mem.registers.pc))
+ digiline_send("monitordisp",string.format("Attempted write\nto unknown\nCSR 0x%03X\nPC: %08X",address,mem.registers.pc))
+ fault = 2
return
end
mem.csr[address] = data
end
+local function trap(reason)
+ writecsr(0x342,reason) --mcause
+ writecsr(0x341,mem.registers.pc) --mepc
+ writecsr(0x343,0) --mtval
+ if mem.trapbase then
+ if mem.trapmode == 1 then
+ --vectored mode
+ local int = reason >= 0x8000000
+ reason = reason%0x80000000
+ mem.registers.pc = reason*4+mem.trapbase
+ return true,false
+ else
+ --direct or reserved (treated as direct) mode
+ mem.registers.pc = mem.trapbase
+ return true,false
+ end
+ else
+ digiline_send("monitordisp","Unhandled trap!")
+ digiline_send("monitordisp",traptypes[reason] or "Unknown reason")
+ digiline_send("monitordisp",string.format("PC: %08X",mem.registers.pc))
+ digiline_send("monitordisp","System halted")
+ mem.running = false
+ return true,true
+ end
+end
+
local cmpushpopreglists = {
[4] = {registers = {1},stack = 16},
[5] = {registers = {1,8},stack = 16},
@@ -933,10 +1005,7 @@ local operations = {
end
end,
ebreak = function()
- mem.running = false
- digiline_send("monitordisp","Hit breakpoint")
- digiline_send("montiordisp","System halted")
- digiline_send("monitordisp",string.format("PC:%08X",mem.registers.pc))
+ return trap(3)
end,
csrrw = function(rd,rs1,imm)
setreg(rd,readcsr(imm))
@@ -1716,6 +1785,10 @@ local operations = {
end
setreg(rd,implodebits(outbits,32))
end,
+ mret = function()
+ mem.registers.pc = readcsr(0x341)
+ return true
+ end,
}
local function runinst(instruction)
@@ -1818,6 +1891,8 @@ local function runinst(instruction)
operations.czeroeqz(rd,rs1,rs2)
elseif f3 == 0x7 and f7 == 0x7 then
operations.czeronez(rd,rs1,rs2)
+ else
+ return trap(2)
end
elseif opcode == 0x13 or opcode == 0x3 or opcode == 0x67 or opcode == 0x73 then
--I-type
@@ -1878,6 +1953,8 @@ local function runinst(instruction)
operations.zip(rd,rs1,imm)
elseif f3 == 0x5 and imm == 0x8f then
operations.unzip(rd,rs1,imm)
+ else
+ return trap(2)
end
elseif opcode == 0x3 then
if f3 == 0x0 then
@@ -1892,21 +1969,29 @@ local function runinst(instruction)
operations.lbu(rd,rs1,imm)
elseif f3 == 0x5 then
operations.lhu(rd,rs1,imm)
+ else
+ return trap(2)
end
elseif opcode == 0x67 then
if f3 == 0x0 then
return operations.jalr(rd,rs1,imm)
+ else
+ return trap(2)
end
elseif opcode == 0x73 then
if f3 == 0x0 then
if imm == 0x0 then
operations.ecall()
elseif imm == 0x1 then
- operations.ebreak()
+ return operations.ebreak()
elseif rs1 == 0 and rd == 0 and imm == 0x0d and mem.isa.a then
return operations.wrsnto()
elseif rs1 == 0 and rd == 0 and imm == 0x1d and mem.isa.a then
return operations.wrssto()
+ elseif imm == 0x302 and rd == 0 and rs1 == 0 then
+ return operations.mret()
+ else
+ return trap(2)
end
elseif f3 == 0x1 then
operations.csrrw(rd,rs1,imm)
@@ -1926,6 +2011,8 @@ local function runinst(instruction)
operations.csrrsi(rd,rs1,imm)
elseif f3 == 0x7 then
operations.csrrci(rd,rs1,imm)
+ else
+ return trap(2)
end
end
elseif opcode == 0x23 then
@@ -1946,6 +2033,8 @@ local function runinst(instruction)
operations.sw(rs1,rs2,imm)
elseif f3 == 0x3 then
operations.sd(rs1,rs2,imm)
+ else
+ return trap(2)
end
elseif opcode == 0x63 then
--B-type
@@ -1969,6 +2058,8 @@ local function runinst(instruction)
return operations.bltu(rs1,rs2,imm)
elseif f3 == 0x7 then
return operations.bgeu(rs1,rs2,imm)
+ else
+ return trap(2)
end
elseif opcode == 0x37 or opcode == 0x17 then
--U-type
@@ -1981,6 +2072,8 @@ local function runinst(instruction)
operations.lui(rd,imm)
elseif opcode == 0x17 then
operations.auipc(rd,imm)
+ else
+ return trap(2)
end
elseif opcode == 0x6f then
--J-type
@@ -2043,11 +2136,15 @@ local function runinst(instruction)
--sw.rl/sw.aqrl
--The normal sw instruction implementation already meets these requirements
operations.sw(rs1,rs2,0)
+ else
+ return trap(2)
end
elseif f3 == 3 then
if f5 == 5 then
--amocas.d
operations.amocasd(rd,rs1,rs2)
+ else
+ return trap(2)
end
elseif f3 == 0 then
if f5 == 1 then
@@ -2088,6 +2185,8 @@ local function runinst(instruction)
--sb.rl/sb.aqrl
--The normal sb instruction implementation already meets these requirements
operations.sb(rs1,rs2,0)
+ else
+ return trap(2)
end
elseif f3 == 1 then
if f5 == 1 then
@@ -2128,6 +2227,8 @@ local function runinst(instruction)
--sh.rl/sh.aqrl
--The normal sh instruction implementation already meets these requirements
operations.sh(rs1,rs2,0)
+ else
+ return trap(2)
end
end
elseif opcode == 0x0f then
@@ -2138,7 +2239,7 @@ local function runinst(instruction)
return false,true
end
elseif opcode == 0x0b then
- --Custom instruction (currently all R-type)
+ --Custom0 instruction (currently all R-type)
local f3 = implodebits({[0] = bits[12],bits[13],bits[14]},3)
local xh3bextmsize = implodebits({[0] = bits[26],bits[27],bits[28]},3)+1
local rd = implodebits({[0] = bits[7],bits[8],bits[9],bits[10],bits[11]},5)
@@ -2148,10 +2249,11 @@ local function runinst(instruction)
operations.h3bextm(rd,rs1,rs2,xh3bextmsize)
elseif f3 == 4 then
operations.h3bextmi(rd,rs1,rs2,xh3bextmsize)
+ else
+ return trap(2)
end
else
- mem.running = false
- digiline_send("monitordisp",string.format("Invalid opcode %02X,\nhalted",opcode))
+ trap(2)
end
end
@@ -2228,7 +2330,7 @@ local function runcinst(instruction)
local rs2 = implodebits({[0] = bits[2],bits[3],bits[4],bits[5],bits[6]},5)
if rd == 0 and rs2 == 0 then
--c.ebreak (CR)
- operations.ebreak()
+ return operations.ebreak()
elseif rs2 == 0 then
--c.jalr (CR)
return operations.jalr(1,rs1,0,true)
@@ -2271,6 +2373,8 @@ local function runcinst(instruction)
elseif rd%2 == 1 and rd <= 15 then
--c.mop
operations.cmop(rd,imm)
+ else
+ return trap(2)
end
elseif opcode == 1 and f3 == 0 then
--c.addi (CI)
@@ -2463,8 +2567,7 @@ local function runcinst(instruction)
return true
end
else
- mem.running = false
- digiline_send("monitordisp","Invalid compressed\ninstruction, halted")
+ trap(2)
end
end
@@ -2485,6 +2588,7 @@ local function run(limit)
local first = true
repeat
+ fault = nil
if mem.registers.pc == mem.breakpoint and not first then
digiline_send("monitordisp","Hit breakpoint")
mem.running = false
@@ -2494,15 +2598,20 @@ local function run(limit)
local instruction = readram(mem.registers.pc,4,true)
if instruction%4 == 3 or not mem.isa.c then
if mem.registers.pc%4 ~= 0 and not mem.isa.c then
- digiline_send("monitordisp",string.format("Misaligned\ninstruction fetch,\nSystem halted\nPC: %08X",mem.registers.pc))
- mem.running = false
- break
+ local _,stop = trap(0)
+ if stop then break end
end
local jumped,stop = runinst(instruction)
+ if fault then
+ jumped,stop = trap(fault)
+ end
if not jumped then mem.registers.pc = (mem.registers.pc + 4) % 2^32 end
if stop then break end
else
local jumped,stop = runcinst(instruction%2^16)
+ if fault then
+ jumped,stop = trap(fault)
+ end
if not jumped then mem.registers.pc = (mem.registers.pc + 2) % 2^32 end
if stop then break end
end
@@ -2554,6 +2663,35 @@ local regaliases = {
t6 = 31,
}
+local csraliases = {
+ cycle = 0xc00,
+ time = 0xc01,
+ instret = 0xc02,
+ cycleh = 0xc80,
+ timeh = 0xc81,
+ instreth = 0xc82,
+ mvendorid = 0xf11,
+ marchid = 0xf12,
+ mimpid = 0xf13,
+ mhartid = 0xf14,
+ mstatus = 0x300,
+ misa = 0x301,
+ medeleg = 0x302,
+ mideleg = 0x303,
+ mie = 0x304,
+ mtvec = 0x305,
+ mcounteren = 0x306,
+ mstatush = 0x310,
+ medelegh = 0x312,
+ mscratch = 0x340,
+ mepc = 0x341,
+ mcause = 0x342,
+ mtval = 0x343,
+ mip = 0x344,
+ mtinst = 0x34a,
+ mtval2 = 0x34b,
+}
+
if event.type == "program" or event.iid == "reset" then
mem.ram = {}
for i=0,(RAM_SIZE/256)-1 do
@@ -2575,7 +2713,12 @@ if event.type == "program" or event.iid == "reset" then
[0xf14] = 0, --mhartid
[0x300] = 0, --mstatus
[0x301] = 0x40001107, --misa (RV32IMACB)
+ [0x305] = 0, --mtvec
[0x310] = 0, --mstatush
+ [0x340] = 0, --mscratch
+ [0x341] = 0, --mepc
+ [0x342] = 0, --mcause
+ [0x343] = 0, --mtval
[0x800] = 0, --Clock Controls
[0x801] = 0, --MMIO Base Address
[0xc00] = 0, --CYCLE
@@ -2605,6 +2748,8 @@ if event.type == "program" or event.iid == "reset" then
mem.meseconsdir = {}
mem.meseconsdata = {}
port = {}
+ mem.trapbase = nil
+ mem.trapmode = nil
elseif event.type == "on" or event.type == "off" then
local pname = string.lower(event.pin.name)
local pstate = pin[pname]
@@ -2756,14 +2901,14 @@ elseif event.channel == "monitorkb" then
elseif argv[1] == "clearbreak" then
mem.breakpoint = -1
elseif argv[1] == "rdcsr" then
- local address = validateandclamp(argv[2],0,0xfff)
+ local address = csraliases[argv[2]] or validateandclamp(argv[2],0,0xfff)
if not address then
digiline_send("monitordisp","Bad CSR address")
return
end
digiline_send("monitordisp",string.format("%03X: %08X",address,readcsr(address)))
elseif argv[1] == "wrcsr" then
- local address = validateandclamp(argv[2],0,0xfff)
+ local address = csraliases[argv[2]] or validateandclamp(argv[2],0,0xfff)
if not address then
digiline_send("monitordisp","Bad CSR address")
return