358 lines
12 KiB
Awk
358 lines
12 KiB
Awk
#!/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
|
|
}
|
|
}
|