Files
a-machine/amach.awk
T
2026-06-18 19:10:32 +03:00

481 lines
18 KiB
Awk

#!/usr/bin/env awk -f
# 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
# 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))
pc += (taken ? immval : 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))
}
# 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
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
} 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
}
}