Skip to content

Instantly share code, notes, and snippets.

@cheapie
Created April 30, 2026 09:51
Show Gist options
  • Select an option

  • Save cheapie/fac8b1fad87bd02700b1c2e1ab05fdce to your computer and use it in GitHub Desktop.

Select an option

Save cheapie/fac8b1fad87bd02700b1c2e1ab05fdce to your computer and use it in GitHub Desktop.
local function implodebits(bits,count,signed)
local negative = false
if signed then
negative = bits[count-1]
end
local out = 0
for i=0,count-1 do
if bits[i] then out = out + (2^i) end
end
if negative then out = out - (2^count) end
return out
end
local function explodebits(num,count)
num = num%(2^count)
if num < 0 then
num = num + (2^count)
end
local out = {}
for i=0,count-1 do
out[i] = num%(2^(i+1)) >= 2^i
end
return out
end
local function signextend(bits,fromcount,tocount)
for i=fromcount,tocount-1 do
bits[i] = bits[fromcount-1]
end
end
local registers = {}
for i=0,31 do registers[i] = 0 end
registers.pc = 0
local ram = {}
local running = false
local function getreg(reg)
return reg == 0 and 0 or registers[reg]
end
local function setreg(reg,val)
if val < 0 then val = val + (2^32) end
if reg ~= 0 then registers[reg] = math.floor(val%(2^32)) end
end
local function readram(address,bytes)
local out = 0
for i=0,bytes-1 do
out = out + (ram[address+i] or 0) * 2^(8*i)
end
return out
end
local function writeram(address,data,bytes)
for i=0,bytes-1 do
local thisbyte = data % (2^((i+1)*8)) / 2^(i*8)
thisbyte = math.floor(thisbyte) % 2^32
if thisbyte ~= 0 then
ram[address+i] = thisbyte
else
--nil values are turned back into 0 on read
ram[address+i] = nil
end
end
end
local function printstate()
local buf = ""
for i=0,31 do
buf = buf..string.format("x%02d: %08X ",i,getreg(i))
if i%4 == 3 then
buf = buf.."\n"
end
end
buf = buf..string.format(" PC: %08X\n",registers.pc)
print(buf)
end
local operations = {
add = function(rd,rs1,rs2)
setreg(rd,getreg(rs1)+getreg(rs2))
end,
sub = function(rd,rs1,rs2)
setreg(rd,getreg(rs1)-getreg(rs2))
end,
xor = function(rd,rs1,rs2)
local rs1bits = explodebits(getreg(rs1),32)
local rs2bits = explodebits(getreg(rs2),32)
local rdbits = {}
for i=0,31 do rdbits[i] = rs1bits[i] ~= rs2bits[i] end
setreg(rd,implodebits(rdbits,32,false))
end,
orr = function(rd,rs1,rs2) --instruction is "or" but that's a reserved word
local rs1bits = explodebits(getreg(rs1),32)
local rs2bits = explodebits(getreg(rs2),32)
local rdbits = {}
for i=0,31 do rdbits[i] = rs1bits[i] or rs2bits[i] end
setreg(rd,implodebits(rdbits,32,false))
end,
andr = function(rd,rs1,rs2) --instruction is "and" but that's a reserved word
local rs1bits = explodebits(getreg(rs1),32)
local rs2bits = explodebits(getreg(rs2),32)
local rdbits = {}
for i=0,31 do rdbits[i] = rs1bits[i] and rs2bits[i] end
setreg(rd,implodebits(rdbits,32,false))
end,
sll = function(rd,rs1,rs2)
setreg(rd,getreg(rs1)*(2^getreg(rs2)))
end,
srl = function(rd,rs1,rs2)
setreg(rd,getreg(rs1)/(2^getreg(rs2)))
end,
sra = function(rd,rs1,rs2)
local num = getreg(rs1)
local amount = getreg(rs2)
local sign = num>=(2^31)
if amount == 0 then return end
for _=1,amount do
num = num/2
if sign then num = num+(2^31) end
end
setreg(rd,num)
end,
slt = function(rd,rs1,rs2)
local num1 = getreg(rs1)
local num2 = getreg(rs2)
if num1 >= 2^31 then num1 = num1-(2^32) end
if num2 >= 2^31 then num2 = num2-(2^32) end
setreg(rd,num1 < num2 and 1 or 0)
end,
sltu = function(rd,rs1,rs2)
local num1 = getreg(rs1)
local num2 = getreg(rs2)
setreg(rd,num1 < num2 and 1 or 0)
end,
addi = function(rd,rs1,imm)
local immbits = explodebits(imm,12)
signextend(immbits,12,32)
imm = implodebits(immbits,32,false)
setreg(rd,getreg(rs1)+imm)
end,
xori = function(rd,rs1,imm)
local rs1bits = explodebits(getreg(rs1),32)
local immbits = explodebits(imm,12)
signextend(immbits,12,32)
local rdbits = {}
for i=0,31 do
rdbits[i] = rs1bits[i] ~= immbits[i]
end
setreg(rd,implodebits(rdbits,32,false))
end,
ori = function(rd,rs1,imm)
local rs1bits = explodebits(getreg(rs1),32)
local immbits = explodebits(imm,12)
signextend(immbits,12,32)
local rdbits = {}
for i=0,31 do
rdbits[i] = rs1bits[i] or immbits[i]
end
setreg(rd,implodebits(rdbits,32,false))
end,
andi = function(rd,rs1,imm)
local rs1bits = explodebits(getreg(rs1),32)
local immbits = explodebits(imm,12)
signextend(immbits,12,32)
local rdbits = {}
for i=0,31 do
rdbits[i] = rs1bits[i] and immbits[i]
end
setreg(rd,implodebits(rdbits,32,false))
end,
slli = function(rd,rs1,imm)
setreg(rd,getreg(rs1)*(2^imm))
end,
srli = function(rd,rs1,imm)
setreg(rd,getreg(rs1)/(2^imm))
end,
srai = function(rd,rs1,imm)
local num = getreg(rs1)
local sign = num>=(2^31)
if imm == 0 then return end
for _=1,imm do
num = num/2
if sign then num = num+(2^31) end
end
setreg(rd,num)
end,
slti = function(rd,rs1,imm)
local num1 = getreg(rs1)
local immbits = explodebits(imm,12)
signextend(immbits,12,32)
local num2 = implodebits(immbits,32,false)
if num1 >= 2^31 then num1 = num1-(2^32) end
if num2 >= 2^31 then num2 = num2-(2^32) end
setreg(rd,num1 < num2 and 1 or 0)
end,
sltiu = function(rd,rs1,imm)
local num1 = getreg(rs1)
local immbits = explodebits(imm,12)
signextend(immbits,12,32)
local num2 = implodebits(immbits,32,false)
setreg(rd,num1 < num2 and 1 or 0)
end,
beq = function(rs1,rs2,imm)
local num1 = getreg(rs1)
local num2 = getreg(rs2)
if num1 == num2 then
local immbits = explodebits(imm,13)
signextend(immbits,13,32)
imm = implodebits(immbits,13,true)
registers.pc = (registers.pc + imm) % 2^32
return true
end
end,
bne = function(rs1,rs2,imm)
local num1 = getreg(rs1)
local num2 = getreg(rs2)
if num1 ~= num2 then
local immbits = explodebits(imm,13)
signextend(immbits,13,32)
imm = implodebits(immbits,13,true)
registers.pc = (registers.pc + imm) % 2^32
return true
end
end,
blt = function(rs1,rs2,imm)
local num1 = getreg(rs1)
local num2 = getreg(rs2)
if num1 >= 2^31 then num1 = num1-(2^32) end
if num2 >= 2^31 then num2 = num2-(2^32) end
if num1 < num2 then
local immbits = explodebits(imm,13)
signextend(immbits,13,32)
imm = implodebits(immbits,13,true)
registers.pc = (registers.pc + imm) % 2^32
return true
end
end,
bge = function(rs1,rs2,imm)
local num1 = getreg(rs1)
local num2 = getreg(rs2)
if num1 >= 2^31 then num1 = num1-(2^32) end
if num2 >= 2^31 then num2 = num2-(2^32) end
if num1 >= num2 then
local immbits = explodebits(imm,13)
signextend(immbits,13,32)
imm = implodebits(immbits,13,true)
registers.pc = (registers.pc + imm) % 2^32
return true
end
end,
bltu = function(rs1,rs2,imm)
local num1 = getreg(rs1)
local num2 = getreg(rs2)
if num1 < num2 then
local immbits = explodebits(imm,13)
signextend(immbits,13,32)
imm = implodebits(immbits,13,true)
registers.pc = (registers.pc + imm) % 2^32
return true
end
end,
bgeu = function(rs1,rs2,imm)
local num1 = getreg(rs1)
local num2 = getreg(rs2)
if num1 >= num2 then
local immbits = explodebits(imm,13)
signextend(immbits,13,32)
imm = implodebits(immbits,13,true)
registers.pc = (registers.pc + imm) % 2^32
return true
end
end,
jal = function(rd,imm)
setreg(rd,registers.pc+4)
local immbits = explodebits(imm,21)
signextend(immbits,21,32)
imm = implodebits(immbits,32,true)
registers.pc = (registers.pc + imm) % 2^32
return true
end,
jalr = function(rd,rs1,imm)
setreg(rd,registers.pc+4)
local immbits = explodebits(imm,12)
signextend(immbits,12,32)
imm = implodebits(immbits,32,true)
registers.pc = (getreg(rs1) + imm) % 2^32
return true
end,
lui = function(rd,imm)
setreg(rd,imm)
end,
auipc = function(rd,imm)
setreg(rd,registers.pc+imm)
end,
--TODO: lb/lh/lw/lbu/lhu/sb/sh/sw (need memory first)
ecall = function()
--right now the only call is to print the machine state
print("Software requested register dump")
printstate()
end,
ebreak = function()
--for now this just stops the processor and prints state
running = false
print("Hit ebreak, system halted")
printstate()
end,
}
local function runinst(instruction)
local bits = explodebits(instruction,32)
local opcode = implodebits(bits,7)
if opcode == 0x33 then
--R-type
--This is spectaularly inefficient and will probably need to be optimized later.
--Really, the whole program is.
--Focus is on making it work first...
local f3bits = {[0] = bits[12],bits[13],bits[14]}
local f3 = implodebits(f3bits,3)
local f7bits = {[0] = bits[25],bits[26],bits[27],bits[28],bits[29],bits[30],bits[31]}
local f7 = implodebits(f7bits,7)
local rdbits = {[0] = bits[7],bits[8],bits[9],bits[10],bits[11]}
local rd = implodebits(rdbits,5)
local rs1bits = {[0] = bits[15],bits[16],bits[17],bits[18],bits[19]}
local rs1 = implodebits(rs1bits,5)
local rs2bits = {[0] = bits[20],bits[21],bits[22],bits[23],bits[24]}
local rs2 = implodebits(rs2bits,5)
if f3 == 0x0 and f7 == 0x0 then
operations.add(rd,rs1,rs2)
elseif f3 == 0x0 and f7 == 0x20 then
operations.sub(rd,rs1,rs2)
elseif f3 == 0x4 and f7 == 0x0 then
operations.xor(rd,rs1,rs2)
elseif f3 == 0x6 and f7 == 0x0 then
operations.orr(rd,rs1,rs2)
elseif f3 == 0x7 and f7 == 0x0 then
operations.andr(rd,rs1,rs2)
elseif f3 == 0x1 and f7 == 0x0 then
operations.sll(rd,rs1,rs2)
elseif f3 == 0x5 and f7 == 0x0 then
operations.srl(rd,rs1,rs2)
elseif f3 == 0x5 and f7 == 0x20 then
operations.sra(rd,rs1,rs2)
elseif f3 == 0x2 and f7 == 0x0 then
operations.slt(rd,rs1,rs2)
elseif f3 == 0x3 and f7 == 0x0 then
operations.sltu(rd,rs1,rs2)
end
elseif opcode == 0x13 or opcode == 0x3 or opcode == 0x67 or opcode == 0x73 then
--I-type
local f3bits = {[0] = bits[12],bits[13],bits[14]}
local f3 = implodebits(f3bits,3)
local rdbits = {[0] = bits[7],bits[8],bits[9],bits[10],bits[11]}
local rd = implodebits(rdbits,5)
local rs1bits = {[0] = bits[15],bits[16],bits[17],bits[18],bits[19]}
local rs1 = implodebits(rs1bits,5)
local immbits = {[0] = bits[20],bits[21],bits[22],bits[23],bits[24],bits[25],bits[26],bits[27],bits[28],bits[29],bits[30],bits[31]}
local imm = implodebits(immbits,12)
if opcode == 0x13 then
if f3 == 0x0 then
operations.addi(rd,rs1,imm)
elseif f3 == 0x4 then
operations.xori(rd,rs1,imm)
elseif f3 == 0x6 then
operations.ori(rd,rs1,imm)
elseif f3 == 0x7 then
operations.andi(rd,rs1,imm)
elseif f3 == 0x1 and imm/0x20 == 0x0 then
operations.slli(rd,rs1,imm%0x20)
elseif f3 == 0x5 and imm/0x20 == 0x0 then
operations.srli(rd,rs1,imm%0x20)
elseif f3 == 0x5 and imm/0x20 == 0x20 then
operations.srai(rd,rs1,imm%0x20)
elseif f3 == 0x2 then
operations.slti(rd,rs1,imm)
elseif f3 == 0x3 then
operations.sltiu(rd,rs1,imm)
end
elseif opcode == 0x3 then
--TODO: implement lb/lh/lw/lbu/lhu
elseif opcode == 0x67 then
if f3 == 0x0 then
return operations.jalr(rd,rs1,imm)
end
elseif opcode == 0x73 then
if imm == 0x0 then
operations.ecall()
elseif imm == 0x1 then
operations.ebreak()
end
end
elseif opcode == 0x23 then
--S-type
local f3bits = {[0] = bits[12],bits[13],bits[14]}
local f3 = implodebits(f3bits,3)
local rs1bits = {[0] = bits[15],bits[16],bits[17],bits[18],bits[19]}
local rs1 = implodebits(rs1bits,5)
local rs2bits = {[0] = bits[20],bits[21],bits[22],bits[23],bits[24]}
local rs2 = implodebits(rs2bits,5)
local immbits = {[0] = bits[7],bits[8],bits[9],bits[10],bits[11],bits[25],bits[26],bits[27],bits[28],bits[29],bits[30],bits[31]}
local imm = implodebits(immbits,12)
--TODO: implement sb/sh/sw
elseif opcode == 0x63 then
--B-type
local f3bits = {[0] = bits[12],bits[13],bits[14]}
local f3 = implodebits(f3bits,3)
local rs1bits = {[0] = bits[15],bits[16],bits[17],bits[18],bits[19]}
local rs1 = implodebits(rs1bits,5)
local rs2bits = {[0] = bits[20],bits[21],bits[22],bits[23],bits[24]}
local rs2 = implodebits(rs2bits,5)
local immbits = {[0] = false,bits[8],bits[9],bits[10],bits[11],bits[25],bits[26],bits[27],bits[28],bits[29],bits[30],bits[7],bits[31]}
local imm = implodebits(immbits,13)
if f3 == 0x0 then
return operations.beq(rs1,rs2,imm)
elseif f3 == 0x1 then
return operations.bne(rs1,rs2,imm)
elseif f3 == 0x4 then
return operations.blt(rs1,rs2,imm)
elseif f3 == 0x5 then
return operations.bge(rs1,rs2,imm)
elseif f3 == 0x6 then
return operations.bltu(rs1,rs2,imm)
elseif f3 == 0x7 then
return operations.bgeu(rs1,rs2,imm)
end
elseif opcode == 0x37 or opcode == 0x17 then
--U-type
local rdbits = {[0] = bits[7],bits[8],bits[9],bits[10],bits[11]}
local rd = implodebits(rdbits,5)
--Immediate bits in this type actually all line up between the instruction and their actual value(!)
--This means it's way easier to just mask out rd/opcode
local imm = instruction - (instruction % 0x1000)
if opcode == 0x37 then
operations.lui(rd,imm)
elseif opcode == 0x17 then
operations.auipc(rd,imm)
end
elseif opcode == 0x6f then
--J-type
local rdbits = {[0] = bits[7],bits[8],bits[9],bits[10],bits[11]}
local rd = implodebits(rdbits,5)
--Something had to give after how easy the U-type immediates were
local immbits = {[0] = false,bits[21],bits[22],bits[23],bits[24],bits[25],bits[26],bits[27],bits[28],bits[29],bits[30],bits[20],bits[12],bits[13],bits[14],bits[15],bits[16],bits[17],bits[18],bits[19],bits[31]}
local imm = implodebits(immbits,21)
return operations.jal(rd,imm)
else
running = false
print(string.format("Invalid opcode %02X, system halted",opcode))
printstate()
end
end
local function run()
running = true
repeat
local instruction = readram(registers.pc,4)
local jumped = runinst(instruction)
if not jumped then registers.pc = (registers.pc + 4) % 2^32 end
until not running
end
writeram(0x00,0x00100293,4) --addi x5,x0,1
writeram(0x04,0x3e800e13,4) --addi x28,x0,1000
writeram(0x08,0x006283b3,4) --loop: add x7,x5,x6
writeram(0x0c,0x00030293,4) --addi x5,x6,0
writeram(0x10,0x00038313,4) --addi x6,x7,0
writeram(0x14,0x00000073,4) --ecall
writeram(0x18,0xffc3e8e3,4) --bltu x7,x28,loop
writeram(0x1c,0x00100073,4) --ebreak
run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment