summaryrefslogtreecommitdiff
path: root/rvcontroller.lua
diff options
context:
space:
mode:
authorcheapie <cheapiephp@gmail.com>2026-06-19 10:23:26 -0500
committercheapie <cheapiephp@gmail.com>2026-06-19 10:23:26 -0500
commit9ae1b986e8461eabe43291fec51abbd4153f8e30 (patch)
tree0959fb1daecc531a04f8fe80c2ba3f2272feed59 /rvcontroller.lua
parenteeb1c32240ace33f843ef708ef027569ab401e7a (diff)
downloadrvcontroller-9ae1b986e8461eabe43291fec51abbd4153f8e30.tar
rvcontroller-9ae1b986e8461eabe43291fec51abbd4153f8e30.tar.gz
rvcontroller-9ae1b986e8461eabe43291fec51abbd4153f8e30.tar.bz2
rvcontroller-9ae1b986e8461eabe43291fec51abbd4153f8e30.tar.xz
rvcontroller-9ae1b986e8461eabe43291fec51abbd4153f8e30.zip
Add trap handling support
Diffstat (limited to 'rvcontroller.lua')
-rw-r--r--rvcontroller.lua191
1 files changed, 168 insertions, 23 deletions
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