commit a836e0046e8cab27378344b7fab0eae8d31772fd
parent 2b5c59eb3b01c8b74c09a8b46f8fbcafb05451ea
Author: Brian Swetland <swetland@frotz.net>
Date: Sun, 29 Jun 2025 10:34:24 -0700
softrisc32: assembler, emulator, and makefile
Diffstat:
10 files changed, 1223 insertions(+), 0 deletions(-)
diff --git a/softrisc32/.gitignore b/softrisc32/.gitignore
@@ -0,0 +1,3 @@
+.*
+bin/
+gen/
diff --git a/softrisc32/Makefile b/softrisc32/Makefile
@@ -0,0 +1,23 @@
+
+CFLAGS := -g -O2 -Wall -Isrc -Igen
+
+all: bin/asm bin/emu
+
+gen/instab.h: instab.txt bin/mkinstab
+ @mkdir -p gen
+ bin/mkinstab < instab.txt > $@
+
+bin/mkinstab: src/mkinstab.c
+ @mkdir -p bin
+ gcc $(CFLAGS) -o $@ src/mkinstab.c
+
+bin/asm: src/sr32asm.c src/sr32dis.c src/sr32.h gen/instab.h
+ @mkdir -p bin
+ gcc $(CFLAGS) -o $@ src/sr32asm.c src/sr32dis.c
+
+bin/emu: src/sr32emu.c src/sr32cpu.c src/sr32emu.h src/sr32.h
+ @mkdir -p bin
+ gcc $(CFLAGS) -o $@ src/sr32emu.c src/sr32cpu.c
+
+clean:
+ rm -rf gen bin
diff --git a/softrisc32/instab.txt b/softrisc32/instab.txt
@@ -0,0 +1,65 @@
+# Copyright 2018, Brian Swetland <swetland@frotz.net>
+# Licensed under the Apache License, Version 2.0.
+
+00000000000000000000000000000000 nop
+----------------00000-----000000 li %d, %i
+0000000000000000----------000000 mv %d, %a
+--------------------------000000 addi %d, %a, %i
+000000000000--------------010000 add %d, %a, %b
+0000000000000000----------000001 neg %d, %a
+--------------------------000001 subi %d, %a, %i
+000000000000--------------010001 sub %d, %a, %b
+--------------------------000010 andi %d, %a, %i
+000000000000--------------010010 and %d, %a, %b
+--------------------------000011 ori %d, %a, %i
+000000000000--------------010011 or %d, %a, %b
+1111111111111111----------000100 not %d, %a
+--------------------------000100 xori %d, %a, %i
+000000000000--------------010100 xor %d, %a, %b
+--------------------------000101 slli %d, %a, %i
+000000000000--------------010101 sll %d, %a, %b
+--------------------------000110 srli %d, %a, %i
+000000000000--------------010110 srl %d, %a, %b
+--------------------------000111 srai %d, %a, %i
+000000000000--------------010111 sra %d, %a, %b
+--------------------------001000 slti %d, %a, %i
+000000000000--------------011000 slt %d, %a, %b
+0000000000000001----------001001 seqz %d, %a
+--------------------------001001 sltui %d, %a, %i
+000000000000--------------011001 sltu %d, %a, %b
+--------------------------001010 muli %d, %a, %i
+000000000000--------------011010 mul %d, %a, %b
+--------------------------001011 divi %d, %a, %i
+000000000000--------------011011 div %d, %a, %b
+0000000000000000-----00000011111 jr %a
+0000000000000000----------011111 jalr %d, %a
+--------------------------011111 jalr %d, %a, %i
+--------------------------100000 ldw %d, %i(%a)
+--------------------------100001 ldh %d, %i(%a)
+--------------------------100010 ldb %d, %i(%a)
+----------------00000-----100011 ldx %d, %i
+--------------------------100011 ldx %d, %i(%a)
+--------------------------100100 lui %d, %U
+--------------------------100101 ldhu %d, %i(%a)
+--------------------------100110 ldbu %d, %i(%a)
+--------------------------100111 auipc %d, %U
+--------------------------101000 stw %d, %i(%a)
+--------------------------101001 sth %d, %i(%a)
+--------------------------101010 stb %d, %i(%a)
+----------------00000-----101011 stx %d, %i
+--------------------------101011 stx %d, %i(%a)
+---------------------00000110000 bz %a, %B
+--------------------------110000 beq %a, %d, %B
+---------------------00000110001 bnz %a, %B
+--------------------------110001 bne %a, %d, %B
+--------------------------110010 blt %a, %d, %B
+--------------------------110011 bltu %a, %d, %B
+--------------------------110100 bge %a, %d, %B
+--------------------------110101 bgeu %a, %d, %B
+---------------------00000111000 j %d, %J
+---------------------00001111000 call %d, %J
+--------------------------111000 jal %d, %J
+--------------------------111001 syscall %i
+--------------------------111010 break
+--------------------------111011 sysret
+-------------------------------- unknown
diff --git a/softrisc32/src/mkinstab.c b/softrisc32/src/mkinstab.c
@@ -0,0 +1,38 @@
+// Copyright 2025, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <ctype.h>
+
+int main(int argc, char** argv) {
+ char line[128];
+ while (fgets(line, sizeof(line), stdin) != NULL) {
+ unsigned end = strlen(line);
+ while (end > 0) {
+ end--;
+ if (!isspace(line[end])) break;
+ line[end] = 0;
+ }
+ if ((line[0] == 0) || (line[0] == '#') ||
+ isspace(line[0]) || (end < 34)) {
+ continue;
+ }
+ uint32_t mask = 0, bits = 0;
+ for (unsigned n = 0; n < 32; n++) {
+ uint32_t bit = 1U << (31 - n);
+ switch (line[n]) {
+ case '1':
+ mask |= bit;
+ bits |= bit;
+ break;
+ case '0':
+ mask |= bit;
+ break;
+ }
+ }
+ printf("{ 0x%08x, 0x%08x, \"%s\" },\n", mask, bits, line + 33);
+ }
+ return 0;
+}
diff --git a/softrisc32/src/sr32.h b/softrisc32/src/sr32.h
@@ -0,0 +1,82 @@
+// Copyright 2025, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+#pragma once
+#include <stdint.h>
+
+#define IR_ADD 0
+#define IR_SUB 1
+#define IR_AND 2
+#define IR_OR 3
+#define IR_XOR 4
+#define IR_SLL 5
+#define IR_SRL 6
+#define IR_SRA 7
+#define IR_SLT 8
+#define IR_SLTU 9
+#define IR_MUL 10
+#define IR_DIV 11
+#define IR_JALR 15
+
+#define B_BEQ 0
+#define B_BNE 1
+#define B_BLT 2
+#define B_BLTU 3
+#define B_GE 4
+#define B_GEU 5
+
+#define L_LDW 0
+#define L_LDH 1
+#define L_LDB 2
+#define L_LDX 3
+#define L_LUI 4
+#define L_LDHU 5
+#define L_LDBU 6
+#define L_AUIPC 7
+
+#define S_STW 0
+#define S_STH 1
+#define S_STB 2
+#define S_STX 3
+
+#define J_JAL 0
+#define J_SYSCALL 1
+#define J_BREAK 2
+#define J_SYSRET 3
+
+static inline uint32_t ins_i(uint32_t i, uint32_t a, uint32_t t, uint32_t o) {
+ return (i << 16) | ((a & 0x1F) << 11) | ((t & 0x1F) << 6) | (o & 0xF);
+}
+static inline uint32_t ins_r(uint32_t n, uint32_t b, uint32_t a, uint32_t t, uint32_t o) {
+ return ((n & 7) << 21) | ((b & 0x1F) << 16) | ((a & 0x1F) << 11) | ((t & 0x1F) << 6) | (o & 0xF) | 0x10;
+}
+static inline uint32_t ins_l(uint32_t i, uint32_t a, uint32_t t, uint32_t o) {
+ return (i << 16) | ((a & 0x1F) << 11) | ((t & 0x1F) << 6) | (o & 7) | 0x20;
+}
+static inline uint32_t ins_s(uint32_t i, uint32_t a, uint32_t b, uint32_t o) {
+ return (i << 16) | ((a & 0x1F) << 11) | ((b & 0x1F) << 6) | (o & 7) | 0x28;
+}
+static inline uint32_t ins_b(uint32_t i, uint32_t a, uint32_t b, uint32_t o) {
+ return (i << 16) | ((a & 0x1F) << 11) | ((b & 0x1F) << 6) | (o & 7) | 0x30;
+}
+static inline uint32_t ins_j(uint32_t i, uint32_t t, uint32_t o) {
+ return (i << 11) | ((t & 0x1F) << 6) | (o & 7) | 0x38;
+}
+
+static inline uint32_t get_i16(uint32_t ins) {
+ return ((int32_t) ins) >> 16;
+}
+static inline uint32_t get_i21(uint32_t ins) {
+ return ((int32_t) ins) >> 11;
+}
+static inline uint32_t get_rt(uint32_t ins) {
+ return (ins >> 6) & 0x1F;
+}
+static inline uint32_t get_ra(uint32_t ins) {
+ return (ins >> 11) & 0x1F;
+}
+static inline uint32_t get_rb(uint32_t ins) {
+ return (ins >> 16) & 0x1F;
+}
+
+void sr32dis(uint32_t pc, uint32_t ins, char *out);
diff --git a/softrisc32/src/sr32asm.c b/softrisc32/src/sr32asm.c
@@ -0,0 +1,677 @@
+// Copyright 2025, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "sr32.h"
+
+#define RBUFSIZE 4096
+#define SMAXSIZE 1024
+
+static unsigned linenumber = 0;
+static char linestring[256];
+static char *filename;
+
+FILE *ofp = 0;
+
+void die(const char *fmt, ...) {
+ va_list ap;
+ fprintf(stderr,"\n%s:%d: ", filename, linenumber);
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr,"\n");
+ if (linestring[0])
+ fprintf(stderr,"%s:%d: >> %s <<\n", filename, linenumber, linestring);
+ exit(1);
+}
+
+int is_signed16(uint32_t n) {
+ n &= 0xFFFF0000;
+ return ((n == 0) || (n == 0xFFFF0000));
+}
+int is_signed21(uint32_t n) {
+ n &= 0xFFFFF800;
+ return ((n == 0) || (n == 0xFFFFF800));
+}
+
+uint32_t image[1*1024*1024];
+uint32_t image_base = 0;
+uint32_t image_size = 0;
+uint32_t PC = 0;
+
+void wr32(uint32_t addr, uint32_t val) {
+ addr -= image_base;
+ if (addr < image_size) {
+ image[addr >> 2] = val;
+ }
+}
+uint32_t rd32(uint32_t addr) {
+ addr -= image_base;
+ if (addr < image_size) {
+ return image[addr >> 2];
+ }
+ return 0;
+}
+
+#define TYPE_PCREL_S16 1
+#define TYPE_PCREL_S21 2
+#define TYPE_ABS_U32 3
+#define TYPE_LI_U32 4
+
+struct fixup {
+ struct fixup *next;
+ unsigned pc;
+ unsigned type;
+};
+
+struct label {
+ struct label *next;
+ struct fixup *fixups;
+ const char *name;
+ unsigned pc;
+ unsigned defined;
+};
+
+struct label *labels;
+struct fixup *fixups;
+
+uint32_t do_fixup(const char *name, uint32_t addr, uint32_t tgt, int type) {
+ uint32_t n = tgt;
+ //fprintf(stderr,"FIXUP '%s' @%08x val=%08x typ=%d\n", name, addr, n, type);
+ switch(type) {
+ case TYPE_PCREL_S16:
+ n = n - (addr + 4);
+ if (!is_signed16(n)) goto oops;
+ wr32(addr, rd32(addr) | (n << 16));
+ break;
+ case TYPE_PCREL_S21:
+ n = n - (addr + 4);
+ if (!is_signed21(n)) goto oops;
+ wr32(addr, rd32(addr) | (n << 11));
+ break;
+ case TYPE_ABS_U32:
+ wr32(addr, n);
+ break;
+ case TYPE_LI_U32:
+ wr32((addr + 0), rd32(addr + 0) | (n & 0xffff0000));
+ wr32((addr + 4), rd32(addr + 4) | (n & 0x0000ffff) << 16);
+ break;
+ default:
+ die("unknown branch type %d\n", type);
+ }
+ //fprintf(stderr,"FIXUP %08x (%d)\n", n, n);
+ return n;
+oops:
+ die("label '%s' at %08x is out of range of %08x\n", name, tgt, addr);
+ return 0;
+}
+
+void setlabel(const char *name, unsigned pc) {
+ struct label *l;
+ struct fixup *f;
+
+ for (l = labels; l; l = l->next) {
+ if (!strcasecmp(l->name, name)) {
+ if (l->defined) die("cannot redefine '%s'", name);
+ l->pc = pc;
+ l->defined = 1;
+ for (f = l->fixups; f; f = f->next) {
+ do_fixup(name, f->pc, l->pc, f->type);
+ }
+ return;
+ }
+ }
+ l = malloc(sizeof(*l));
+ l->name = name;
+ l->pc = pc;
+ l->fixups = 0;
+ l->defined = 1;
+ l->next = labels;
+ labels = l;
+}
+
+const char *getlabel(unsigned pc) {
+ struct label *l;
+ for (l = labels; l; l = l->next)
+ if (l->pc == pc)
+ return l->name;
+ return 0;
+}
+
+uint32_t uselabel(const char *name, unsigned pc, unsigned type) {
+ struct label *l;
+ struct fixup *f;
+
+ for (l = labels; l; l = l->next) {
+ if (!strcmp(l->name, name)) {
+ if (l->defined) {
+ return do_fixup(name, pc, l->pc, type);
+ } else {
+ goto add_fixup;
+ }
+ }
+ }
+ l = malloc(sizeof(*l));
+ l->name = strdup(name);
+ l->pc = 0;
+ l->fixups = 0;
+ l->defined = 0;
+ l->next = labels;
+ labels = l;
+add_fixup:
+ f = malloc(sizeof(*f));
+ f->pc = pc;
+ f->type = type;
+ f->next = l->fixups;
+ l->fixups = f;
+ return 0;
+}
+
+void checklabels(void) {
+ struct label *l;
+ for (l = labels; l; l = l->next) {
+ if (!l->defined) {
+ die("undefined label '%s'", l->name);
+ }
+ }
+}
+
+void sr32dis(uint32_t pc, uint32_t ins, char *out);
+
+void emit(uint32_t instr) {
+ //fprintf(stderr,"{%08x:%08x} ", PC,instr);
+ wr32(PC, instr);
+ PC += 4;
+}
+
+void save(const char *fn) {
+ const char *name;
+ uint32_t n;
+ char dis[128];
+
+ FILE *fp = fopen(fn, "w");
+ if (!fp) die("cannot write to '%s'", fn);
+ for (n = image_base; n < PC; n += 4) {
+ uint32_t ins = rd32(n);
+ sr32dis(n, ins, dis);
+ name = getlabel(n);
+ char bs[8] = "000000 ";
+ for (unsigned i = 0; i < 6; i++) {
+ if (ins & (1<<i)) bs[5-i] = '1';
+ }
+ if (name) {
+ fprintf(fp, "%08x: %08x // %s %-25s <- %s\n", n, ins, bs, dis, name);
+ } else {
+ fprintf(fp, "%08x: %08x // %s %s\n", n, ins, bs, dis);
+ }
+ }
+ fclose(fp);
+}
+
+enum tokens {
+ tEOF, tEOL, tIDENT, tREGISTER, tNUMBER, tSTRING,
+ tCOMMA, tCOLON, tOPAREN, tCPAREN, tDOT, tHASH,
+ tADD, tSUB, tAND, tOR, tXOR, tSLL, tSRL, tSRA,
+ tSLT, tSLTU, tMUL, tDIV,
+ tADDI, tSUBI, tANDI, tORI, tXORI, tSLLI, tSRLI, tSRAI,
+ tSLTI, tSLTUI, tMULI, tDIVI,
+ tJALR,
+ tBEQ, tBNE, tBLT, tBLTU, tBGE, tBGEU,
+ tLDW, tLDH, tLDB, tLDX, tLUI, tLDHU, tLDBU, tAUIPC,
+ tSTW, tSTH, tSTB, tSTX,
+ tJAL, tSYSCALL, tBREAK, tSYSRET,
+ tNOP, tMV, tLI, tJ, tJR, tRET,
+ tEQU, tBYTE, tHALF, tWORD,
+ NUMTOKENS,
+};
+
+char *tnames[] = { "<EOF>", "<EOL>", "IDENT", "REGISTER", "NUMBER", "STRING",
+ ",", ":", "(", ")", ".", "#",
+ "ADD", "SUB", "AND", "OR", "XOR", "SLL", "SRL", "SRA",
+ "SLT", "SLTU", "MUL", "DIV",
+ "ADDI", "SUBI", "ANDI", "ORI", "XORI", "SLLI", "SRLI", "SRAI",
+ "SLTI", "SLTUI", "MULI", "DIVI",
+ "JALR",
+ "BEQ", "BNE", "BLT", "BLTU", "BGE", "BGEU",
+ "LDW", "LDH", "LDB", "LDX", "LUI", "LDHU", "LDBU", "AUIPC",
+ "STW", "STH", "STB", "STX",
+ "JAL", "SYSCALL", "BREAK", "SYSRET",
+ "NOP", "MV", "LI", "J", "JR", "RET",
+ "EQU", "BYTE", "HALF", "WORD",
+};
+
+static_assert(NUMTOKENS == (sizeof(tnames) / sizeof(tnames[0])),
+ "length of tokens and tnames must be equal");
+
+#define FIRSTKEYWORD tADD
+
+int is_stopchar(unsigned x) {
+ switch (x) {
+ case 0: case ' ': case '\t': case '\r': case '\n':
+ case '.': case ',': case ':': case ';':
+ case '#': case '"': case '/': case '(': case ')':
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+typedef struct State {
+ char *next;
+ unsigned avail;
+ unsigned tok;
+ unsigned num;
+ char *str;
+ int fd;
+ char rbuf[RBUFSIZE];
+ char sbuf[SMAXSIZE + 1];
+} State;
+
+static char* rnames[] = {
+ "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7",
+ "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
+ "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23",
+ "x24", "x25", "x26", "x27", "x28", "x29", "x30", "x31",
+ "zero", "ra", "sp",
+};
+
+// may be called once after nextchar
+void pushback(State *state, unsigned ch) {
+ state->avail++;
+ state->next--;
+ *(state->next) = ch;
+}
+
+unsigned nextchar(State *state) {
+ for (;;) {
+ if (state->avail) {
+ state->avail--;
+ return *(state->next)++;
+ } else {
+ if (state->fd < 0) {
+ return 0;
+ }
+ state->next = state->rbuf;
+ ssize_t n = read(state->fd, state->rbuf, RBUFSIZE);
+ if (n <= 0) {
+ close(state->fd);
+ state->fd = -1;
+ state->avail = 0;
+ return 0;
+ } else {
+ state->avail = n;
+ }
+ }
+ }
+}
+
+unsigned tokenize(State *state) {
+ char *sbuf = state->sbuf;
+ char *s = sbuf;
+ unsigned x, n;
+
+ for (;;) {
+ switch (x = nextchar(state)) {
+ case 0:
+ return tEOF;
+ case ' ': case '\t': case '\r':
+ continue;
+ case '\n':
+ linenumber++;
+ return tEOL;
+ case '/':
+ if (nextchar(state) != '/') {
+ die("stray /");
+ }
+ case ';':
+ for (;;) {
+ x = nextchar(state);
+ if ((x == '\n') || (x == 0)) {
+ linenumber++;
+ return tEOL;
+ }
+ }
+ case ',': return tCOMMA;
+ case ':': return tCOLON;
+ case '(': return tOPAREN;
+ case ')': return tCPAREN;
+ case '.': return tDOT;
+ case '#': return tHASH;
+ case '"':
+ while ((x = nextchar(state)) != '"') {
+ if ((x == '\n') || (x == 0)) {
+ die("unterminated string");
+ }
+ if ((s - sbuf) == SMAXSIZE) {
+ die("string too long");
+ }
+ *s++ = x;
+ }
+ *s++ = 0;
+ state->str = sbuf;
+ return tIDENT;
+ }
+ *s++ = x;
+ while (!is_stopchar(x = nextchar(state))) {
+ if ((s - sbuf) == SMAXSIZE) {
+ die("token too long");
+ }
+ *s++ = x;
+ }
+ *s = 0;
+ pushback(state, x);
+ state->str = sbuf;
+
+ char *end = sbuf;
+ n = strtoul(sbuf, &end, 0);
+ if (*end == '\0') {
+ state->num = n;
+ return tNUMBER;
+ }
+ for (n = 0; n < (sizeof(rnames)/sizeof(rnames[0])); n++) {
+ if (!strcasecmp(sbuf, rnames[n])) {
+ state->str = rnames[n];
+ state->num = n & 31;
+ return tREGISTER;
+ }
+ }
+ if (isalpha(sbuf[0])) {
+ s = sbuf;
+ for (n = FIRSTKEYWORD; n < NUMTOKENS; n++) {
+ if (!strcasecmp(s, tnames[n])) {
+ return n;
+ }
+ }
+ while (*s) {
+ if (!isalnum(*s) && (*s != '_')) {
+ die("invalid character '%c' (%d) in identifier", *s, *s);
+ }
+ s++;
+ }
+ return tIDENT;
+ }
+ die("invalid character '%c' (%d)", s[0], s[0]);
+ }
+ // impossible
+ return tEOF;
+}
+
+unsigned next(State *state) {
+ state->num = 0;
+ state->str = 0;
+ state->tok = tokenize(state);
+ if (state->str == 0) {
+ state->str = tnames[state->tok];
+ }
+#if 0
+ if (state->tok == tNUMBER) {
+ fprintf(stderr, "#%08x ", state->num);
+ } else {
+ fprintf(stderr,"%s ", state->str);
+ }
+ if (state->tok == tEOL) fprintf(stderr,"\n");
+#endif
+ return state->tok;
+}
+
+void require(State *s, unsigned expected) {
+ if (expected != s->tok) {
+ die("expected %s, got %s", tnames[expected], tnames[s->tok]);
+ }
+}
+
+void expect(State *s, unsigned expected) {
+ if (expected != next(s)) {
+ die("expected %s, got %s", tnames[expected], tnames[s->tok]);
+ }
+}
+
+unsigned expect_reg(State *s) {
+ if (next(s) != tREGISTER) {
+ die("expected register, got %s", tnames[s->tok]);
+ }
+ return s->num;
+}
+
+uint32_t expect_num(State *s) {
+ if (next(s) != tNUMBER) {
+ die("expected number, got %s", tnames[s->tok]);
+ }
+ return s->num;
+}
+
+// next() advance to next token
+// expect...() advance to next token and fail if wrong kind
+// require...() fail if current token is wrong kind
+// allow...() advance to next token and consume if it matches
+
+void expect_memref(State *s, uint32_t *r, uint32_t *i) {
+ if (next(s) == tNUMBER) {
+ *i = s->num;
+ next(s);
+ } else {
+ *i = 0;
+ }
+ require(s, tOPAREN);
+ *r = expect_reg(s);
+ expect(s, tCPAREN);
+}
+
+uint32_t expect_rel(State *s, unsigned type) {
+ switch (next(s)) {
+ case tIDENT:
+ return uselabel(s->str, PC, type);
+ case tDOT:
+ return -4;
+ default:
+ die("expected address");
+ return 0;
+ }
+}
+
+void expect_r_c(State *s, uint32_t *one) {
+ *one = expect_reg(s);
+ expect(s, tCOMMA);
+}
+void expect_2r(State *s, uint32_t *one, uint32_t *two) {
+ expect_r_c(s, one);
+ *two = expect_reg(s);
+}
+void expect_2r_c(State *s, uint32_t *one, uint32_t *two) {
+ expect_2r(s, one, two);
+ expect(s, tCOMMA);
+}
+
+int parse_line(State *s) {
+ uint32_t a, b, t, i, o;
+ char *name;
+
+ unsigned tok = next(s);
+ if (tok == tIDENT) {
+ name = strdup(s->str);
+ setlabel(name, PC);
+ if (next(s) != tCOLON) {
+ die("unexpected '%s'\n", name);
+ }
+ tok = next(s);
+ }
+ switch (tok) {
+ case tADD: case tSUB: case tAND: case tOR:
+ case tXOR: case tSLL: case tSRL: case tSRA:
+ case tSLT: case tSLTU:
+ o = tok - tADD;
+ expect_2r_c(s, &t, &a);
+ b = expect_reg(s);
+ emit(ins_r(0, b, a, t, o));
+ break;
+ case tADDI: case tSUBI: case tANDI: case tORI:
+ case tXORI: case tSLLI: case tSRLI: case tSRAI:
+ case tSLTI: case tSLTUI:
+ o = tok - tADDI;
+ expect_2r_c(s, &t, &a);
+ b = expect_num(s);
+ emit(ins_i(b, a, t, o));
+ break;
+ // todo: mul div
+ case tBEQ: case tBNE: case tBLT:
+ case tBLTU: case tBGE: case tBGEU:
+ o = tok - tBEQ;
+ expect_2r_c(s, &a, &b);
+ i = expect_rel(s, TYPE_PCREL_S16);
+ emit(ins_b(i, a, b, o));
+ break;
+ case tLDW: case tLDH: case tLDB: case tLDX:
+ case tLDHU: case tLDBU:
+ o = tok - tLDW;
+ expect_r_c(s, &t);
+ expect_memref(s, &a, &i);
+ emit(ins_l(i, a, t, o));
+ break;
+ case tLUI:
+ case tAUIPC:
+ o = tok - tLDW;
+ expect_r_c(s, &t);
+ i = expect_num(s);
+ emit(ins_l(i >> 16, 0, t, o));
+ break;
+ case tSTW: case tSTH: case tSTB: case tSTX:
+ o = tok - tSTW;
+ expect_r_c(s, &b);
+ expect_memref(s, &a, &i);
+ emit(ins_s(i, a, b, o));
+ break;
+ case tJAL:
+ expect_r_c(s, &t);
+ i = expect_rel(s, TYPE_PCREL_S21);
+ emit(ins_j(i, t, J_JAL));
+ break;
+ case tSYSCALL:
+ i = expect_num(s);
+ emit(ins_j(i, 0, J_SYSCALL));
+ break;
+ case tBREAK:
+ emit(ins_j(0, 0, J_BREAK));
+ break;
+ case tSYSRET:
+ emit(ins_j(0, 0, J_SYSRET));
+ break;
+ case tJALR:
+ expect_2r_c(s, &t, &a);
+ if (s->tok == tNUMBER) {
+ emit(ins_i(s->num, a, t, IR_JALR));
+ } else if (s->tok == tREGISTER) {
+ emit(ins_r(0, s->num, a, t, IR_JALR));
+ } else {
+ die("expected register or immediate");
+ }
+ break;
+ case tJR:
+ a = expect_reg(s);
+ emit(ins_r(0, 0, a, 0, IR_JALR));
+ break;
+ case tRET:
+ emit(ins_r(0, 0, 1, 0, IR_JALR));
+ break;
+ case tNOP:
+ emit(ins_i(0, 0, 0, IR_ADD));
+ break;
+ case tMV:
+ expect_2r(s, &t, &a);
+ emit(ins_r(0, 0, a, t, IR_ADD));
+ break;
+ case tLI:
+ // todo: edge case for bit15 set in lsh
+ expect_r_c(s, &t);
+ i = expect_num(s);
+ if (is_signed16(i)) {
+ emit(ins_i(i, 0, t, IR_ADD));
+ } else {
+ emit(ins_l(i >> 16, 0, t, L_LUI));
+ if (i & 0xFFFF) {
+ emit(ins_i(i & 0xFFFF, t, t, IR_ADD));
+ }
+ }
+ break;
+ case tJ:
+ i = expect_rel(s, TYPE_PCREL_S21);
+ emit(ins_j(i, 0, J_JAL));
+ break;
+ case tEQU:
+ expect(s, tIDENT);
+ name = strdup(s->str);
+ setlabel(name, expect_num(s));
+ break;
+
+ case tWORD:
+ do {
+ switch (next(s)) {
+ case tNUMBER:
+ emit(s->num);
+ break;
+ case tIDENT:
+ emit(0);
+ uselabel(s->str, PC - 4, TYPE_ABS_U32);
+ break;
+ default:
+ die("expected constant or symbol");
+ }
+ } while (next(s) == tCOMMA);
+ goto end_of_line;
+
+ // todo: BYTE, HALF, string data
+ case tEOL:
+ return 1;
+ }
+
+ next(s);
+end_of_line:
+ switch (s->tok) {
+ case tEOF:
+ return 0;
+ case tEOL:
+ return 1;
+ default:
+ die("unexpected %s\n", tnames[tok]);
+ return 0;
+ }
+}
+
+void assemble(const char *fn) {
+ State state;
+ memset(&state, 0, sizeof(state));
+ state.fd = open(fn, O_RDONLY);
+ if (state.fd < 0) {
+ die("cannot open '%s'", fn);
+ }
+ state.next = state.sbuf;
+ linenumber = 1;
+ while (parse_line(&state)) ;
+}
+
+int main(int argc, char **argv) {
+ const char *outname = "out.hex";
+ filename = argv[1];
+
+ image_base = 0x100000;
+ image_size = sizeof(image);
+ PC = image_base;
+
+ if (argc < 2)
+ die("no file specified");
+ if (argc == 3)
+ outname = argv[2];
+
+ assemble(filename);
+ linestring[0] = 0;
+ checklabels();
+ save(outname);
+ return 0;
+}
diff --git a/softrisc32/src/sr32cpu.c b/softrisc32/src/sr32cpu.c
@@ -0,0 +1,108 @@
+// Copyright 2025, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include "sr32emu.h"
+
+void sr32core(CpuState *s) {
+ int32_t a, b, n;
+ uint32_t pc = s->pc;
+ for (;;) {
+ int32_t ins = mem_rd32(pc);
+ //fprintf(stderr,"%08x %08x\n", pc, ins);
+ pc += 4;
+ switch ((ins >> 3) & 7) {
+ case 0b000:
+ case 0b001:
+ case 0b010:
+ case 0b011: // I/R
+ a = s->r[(ins >> 11) & 31];
+ b = ins >> 16;
+ if (ins & 0b010000) b = s->r[b & 31];
+ switch (ins & 15) {
+ case 0x0: n = a + b; break;
+ case 0x1: n = a - b; break;
+ case 0x2: n = a & b; break;
+ case 0x3: n = a | b; break;
+ case 0x4: n = a ^ b; break;
+ case 0x5: n = a << (b & 31); break;
+ case 0x6: n = ((uint32_t)a) >> (b & 31); break;
+ case 0x7: n = a >> (b & 31); break;
+ case 0x8: n = (a < b) ? 1 : 0; break;
+ case 0x9: n = (((uint32_t)a) < ((uint32_t)b)) ? 1 : 0; break;
+ case 0xa: n = a * b; break;
+ case 0xb: n = a / b; break;
+ case 0xf: n = pc; pc = a + b; break;
+ default: goto undef;
+ }
+ b = (ins >> 6) & 31;
+ if (b) s->r[b] = n;
+ if (b) fprintf(stderr,"%08x -> X%d\n", n, b);
+ break;
+ case 0b100: // L
+ a = s->r[(ins >> 11) & 31] + (ins >> 16);
+ switch (ins & 7) {
+ case 0: n = mem_rd32(a); break;
+ case 1: n = mem_rd16(a); if (n & 0x8000) n |= 0xFFFF0000; break;
+ case 2: n = mem_rd8(a); if (n & 0x80) n |= 0xFFFFFF00; break;
+ case 3: n = io_rd32(a); break;
+ case 4: n = ins & 0xFFFF0000; break;
+ case 5: n = mem_rd16(a); break;
+ case 6: n = mem_rd8(a); break;
+ case 7: n = pc + (ins & 0xFFFF0000); break;
+ }
+ b = (ins >> 6) & 31;
+ if (b) s->r[b] = n;
+ if (b) fprintf(stderr,"%08x -> X%d\n", n, b);
+ break;
+ case 0b101: // S
+ a = s->r[(ins >> 11) & 31] + (ins >> 16);
+ b = s->r[(ins >> 6) & 31];
+ switch (ins & 7) {
+ case 0: mem_wr32(a, b); break;
+ case 1: mem_wr16(a, b); break;
+ case 2: mem_wr8(a, b); break;
+ case 3: io_wr32(a, b); break;
+ default: goto undef;
+ }
+ break;
+ case 0b110: // B
+ a = s->r[(ins >> 11) & 31];
+ b = s->r[(ins >> 6) & 31];
+ switch (ins & 7) {
+ case 0: n = (a == b); break;
+ case 1: n = (a != b); break;
+ case 2: n = (a < b); break;
+ case 3: n = (((uint32_t)a) < ((uint32_t)b)); break;
+ case 4: n = (a >= b); break;
+ case 5: n = (((uint32_t)a) >= ((uint32_t)b)); break;
+ default: goto undef;
+ }
+ if (n) pc = pc + (ins >> 16);
+ break;
+ case 0b111: // J
+ switch (ins & 7) {
+ case 0:
+ a = ins >> 11;
+ b = (ins >> 6) & 31;
+ if (b) s->r[b] = pc;
+ pc = pc + a;
+ break;
+ case 1: s->pc = pc; do_syscall(s, ins >> 11); break;
+ //case 1: s->xpc = pc; pc = s->vec_syscall; break;
+ //case 2: s->xpc = pc; pc = s->vec_break; break;
+ //case 3: pc = s->xpc;
+ default: /* undefined instruction */
+undef:
+ s->pc = pc;
+ do_undef(s, ins);
+ return;
+ //s->xpc = pc; pc = s->vec_undef;
+ }
+ break;
+ }
+ }
+}
+
diff --git a/softrisc32/src/sr32dis.c b/softrisc32/src/sr32dis.c
@@ -0,0 +1,61 @@
+// Copyright 2025, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+#include <string.h>
+#include <stdio.h>
+
+#include "sr32.h"
+
+static char *append_str(char *buf, const char *s) {
+ while (*s) *buf++ = *s++;
+ return buf;
+}
+static char *append_i32(char *buf, int32_t n) {
+ return buf + sprintf(buf, "%d", n);
+}
+static char *append_u32(char *buf, int32_t n) {
+ return buf + sprintf(buf, "0x%x", n);
+}
+
+static const char* regname[32] = {
+ "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7",
+ "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
+ "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23",
+ "x24", "x25", "x26", "x27", "x28", "x29", "x30", "x31",
+};
+
+typedef struct {
+ uint32_t mask;
+ uint32_t bits;
+ const char* fmt;
+} sr32ins_t;
+
+static sr32ins_t instab[] = {
+#include <instab.h>
+};
+
+void sr32dis(uint32_t pc, uint32_t ins, char *out) {
+ unsigned n = 0;
+ while ((ins & instab[n].mask) != instab[n].bits) n++;
+ const char* fmt = instab[n].fmt;
+ char c;
+ while ((c = *fmt++) != 0) {
+ if (c != '%') {
+ *out++ = c;
+ continue;
+ }
+ switch (*fmt++) {
+ case 'a': out = append_str(out, regname[get_ra(ins)]); break;
+ case 'b': out = append_str(out, regname[get_rb(ins)]); break;
+ case 'd': out = append_str(out, regname[get_rt(ins)]); break;
+ case 'i': out = append_i32(out, get_i16(ins)); break;
+ case 'u': out = append_u32(out, get_i16(ins)); break;
+ case 'j': out = append_i32(out, get_i21(ins)); break;
+ case 's': out = append_i32(out, get_rb(ins)); break;
+ case 'J': out = append_u32(out, pc + 4 + get_i21(ins)); break;
+ case 'B': out = append_u32(out, pc + get_i16(ins)); break;
+ case 'U': out = append_u32(out, get_i16(ins) << 16); break;
+ }
+ }
+ *out = 0;
+}
diff --git a/softrisc32/src/sr32emu.c b/softrisc32/src/sr32emu.c
@@ -0,0 +1,139 @@
+// Copyright 2025, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "sr32emu.h"
+
+#define RAMSIZE (8*1024*1024)
+#define RAMMASK8 (RAMSIZE - 1)
+#define RAMMASK32 (RAMMASK8 & (~3))
+#define RAMMASK16 (RAMMASK8 & (~1))
+uint8_t emu_ram[RAMSIZE];
+
+uint32_t mem_rd32(uint32_t addr) {
+ return *((uint32_t*) (emu_ram + (addr & RAMMASK32)));
+}
+uint32_t mem_rd16(uint32_t addr) {
+ return *((uint16_t*) (emu_ram + (addr & RAMMASK16)));
+}
+uint32_t mem_rd8(uint32_t addr) {
+ return *((uint8_t*) (emu_ram + (addr & RAMMASK8)));
+}
+
+void mem_wr32(uint32_t addr, uint32_t val) {
+ *((uint32_t*) (emu_ram + (addr & RAMMASK32))) = val;
+}
+void mem_wr16(uint32_t addr, uint32_t val) {
+ *((uint16_t*) (emu_ram + (addr & RAMMASK16))) = val;
+}
+void mem_wr8(uint32_t addr, uint32_t val) {
+ *((uint8_t*) (emu_ram + (addr & RAMMASK8))) = val;
+}
+
+uint32_t io_rd32(uint32_t addr) {
+ return 0;
+}
+
+void io_wr32(uint32_t addr, uint32_t val) {
+ fprintf(stderr,"IO %08x -> %08x\n", val, addr);
+}
+
+void do_syscall(CpuState *s, uint32_t n) {
+}
+
+void do_undef(CpuState *s, uint32_t ins) {
+ fprintf(stderr, "UNDEF INSTR (PC=%08x INS=%08x)\n", s->pc, ins);
+ exit(1);
+}
+
+void load_hex_image(const char* fn) {
+ FILE *fp = fopen(fn, "r");
+ if (fp == NULL) {
+ fprintf(stderr, "emu: cannot open: %s\n", fn);
+ exit(1);
+ }
+ char line[256];
+ while (fgets(line, 256, fp) != NULL) {
+ if ((line[0] == '#') || (line[0] == '/')) {
+ continue;
+ }
+ if ((strlen(line) > 18) && (line[8] == ':')) {
+ uint32_t addr = strtoul(line, 0, 16);
+ uint32_t val = strtoul(line + 10, 0, 16);
+ mem_wr32(addr, val);
+ }
+ }
+ fclose(fp);
+}
+
+int main(int argc, char** argv) {
+ CpuState cs;
+ uint32_t entry = 0x100000;
+ const char* fn = NULL;
+ int args = 0;
+
+
+ while (argc > 1) {
+ if (argv[1][0] == '-') {
+ fprintf(stderr, "emu: unknown option: %s\n", argv[1]);
+ return -1;
+ } else {
+ fn = argv[1];
+ // arguments after the image file belong to the guest
+ args = argc - 2;
+ argv += 2;
+ break;
+ }
+ argc--;
+ argv++;
+ }
+ if (fn == NULL) {
+ fprintf(stderr, "usage: emu <options> <image.hex> <arguments>\n");
+ return -1;
+ }
+
+ memset(&cs, 0, sizeof(cs));
+ memset(emu_ram, 0, sizeof(emu_ram));
+ load_hex_image(fn);
+
+ uint32_t sp = entry - 16;
+ uint32_t lr = sp;
+ mem_wr32(lr + 0, 0xffffffff);
+
+ uint32_t guest_argc = args;
+ uint32_t guest_argv = 0;
+ if (args) {
+ sp -= (args + 1) * 4;
+ uint32_t p = sp;
+ guest_argv = p;
+ while (args > 0) {
+ uint32_t n = strlen(argv[0]) + 1;
+ sp -= (n + 3) & (~3);
+ for (uint32_t i = 0; i < n; i++) {
+ mem_wr8(sp + i, argv[0][i]);
+ }
+ mem_wr32(p, sp);
+ p += 4;
+ args--;
+ argv++;
+ }
+ mem_wr32(p, 0);
+ }
+
+ cs.pc = entry;
+ cs.r[1] = lr;
+ cs.r[2] = sp;
+ cs.r[4] = guest_argc;
+ cs.r[5] = guest_argv;
+
+ fprintf(stderr, "%08x %08x %08x %08x %08x\n",
+ entry, lr, sp, guest_argc, guest_argv);
+ sr32core(&cs);
+
+ return 0;
+}
diff --git a/softrisc32/src/sr32emu.h b/softrisc32/src/sr32emu.h
@@ -0,0 +1,27 @@
+// Copyright 2025, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+#pragma once
+
+#include <stdint.h>
+
+typedef struct {
+ int32_t r[32];
+ uint32_t pc;
+ uint32_t xpc;
+} CpuState;
+
+uint32_t mem_rd32(uint32_t addr);
+uint32_t mem_rd16(uint32_t addr);
+uint32_t mem_rd8(uint32_t addr);
+void mem_wr32(uint32_t addr, uint32_t val);
+void mem_wr16(uint32_t addr, uint32_t val);
+void mem_wr8(uint32_t addr, uint32_t val);
+
+uint32_t io_rd32(uint32_t addr);
+void io_wr32(uint32_t addr, uint32_t val);
+
+void do_syscall(CpuState *s, uint32_t n);
+void do_undef(CpuState *s, uint32_t ins);
+
+void sr32core(CpuState *s);