spl

systems programming language
git clone http://frotz.net/git/spl.git
Log | Files | Refs | README | LICENSE

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:
Asoftrisc32/.gitignore | 3+++
Asoftrisc32/Makefile | 23+++++++++++++++++++++++
Asoftrisc32/instab.txt | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asoftrisc32/src/mkinstab.c | 38++++++++++++++++++++++++++++++++++++++
Asoftrisc32/src/sr32.h | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asoftrisc32/src/sr32asm.c | 677+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asoftrisc32/src/sr32cpu.c | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asoftrisc32/src/sr32dis.c | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asoftrisc32/src/sr32emu.c | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asoftrisc32/src/sr32emu.h | 27+++++++++++++++++++++++++++
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);