#!/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, fd) { for(fd in FD_PATH) if(FD_DIRTY[fd]) write_fd_to_file(fd) cmd = "cat 1>&2" printf("Fatal: %s\n", msg) | cmd close(cmd) exit(1) } # helper functions function uint32(val) { val = int(val) if (val >= 0 && val < 4294967296) return val val = val % 4294967296 return (val < 0) ? (val + 4294967296) : val } 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_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) if (bytes == 1) { val = MEM[addr] if (signed && val >= 128) val -= 256 } else if (bytes == 2) { val = MEM[addr] + MEM[addr+1] * 256 if (signed && val >= 32768) val -= 65536 } else if (bytes == 4) { val = MEM[addr] + MEM[addr+1] * 256 + MEM[addr+2] * 65536 + MEM[addr+3] * 16777216 if (signed && val >= 2147483648) val -= 4294967296 } else { 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) if (bytes == 1) { MEM[addr] = val % 256 } else if (bytes == 2) { MEM[addr] = val % 256 MEM[addr+1] = int(val / 256) % 256 } else if (bytes == 4) { MEM[addr] = val % 256 MEM[addr+1] = int(val / 256) % 256 MEM[addr+2] = int(val / 65536) % 256 MEM[addr+3] = int(val / 16777216) % 256 } else { for(i=0; i 0) { if(length(line) > 9 && line ~ /^[0-9]+$/) { time_arr["sec"] = int(substr(line, 1, length(line) - 9)) time_arr["nsec"] = int(substr(line, length(line) - 8)) close(cmd) return } } close(cmd) # Fallback if %N is not supported cmd = "date +%s" if((cmd | getline line) > 0) { time_arr["sec"] = int(line) } else time_arr["sec"] = 0 close(cmd) if(time_arr["sec"] == LAST_SEC) { VIRT_NSEC += 500000 if(VIRT_NSEC >= 1000000000) { VIRT_NSEC = 999999999 } } else { LAST_SEC = time_arr["sec"] VIRT_NSEC = 0 } time_arr["nsec"] = VIRT_NSEC } # Fills stat structure in VM memory function fill_stat_struct(path, addr, follow, cmd, line, parts, opt, mode_hex, mode_dec, size, atime, mtime, ctime, i) { opt = follow ? "-L " : "" cmd = "stat " opt "-c \"%f %s %X %Y %Z\" " qquote(path) if((cmd | getline line) > 0) { split(line, parts) mode_hex = parts[1] mode_dec = hex_to_dec(mode_hex) size = int(parts[2]) atime = int(parts[3]) mtime = int(parts[4]) ctime = int(parts[5]) close(cmd) for(i = 0; i < 104; i++) MEM[addr + i] = 0 write_mem(addr + 0, 1, 8) write_mem(addr + 8, 1, 8) write_mem(addr + 16, mode_dec, 4) write_mem(addr + 20, 1, 4) write_mem(addr + 24, 1000, 4) write_mem(addr + 28, 1000, 4) write_mem(addr + 32, 0, 8) write_mem(addr + 48, size, 8) write_mem(addr + 56, 4096, 4) write_mem(addr + 64, int((size + 511) / 512), 8) write_mem(addr + 72, atime, 4) write_mem(addr + 80, mtime, 4) write_mem(addr + 88, ctime, 4) return 0 } else { close(cmd) return -2 } } # Writes file descriptor cached data back to file function write_fd_to_file(fd, path, chunk, i, size) { path = FD_PATH[fd] size = FD_SIZE[fd] printf("") > path close(path) chunk = "" for(i = 0; i < size; i++) { chunk = chunk sprintf("%c", FD_DATA[fd, i]) if(length(chunk) >= 1024) { printf("%s", chunk) >> path chunk = "" } } if(length(chunk) > 0) printf("%s", chunk) >> path close(path) } function amach_exit(code, fd) { for(fd in FD_PATH) if(FD_DIRTY[fd]) write_fd_to_file(fd) exit(code) } # syscall emulation function handle_ecall(callnum, a0, a1, a2, a3, a4, a5, i, reclen, cmd, line, arr) { ECALL_COUNT++ if(ECALL_COUNT == 1) POST_ECALL_IC = ic if(callnum == 34) { # sys_mkdirat a1 = resolve_path(a0, read_str(a1)) if(path_exists(a1)) setreg(10, -17) # -EEXIST else setreg(10, (system("mkdir " qquote(a1)) == 0) ? 0 : -2) } else if(callnum == 35) { # sys_unlinkat a1 = resolve_path(a0, read_str(a1)) if(!path_exists(a1)) setreg(10, -2) # -ENOENT else { a2 = bw_and(a2, 512) ? "rmdir " : "rm " setreg(10, (system(a2 qquote(a1)) == 0) ? 0 : -1) } } else if(callnum == 38 || callnum == 276) { # sys_renameat / sys_renameat2 a1 = resolve_path(a0, read_str(a1)) a3 = resolve_path(a2, read_str(a3)) setreg(10, (system("mv " qquote(a1) " " qquote(a3)) == 0) ? 0 : -1) } else if(callnum == 56) { # sys_openat a1 = resolve_path(a0, read_str(a1)) if(bw_and(a2, 64)) { # O_CREAT (0x40) if(bw_and(a2, 128) && path_exists(a1)) { # O_EXCL (0x80) setreg(10, -17) # -EEXIST return } if(!path_exists(a1)) { printf("") > a1 close(a1) } } else if(!path_exists(a1)) { setreg(10, -2) # -ENOENT return } a3 = NEXT_FD++ FD_PATH[a3] = a1 FD_FLAGS[a3] = a2 FD_OFFSET[a3] = 0 FD_SIZE[a3] = 0 FD_DIRTY[a3] = 0 if(is_dir(a1)) { cmd = "ls -a1 " qquote(a1) a4 = 0 while((cmd | getline line) > 0) if(line != "") FD_DIRENTS[a3, a4++] = line close(cmd) FD_DIRENT_COUNT[a3] = a4 } else { a4 = bw_and(a2, 3) # access_mode a5 = (a4 != 0) && bw_and(a2, 512) # do_trunc if(a4 != 1 && !a5) { # is_read && !do_trunc cmd = "od -An -v -tu1 " qquote(a1) a0 = 0 # reuse a0 for size while((cmd | getline line) > 0) { split(line, arr) for(i = 1; i in arr; i++) FD_DATA[a3, a0++] = arr[i] } close(cmd) FD_SIZE[a3] = a0 } else if(a5) { printf("") > a1 close(a1) } } setreg(10, a3) } else if(callnum == 57) { # sys_close if(a0 >= 3 && (a0 in FD_PATH)) { if(FD_DIRTY[a0]) write_fd_to_file(a0) delete FD_PATH[a0] delete FD_FLAGS[a0] delete FD_OFFSET[a0] delete FD_SIZE[a0] delete FD_DIRTY[a0] if(a0 in FD_DIRENT_COUNT) { for(i = 0; i < FD_DIRENT_COUNT[a0]; i++) delete FD_DIRENTS[a0, i] delete FD_DIRENT_COUNT[a0] } else for(i = 0; i < FD_SIZE[a0]; i++) delete FD_DATA[a0, i] setreg(10, 0) } else setreg(10, -9) # -EBADF } else if(callnum == 61) { # sys_getdents64 if(!(a0 in FD_DIRENT_COUNT)) setreg(10, -9) # -EBADF else { a3 = 0 # bytes_written a4 = FD_OFFSET[a0] # idx while(a4 < FD_DIRENT_COUNT[a0]) { a5 = FD_DIRENTS[a0, a4] # name reclen = int((20 + length(a5) + 7) / 8) * 8 if(a3 + reclen > a2) { # count_limit if(a3 == 0) { setreg(10, -22) # -EINVAL return } break } write_mem(a1 + a3, 1, 8) write_mem(a1 + a3 + 8, a4 + 1, 8) write_mem(a1 + a3 + 16, reclen, 2) write_mem(a1 + a3 + 18, is_dir(FD_PATH[a0] "/" a5) ? 4 : 8, 1) # dtype for(i = 0; i < length(a5); i++) MEM[a1 + a3 + 19 + i] = ord(substr(a5, i + 1, 1)) MEM[a1 + a3 + 19 + length(a5)] = 0 for(i = 19 + length(a5) + 1; i < reclen; i++) MEM[a1 + a3 + i] = 0 a3 += reclen a4++ } FD_OFFSET[a0] = a4 setreg(10, a3) } } else if(callnum == 62) { # sys_lseek if(a0 >= 3 && (a0 in FD_PATH) && !(a0 in FD_DIRENT_COUNT)) { if(a2 == 0) {} # SEEK_SET, a1 is unchanged else if(a2 == 1) a1 += FD_OFFSET[a0] else if(a2 == 2) a1 += FD_SIZE[a0] else { setreg(10, -22) # -EINVAL return } if(a1 < 0) setreg(10, -22) # -EINVAL else { FD_OFFSET[a0] = a1 setreg(10, a1) } } else setreg(10, -9) # -EBADF } else if(callnum == 63) { # sys_read if(a0 == 0) { STARTED_IO = 1 if(length(STDIN_BUF) == 0) { if((getline a3 < "-") > 0) STDIN_BUF = a3 "\n" } a4 = length(STDIN_BUF); if(a4 > a2) a4 = a2 for(i = 0; i < a4; i++) MEM[uint32(a1 + i)] = ord(substr(STDIN_BUF, i + 1, 1)) STDIN_BUF = substr(STDIN_BUF, a4 + 1) setreg(10, a4) } else if(a0 >= 3 && (a0 in FD_PATH) && !(a0 in FD_DIRENT_COUNT)) { a3 = FD_OFFSET[a0] # idx a4 = FD_SIZE[a0] # size if(a3 >= a4) setreg(10, 0) else { if(a3 + a2 > a4) a2 = a4 - a3 # count for(i = 0; i < a2; i++) MEM[uint32(a1 + i)] = FD_DATA[a0, a3 + i] FD_OFFSET[a0] = a3 + a2 setreg(10, a2) } } else setreg(10, -9) # -EBADF } else if(callnum == 64) { # sys_write if(a0 == 1 || a0 == 2) { for(i = 0; i < a2; i++) printf("%c", MEM[uint32(a1 + i)]) fflush("") setreg(10, a2) } else if(a0 >= 3 && (a0 in FD_PATH) && !(a0 in FD_DIRENT_COUNT)) { a3 = bw_and(FD_FLAGS[a0], 1024) ? FD_SIZE[a0] : FD_OFFSET[a0] # idx for(i = 0; i < a2; i++) FD_DATA[a0, a3 + i] = MEM[uint32(a1 + i)] FD_OFFSET[a0] = a3 + a2 if(FD_OFFSET[a0] > FD_SIZE[a0]) FD_SIZE[a0] = FD_OFFSET[a0] FD_DIRTY[a0] = 1 setreg(10, a2) } else setreg(10, -9) # -EBADF } else if(callnum == 78) { # sys_readlinkat a0 = resolve_path(a0, read_str(a1)) a1 = "readlink " qquote(a0) if((a1 | getline line) > 0) { close(a1) a4 = length(line) if(a4 > a3) a4 = a3 for(i = 0; i < a4; i++) MEM[uint32(a2 + i)] = ord(substr(line, i + 1, 1)) setreg(10, a4) } else { close(a1) setreg(10, -22) # -EINVAL } } else if(callnum == 79) { # sys_newfstatat a1 = read_str(a1) if(a1 == "" && bw_and(a3, 4096)) { # AT_EMPTY_PATH if(a0 == -100) a0 = "." else if(a0 in FD_PATH) a0 = FD_PATH[a0] else { setreg(10, -9) # -EBADF return } } else a0 = resolve_path(a0, a1) a4 = (bw_and(a3, 256) == 0) # follow link if AT_SYMLINK_NOFOLLOW is NOT set setreg(10, fill_stat_struct(a0, a2, a4)) } else if(callnum == 80) { # sys_fstat if(a0 == 0) a0 = "/dev/stdin" else if(a0 == 1) a0 = "/dev/stdout" else if(a0 == 2) a0 = "/dev/stderr" else if(a0 >= 3 && (a0 in FD_PATH)) a0 = FD_PATH[a0] else { setreg(10, -9) # -EBADF return } setreg(10, fill_stat_struct(a0, a1, 1)) } else if(callnum == 93 || callnum == 94) { # sys_exit / sys_exit_group amach_exit(a0) } else if(callnum == 113 || callnum == 403) { # sys_clock_gettime / sys_clock_gettime64 get_system_time(arr) if(callnum == 113) { write_mem(a1, arr["sec"], 4) write_mem(a1 + 4, arr["nsec"], 4) } else { write_mem(a1, arr["sec"], 8) write_mem(a1 + 8, arr["nsec"], 4) } setreg(10, 0) } else if(callnum == 169) { # sys_gettimeofday get_system_time(arr) write_mem(a0, arr["sec"], 4) write_mem(a0 + 4, int(arr["nsec"] / 1000), 4) setreg(10, 0) } else if(callnum == 278) { # sys_getrandom for(i = 0; i < a1; i++) MEM[uint32(a0 + i)] = int(rand() * 256) setreg(10, a1) } 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 = rs1 ? int(REG[rs1]) : 0; r2 = rs2 ? int(REG[rs2]) : 0 ur1 = uint32(r1); ur2 = uint32(r2) 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 * P2[shamt]) % 4294967296) else if(f3 == 5 && f7 == 0) setreg(rd, int(ur1 / P2[shamt])) else if(f3 == 5 && f7 == 32) setreg(rd, floor(r1 / P2[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) { r1 = rs1 ? int(REG[rs1]) : 0; r2 = rs2 ? int(REG[rs2]) : 0 if(f3 >= 0 && f3 <= 2) write_mem(r1 + immval, r2, P2[f3]) else trapout(sprintf("Illegal instruction at 0x%X", pc-4)) } function amach_branch(f3, rs1, rs2, immval, r1, r2, ur1, ur2, taken) { r1 = rs1 ? int(REG[rs1]) : 0; r2 = rs2 ? int(REG[rs2]) : 0 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 = rs1 ? int(REG[rs1]) : 0; ur1 = uint32(r1) if(opcode == 3) { # load if(f3 == 0 || f3 == 1 || f3 == 2 || f3 == 4 || f3 == 5) { setreg(rd, read_mem(r1 + immval, P2[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 * P2[shamt]) % 4294967296) # slli else if(f3 == 5 && immval < 1024) setreg(rd, int(ur1 / P2[shamt])) # srli else if(f3 == 5 && immval >= 1024) setreg(rd, floor(r1 / P2[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(REG[17], REG[10], REG[11], REG[12], REG[13], REG[14], REG[15]) else if(immval == 1) trapout(sprintf("EBREAK at 0x%X", pc-4)) 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 = rs1 ? int(REG[rs1]) : 0; r2 = rs2 ? int(REG[rs2]) : 0 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, addr, op, f3, b12, r11_7, r9_7, r6_2, r4_2, shamt, imm, base, val) { if (addr in CACHE_COP) { op = CACHE_COP[addr] f3 = CACHE_CF3[addr] b12 = CACHE_CB12[addr] r11_7 = CACHE_CR11_7[addr] r9_7 = CACHE_CR9_7[addr] r6_2 = CACHE_CR6_2[addr] r4_2 = CACHE_CR4_2[addr] shamt = CACHE_CSHAMT[addr] } else { 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 CACHE_COP[addr] = op CACHE_CF3[addr] = f3 CACHE_CB12[addr] = b12 CACHE_CR11_7[addr] = r11_7 CACHE_CR9_7[addr] = r9_7 CACHE_CR6_2[addr] = r6_2 CACHE_CR4_2[addr] = r4_2 CACHE_CSHAMT[addr] = shamt } 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, REG[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((r9_7 ? REG[r9_7] : 0) + 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((r9_7 ? REG[r9_7] : 0) + imm, r4_2 ? REG[r4_2] : 0, 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, (r11_7 ? REG[r11_7] : 0) + 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, REG[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 = r9_7 ? REG[r9_7] : 0 if(imm < 0) imm += 4294967296 setreg(r9_7, int(imm / P2[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((r9_7 ? REG[r9_7] : 0) / P2[shamt])) } else if(val == 2) { # C.ANDI imm = r6_2 + b12 * 32 if(imm >= 32) imm -= 64 setreg(r9_7, bw_and(r9_7 ? REG[r9_7] : 0, 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)) r9_val = r9_7 ? REG[r9_7] : 0 r4_val = r4_2 ? REG[r4_2] : 0 if(imm == 0) setreg(r9_7, r9_val - r4_val) else if(imm == 1) setreg(r9_7, bw_xor(r9_val, r4_val)) else if(imm == 2) setreg(r9_7, bw_or(r9_val, r4_val)) else if(imm == 3) setreg(r9_7, bw_and(r9_val, r4_val)) } } 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) ? ((r9_7 ? REG[r9_7] : 0) == 0) : ((r9_7 ? REG[r9_7] : 0) != 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, (REG[r11_7] * P2[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(REG[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 = r11_7 ? REG[r11_7] : 0 } else setreg(r11_7, r6_2 ? REG[r6_2] : 0) # C.MV } else { if(r6_2 == 0) { if(r11_7 == 0) trapout(sprintf("EBREAK at 0x%X", pc-2)) # C.EBREAK else { # C.JALR imm = r11_7 ? REG[r11_7] : 0 setreg(1, pc) pc = imm } } else setreg(r11_7, (r11_7 ? REG[r11_7] : 0) + (r6_2 ? REG[r6_2] : 0)) # C.ADD } } else if(f3 == 6) { # C.SWSP imm = (r11_7 % 4) * 64 + int(r11_7 / 4) * 4 + b12 * 32 write_mem(REG[2] + imm, r6_2 ? REG[r6_2] : 0, 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, addr, opcode, rd, rs1, rs2, imm, funct3, funct7) { if((instr % 4) != 3) { amach_comp(instr, addr) return } if (addr in CACHE_OPCODE) { opcode = CACHE_OPCODE[addr] rd = CACHE_RD[addr] rs1 = CACHE_RS1[addr] rs2 = CACHE_RS2[addr] imm = CACHE_IMM[addr] funct3 = CACHE_F3[addr] funct7 = CACHE_F7[addr] } else { 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) # Reconstruct/cache final immediate values for caching if(opcode == 3 || opcode == 19 || opcode == 103 || opcode == 115) { imm = imm_sign_ex(rs2 + funct7 * 32) } else if(opcode == 35) { imm = imm_sign_ex(rd + funct7 * 32) } else if(opcode == 99) { imm = rd + (funct7 % 64) * 32 + (rd % 2) * 2047 + int(funct7 / 64) * 4096 imm = imm_sign_ex_b(imm) } else if(opcode == 111) { 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 } else if(opcode == 55 || opcode == 23) { imm = imm * 4096 } CACHE_OPCODE[addr] = opcode CACHE_RD[addr] = rd CACHE_RS1[addr] = rs1 CACHE_RS2[addr] = rs2 CACHE_IMM[addr] = imm CACHE_F3[addr] = funct3 CACHE_F7[addr] = funct7 } 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) } else if(opcode == 35) { # 0x23, store, type S amach_store(funct3, rs1, rs2, imm) } else if(opcode == 99) { # 0x63, branch, type B pc -= 4 amach_branch(funct3, rs1, rs2, imm) } else if(opcode == 111) { # 0x6F, JAL, type J setreg(rd, pc) pc += imm - 4 } else if(opcode == 55) { # 0x37, LUI, type U setreg(rd, imm) } else if(opcode == 23) { # 0x17, AUIPC, type U setreg(rd, imm + 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 NEXT_FD = 3 LAST_SEC = -1 VIRT_NSEC = 0 srand() STDIN_BUF = "" for(i=0; i<=32; i++) P2[i] = 2^i } # Decimal memory collection section /^[[:space:]]*[[:digit:][:space:]]+/ {for(i=1; i<=NF; i++) MEM[pc++] = int($i)} # Execution section END { # ELF detection and parsing # The file has been loaded into MEM starting from original LVA orig_LVA = LVA elf_size = pc - orig_LVA # Verify if it starts with ELF magic (0x7F, 'E', 'L', 'F') and is large enough to contain a header if(elf_size >= 52 && MEM[LVA] == 127 && MEM[LVA+1] == 69 && MEM[LVA+2] == 76 && MEM[LVA+3] == 70) { # Verify class is ELF32, data is LSB (little endian), and machine is RISC-V (243 / 0xf3) if(MEM[LVA+4] != 1 || MEM[LVA+5] != 1 || (MEM[LVA+18] + MEM[LVA+19] * 256) != 243) trapout("Incompatible ELF file (must be RV32 little-endian)") # Read entry point (4 bytes at offset 24) e_entry = MEM[LVA+24] + MEM[LVA+25] * 256 + MEM[LVA+26] * 65536 + MEM[LVA+27] * 16777216 # Read program header table offset (4 bytes at offset 28) e_phoff = MEM[LVA+28] + MEM[LVA+29] * 256 + MEM[LVA+30] * 65536 + MEM[LVA+31] * 16777216 # Read number of program headers (2 bytes at offset 44) e_phnum = MEM[LVA+44] + MEM[LVA+45] * 256 # Read size of program header entry (2 bytes at offset 42) e_phentsize = MEM[LVA+42] + MEM[LVA+43] * 256 # Back up the entire ELF image to ELF_DATA array and clear MEM in the loaded range for(i = 0; i < elf_size; i++) { ELF_DATA[i] = MEM[orig_LVA + i] delete MEM[orig_LVA + i] } # Load segment data into memory for(p = 0; p < e_phnum; p++) { ph_addr = e_phoff + p * e_phentsize p_type = ELF_DATA[ph_addr] + ELF_DATA[ph_addr+1] * 256 + ELF_DATA[ph_addr+2] * 65536 + ELF_DATA[ph_addr+3] * 16777216 # We only load PT_LOAD (p_type == 1) if(p_type == 1) { p_offset = ELF_DATA[ph_addr+4] + ELF_DATA[ph_addr+5] * 256 + ELF_DATA[ph_addr+6] * 65536 + ELF_DATA[ph_addr+7] * 16777216 p_vaddr = ELF_DATA[ph_addr+8] + ELF_DATA[ph_addr+9] * 256 + ELF_DATA[ph_addr+10] * 65536 + ELF_DATA[ph_addr+11] * 16777216 p_filesz = ELF_DATA[ph_addr+16] + ELF_DATA[ph_addr+17] * 256 + ELF_DATA[ph_addr+18] * 65536 + ELF_DATA[ph_addr+19] * 16777216 p_memsz = ELF_DATA[ph_addr+20] + ELF_DATA[ph_addr+21] * 256 + ELF_DATA[ph_addr+22] * 65536 + ELF_DATA[ph_addr+23] * 16777216 # Copy p_filesz bytes from ELF_DATA to MEM for(i = 0; i < p_filesz; i++) MEM[p_vaddr + i] = ELF_DATA[p_offset + i] # Zero-initialize the remaining p_memsz - p_filesz bytes for(i = p_filesz; i < p_memsz; i++) MEM[p_vaddr + i] = 0 } } # Override LVA to the entry point (actual memory start value inside the ELF file) LVA = e_entry } # Setup command-line arguments on the stack n = 0 if(length(CMD_OPTS) > 0) n = split(CMD_OPTS, args, "\001") else { n = 1 args[1] = "amach" } sp = REG[2] for(i = n; i >= 1; i--) { len = length(args[i]) sp = sp - (len + 1) for(j = 1; j <= len; j++) MEM[uint32(sp + j - 1)] = ord(substr(args[i], j, 1)) MEM[uint32(sp + len)] = 0 argv_ptr[i] = sp } sp = sp - (sp % 16) sp = sp - 4 * (n + 3) sp = sp - (sp % 16) write_word(sp, n) for(i = 1; i <= n; i++) write_word(sp + 4 * i, argv_ptr[i]) write_word(sp + 4 * (n + 1), 0) write_word(sp + 4 * (n + 2), 0) setreg(2, sp) memsize = pc - LVA pc = LVA ic = 0 while(pc > -1) { ic++ instr_pc = pc if (instr_pc in CACHE_INSTR) { instr = CACHE_INSTR[instr_pc] pc += CACHE_LEN[instr_pc] } else { instr = MEM[pc++] + 256 * MEM[pc++] if((instr % 4) == 3) { # full instruction instr += 65536 * MEM[pc++] + 16777216 * MEM[pc++] CACHE_LEN[instr_pc] = 4 } else { CACHE_LEN[instr_pc] = 2 } CACHE_INSTR[instr_pc] = instr } amach_exec(instr, instr_pc) # decode and execute } for(fd in FD_PATH) if(FD_DIRTY[fd]) write_fd_to_file(fd) }