#!/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 uint32(val) { val = int(val) % 4294967296 return (val < 0) ? (val + 4294967296) : val } function setreg(idx, val) { if(idx > 0) { val = uint32(val) 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_op(a, b, op, v, r) { v = 1; r = 0; a = uint32(a); b = uint32(b) while(a > 0 || b > 0) { if(op == "&" && (a%2) == 1 && (b%2) == 1) r += v else if(op == "|" && ((a%2) == 1 || (b%2) == 1)) r += v else if(op == "^" && (a%2) != (b%2)) r += v a = int(a/2); b = int(b/2); v *= 2 } return int(r) } function bw_and(a, b) {return bw_op(a, b, "&")} function bw_or(a, b) {return bw_op(a, b, "|")} function bw_xor(a, b) {return bw_op(a, b, "^")} function read_mem(addr, bytes, signed, i, val, max_val) { addr = uint32(addr) val = 0 for(i=0; i= max_val/2) val -= max_val } return val } function write_mem(addr, val, bytes, i) { addr = uint32(addr) val = uint32(val) for(i=0; i a2) l = a2 for(i=0; i= 0 && f3 <= 2) write_mem(r1 + immval, r2, 2^f3) 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 = uint32(r1); ur2 = uint32(r2) 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, shamt) { r1 = getreg(rs1); ur1 = uint32(r1) if(opcode == 3) { # load if(f3 == 0 || f3 == 1 || f3 == 2 || f3 == 4 || f3 == 5) { setreg(rd, read_mem(r1 + immval, 2^(f3 % 4), f3 < 4)) } 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 = uint32(r2) if(f3 == 2) { val = read_word(r1) uval = uint32(val) 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 = (r11_7 % 8) + 8 r6_2 = int(instr / 4) % 32 r4_2 = (r6_2 % 8) + 8 shamt = r6_2 + b12 * 32 if(op == 0) { if(f3 == 0) { # C.ADDI4SPN imm = (r11_7 % 16) * 64 + (int(r11_7 / 16) + b12 * 2) * 16 + (int(r6_2 / 8) % 2) * 8 + (int(r6_2 / 16) % 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(r6_2 / 8) % 2) * 64 + (int(r11_7 / 8) % 4) * 8 + b12 * 32 + (int(r6_2 / 16) % 2) * 4 setreg(r4_2, read_mem(getreg(r9_7) + imm, 4, 1)) } else if(f3 == 6) { # C.SW imm = (int(r6_2 / 8) % 2) * 64 + (int(r11_7 / 8) % 4) * 8 + b12 * 32 + (int(r6_2 / 16) % 2) * 4 write_mem(getreg(r9_7) + imm, getreg(r4_2), 4) } 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(r11_7 / 2) % 2) * 1024 + (int(r11_7 / 4) % 4) * 256 + (int(r6_2 / 16) % 2) * 128 + (r11_7 % 2) * 64 + (r6_2 % 2) * 32 + (int(r11_7 / 16)) * 16 + (int(r6_2 / 2) % 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(r6_2 / 2) % 4) * 128 + (int(r6_2 / 8) % 2) * 64 + (r6_2 % 2) * 32 + (int(r6_2 / 16) % 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(r11_7 / 8) % 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(r6_2 / 8) % 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(r6_2 / 8) % 4) * 64 + (r6_2 % 2) * 32 + (int(r11_7 / 8) % 4) * 8 + (int(r6_2 / 2) % 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 = (r6_2 % 4) * 64 + b12 * 32 + (int(r6_2 / 4) % 8) * 4 setreg(r11_7, read_mem(getreg(2) + imm, 4, 1)) } 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 setreg(r11_7, getreg(r6_2)) # C.MV } 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 setreg(r11_7, getreg(r11_7) + getreg(r6_2)) # C.ADD } } else if(f3 == 6) { # C.SWSP imm = (r11_7 % 4) * 64 + int(r11_7 / 4) * 4 + b12 * 32 write_mem(getreg(2) + imm, getreg(r6_2), 4) } 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 } }