commit f6f9132373295fa8b9dd5a53d3f2acb1464374da
Author: Brian Swetland <swetland@frotz.net>
Date: Sat, 4 Feb 2012 22:46:59 -0800
initial commit
Diffstat:
A | Makefile | | | 17 | +++++++++++++++++ |
A | a32.c | | | 583 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | alu.v | | | 34 | ++++++++++++++++++++++++++++++++++ |
A | cpu32.v | | | 115 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | isa.txt | | | 66 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | library.v | | | 145 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | ram.v | | | 20 | ++++++++++++++++++++ |
A | regfile.v | | | 32 | ++++++++++++++++++++++++++++++++ |
A | rom.asm | | | 20 | ++++++++++++++++++++ |
A | rom.v | | | 22 | ++++++++++++++++++++++ |
A | testbench.v | | | 68 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
11 files changed, 1122 insertions(+), 0 deletions(-)
diff --git a/Makefile b/Makefile
@@ -0,0 +1,17 @@
+# Copyright 2012, Brian Swetland. Use at your own risk.
+
+SRC := testbench.v cpu32.v alu.v ram.v rom.v regfile.v library.v
+
+all: a32 testbench
+
+testbench: $(SRC) rom.txt
+ iverilog -o testbench $(SRC)
+
+rom.txt: rom.asm
+ ./a32 rom.asm rom.txt
+
+a32: a32.c
+ gcc -g -Wall -o a32 a32.c
+
+clean::
+ rm -f testbench testbench.vcd a32 rom.txt
diff --git a/a32.c b/a32.c
@@ -0,0 +1,583 @@
+// Copyright 2009-2012, Brian Swetland. Use at your own risk.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <strings.h>
+#include <string.h>
+
+static unsigned linenumber = 0;
+static char linestring[256];
+static char *filename;
+
+FILE *ofp = 0;
+
+void die(const char *fmt, ...) {
+ va_list ap;
+ fprintf(stderr,"%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_signed_16(unsigned n) {
+ if (n < 0xFFFF)
+ return 1;
+ if ((n & 0xFFFF0000) == 0xFFFF0000)
+ return 1;
+ return 0;
+}
+
+unsigned rom[65535];
+unsigned PC = 0;
+
+struct fixup {
+ struct fixup *next;
+ unsigned pc;
+ unsigned type; // 16, 24
+};
+
+struct label {
+ struct label *next;
+ struct fixup *fixups;
+ const char *name;
+ unsigned pc;
+ unsigned defined;
+};
+
+struct label *labels;
+struct fixup *fixups;
+
+void fixup_branch(const char *name, int addr, int btarget, int type) {
+ unsigned n;
+
+ n = btarget - addr;
+
+ if (!is_signed_16(n)) {
+ die("label '%s' at %08x is out of range of %08x\n",
+ name, btarget, addr);
+ }
+ rom[addr] = (rom[addr] & 0xFFFF0000) | (n & 0xFFFF);
+}
+
+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) {
+ fixup_branch(name, f->pc, l->pc, f->type);
+ }
+ return;
+ }
+ }
+ l = malloc(sizeof(*l));
+ l->name = strdup(name);
+ l->pc = pc;
+ l->fixups = 0;
+ l->defined = 1;
+ l->next = labels;
+ labels = l;
+}
+
+void uselabel(const char *name, unsigned pc, unsigned type) {
+ struct label *l;
+ struct fixup *f;
+
+ for (l = labels; l; l = l->next) {
+ if (!strcasecmp(l->name, name)) {
+ if (l->defined) {
+ fixup_branch(name, pc, l->pc, type);
+ return;
+ } 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;
+}
+
+void checklabels(void) {
+ struct label *l;
+ for (l = labels; l; l = l->next) {
+ if (!l->defined) {
+ die("undefined label '%s'", l->name);
+ }
+ }
+}
+
+void disassemble(char *buf, unsigned pc, unsigned instr);
+
+void emit(unsigned instr) {
+ rom[PC++] = instr;
+}
+
+void save(const char *fn) {
+ unsigned n;
+ char dis[128];
+ FILE *fp = fopen(fn, "w");
+ if (!fp) die("cannot write to '%s'", fn);
+ for (n = 0; n < PC; n++) {
+ disassemble(dis, n, rom[n]);
+ fprintf(fp, "%08x // %08x: %s\n", rom[n], n, dis);
+ }
+ fclose(fp);
+}
+
+#define MAXTOKEN 32
+
+enum tokens {
+ tEOL,
+ tCOMMA, tCOLON, tOBRACK, tCBRACK, tDOT,
+ tSTRING,
+ tNUMBER,
+ tORR, tAND, tADD, tSUB, tSHL, tSHR, tXOR, tTBS,
+ tSEQ, tSLT, tSGT, tREV, tBIS, tBIC, tMOV, tMHI,
+ tB, tBL, tBZ, tBNZ, tBLZ, tBLNZ, tLW, tSW,
+ tR0, tR1, tR2, tR3, tR4, tR5, tR6, tR7,
+ rR8, tR9, tR10, tR11, tR12, tR13, tR14, tR15,
+ tSP, tLR,
+ tNOP,
+ tEQU,
+ NUMTOKENS,
+};
+
+char *tnames[] = {
+ "<EOL>",
+ ",", ":", "[", "]", ".",
+ "<STRING>",
+ "<NUMBER>",
+ "ORR", "AND", "ADD", "SUB", "SHL", "SHR", "XOR", "TBS",
+ "SEQ", "SLT", "SGT", "REV", "BIS", "BIC", "MOV", "MHI",
+ "B", "BL", "BZ", "BNZ", "BLZ", "BLNZ", "LW", "SW",
+ "R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7",
+ "R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15",
+ "SP", "LR",
+ "NOP",
+ "EQU",
+};
+
+#define FIRST_ALU_OP tORR
+#define LAST_ALU_OP tMHI
+#define FIRST_REGISTER tR0
+#define LAST_REGISTER tR15
+
+int is_register(unsigned tok) {
+ return ((tok >= FIRST_REGISTER) && (tok <= LAST_REGISTER));
+}
+
+int is_alu_op(int token) {
+ return ((token >= FIRST_ALU_OP) && (token <= LAST_ALU_OP));
+}
+
+unsigned to_register(unsigned tok) {
+ if (!is_register(tok)) die("not a register (%s)", tnames[tok]);
+ return tok - FIRST_REGISTER;
+}
+
+int is_stopchar(unsigned x) {
+ switch (x) {
+ case 0:
+ case ' ':
+ case '\t':
+ case '\r':
+ case '\n':
+ case ',':
+ case ':':
+ case '[':
+ case ']':
+ case '.':
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+int tokenize(char *line, unsigned *tok, unsigned *num, char **str) {
+ char *s;
+ int count = 0;
+ unsigned x, n, neg;
+ linenumber++;
+
+ for (;;) {
+ x = *line;
+ again:
+ if (count == 31) die("line too complex");
+
+ switch (x) {
+ case 0:
+ goto alldone;
+ case ' ':
+ case '\t':
+ case '\r':
+ case '\n':
+ line++;
+ continue;
+ case '/':
+ if (line[1] == '/')
+ goto alldone;
+
+ case ',':
+ str[count] = ",";
+ num[count] = 0;
+ tok[count++] = tCOMMA;
+ line++;
+ continue;
+ case ':':
+ str[count] = ":";
+ num[count] = 0;
+ tok[count++] = tCOLON;
+ line++;
+ continue;
+ case '[':
+ str[count] = "[";
+ num[count] = 0;
+ tok[count++] = tOBRACK;
+ line++;
+ continue;
+ case ']':
+ str[count] = "]";
+ num[count] = 0;
+ tok[count++] = tCBRACK;
+ line++;
+ continue;
+ case '.':
+ str[count] = ".";
+ num[count] = 0;
+ tok[count++] = tDOT;
+ line++;
+ continue;
+ }
+
+ s = line++;
+ while (!is_stopchar(*line)) line++;
+
+ /* save the stopchar */
+ x = *line;
+ *line = 0;
+
+ neg = (s[0] == '-');
+ if (neg && isdigit(s[1])) s++;
+
+ str[count] = s;
+ if (isdigit(s[0])) {
+ num[count] = strtoul(s, 0, 0);
+ if(neg) num[count] = -num[count];
+ tok[count++] = tNUMBER;
+ goto again;
+ }
+ if (isalpha(s[0])) {
+ num[count] = 0;
+ for (n = tNUMBER + 1; n < NUMTOKENS; n++) {
+ if (!strcasecmp(s, tnames[n])) {
+ str[count] = tnames[n];
+ tok[count++] = n;
+ goto again;
+ }
+ }
+
+ while (*s) {
+ if (!isalnum(*s) && (*s != '_'))
+ die("invalid character '%c' in identifier", *s);
+ s++;
+ }
+ tok[count++] = tSTRING;
+ goto again;
+ }
+ die("invalid character '%c'", s[0]);
+ }
+
+alldone:
+ str[count] = "";
+ num[count] = 0;
+ tok[count++] = tEOL;
+ return count;
+}
+
+
+void expect(unsigned expected, unsigned got) {
+ if (expected != got)
+ die("expected %s, got %s", tnames[expected], tnames[got]);
+}
+
+void expect_register(unsigned got) {
+ if (!is_register(got))
+ die("expected register, got %s", tnames[got]);
+}
+
+#define REG(n) (tnames[FIRST_REGISTER + (n)])
+
+void disassemble(char *buf, unsigned pc, unsigned instr) {
+ unsigned op = instr >> 28;
+ unsigned fn = (instr >> 24) & 0xF;
+ unsigned opfn = (instr >> 24) & 0xFF;
+ unsigned a = (instr >> 20) & 0xF;
+ unsigned b = (instr >> 16) & 0xF;
+ unsigned d = (instr >> 12) & 0xF;
+ unsigned i16 = instr & 0xFFFF;
+ short s16 = i16;
+
+ /* check for special forms */
+ if (instr == 0) {
+ sprintf(buf, "NOP");
+ return;
+ }
+
+ switch (opfn) {
+ case 0x0B:
+ case 0x1B:
+ sprintf(buf, "REV %s, %s", REG(d), REG(a));
+ break;
+ case 0x0E:
+ sprintf(buf, "MOV %s, %s", REG(d), REG(b));
+ break;
+ case 0x1E:
+ sprintf(buf, "MOV %s, #%d", REG(d), i16);
+ break;
+ case 0x0F:
+ sprintf(buf, "MHI %s, %s", REG(d), REG(b));
+ break;
+ case 0x1F:
+ sprintf(buf, "MHI %s, #%d", REG(d), i16);
+ break;
+ case 0x20:
+ sprintf(buf, "LW %s, [%s, #%d]", REG(b), REG(a), i16);
+ break;
+ case 0x30:
+ sprintf(buf, "SW %s, [%s, #%d]", REG(b), REG(a), i16);
+ break;
+ case 0x41:
+ sprintf(buf, "BZ %s, 0x%08x", REG(a), (pc + s16));
+ break;
+ case 0x42:
+ sprintf(buf, "BNZ %s, 0x%08x", REG(a), (pc + s16));
+ break;
+ case 0x43:
+ sprintf(buf, "B 0x%08x", (pc + s16));
+ break;
+ case 0x49:
+ sprintf(buf, "BLZ %s, 0x%08x", REG(a), (pc + s16));
+ break;
+ case 0x4A:
+ sprintf(buf, "BLNZ %s, 0x%08x", REG(a), (pc + s16));
+ break;
+ case 0x4B:
+ sprintf(buf, "BL 0x%08x", (pc + s16));
+ break;
+ default:
+ if (op == 0) {
+ sprintf(buf, "%-5s%s, %s, %s",
+ tnames[FIRST_ALU_OP + fn], REG(d), REG(a), REG(b));
+ } else if (op == 1) {
+ sprintf(buf, "%-5s%s, %s, #%d",
+ tnames[FIRST_ALU_OP + fn], REG(b), REG(a), i16);
+ return;
+ } else {
+ sprintf(buf, "UND 0x%04x", opfn);
+ }
+ }
+}
+
+#define TO_A(n) (((n) & 0xF) << 20)
+#define TO_B(n) (((n) & 0xF) << 16)
+#define TO_D(n) (((n) & 0xF) << 12)
+#define TO_I16(n) ((n) & 0xFFFF)
+
+void assemble_line(int n, unsigned *tok, unsigned *num, char **str) {
+ unsigned instr = 0;
+ unsigned tmp;
+
+ if (tok[0] == tSTRING) {
+ if (tok[1] == tCOLON) {
+ setlabel(str[0],PC);
+ tok+=2;
+ num+=2;
+ str+=2;
+ n-=2;
+ } else {
+ die("unexpected identifier '%s'", str[0]);
+ }
+ }
+
+ switch(tok[0]) {
+ case tEOL:
+ /* blank lines are fine */
+ return;
+ case tNOP:
+ emit(0x00000000);
+ return;
+ case tMOV:
+ expect_register(tok[1]);
+ expect(tCOMMA,tok[2]);
+ expect(tNUMBER,tok[3]);
+ emit(0x1E000000 | TO_B(to_register(tok[1])) | TO_I16(num[3]));
+ if (num[3] & 0xFFFF0000)
+ emit(0x1F000000 | TO_B(to_register(tok[1])) | (TO_I16(num[3] >> 16)));
+ return;
+ case tMHI:
+ expect_register(tok[1]);
+ expect(tCOMMA,tok[2]);
+ expect(tNUMBER,tok[3]);
+ emit(0x1F000000 | TO_B(to_register(tok[1])) | TO_I16(num[3]));
+ return;
+ case tB:
+ case tBL:
+ if (tok[0] == tB) {
+ instr = 0x43000000;
+ } else {
+ instr = 0x4B000000;
+ }
+ if (tok[1] == tSTRING) {
+ emit(instr);
+ uselabel(str[1], PC - 1, 16);
+ } else if ((tok[1] == tNUMBER) || (tok[1] == tDOT)) {
+ if (!is_signed_16(num[1])) die("branch target out of range");
+ emit(instr | TO_I16(num[1]));
+ } else {
+ die("expected branch target, got %s", tnames[tok[1]]);
+ }
+ return;
+ case tBNZ:
+ case tBZ:
+ case tBLNZ:
+ case tBLZ:
+ switch (tok[0]) {
+ case tBZ: instr = 0x41000000; break;
+ case tBNZ: instr = 0x42000000; break;
+ case tBLZ: instr = 0x49000000; break;
+ case tBLNZ: instr = 0x4A000000; break;
+ }
+ expect_register(tok[1]);
+ expect(tCOMMA,tok[2]);
+ instr |= TO_A(to_register(tok[1]));
+ if (tok[3] == tSTRING) {
+ emit(instr);
+ uselabel(str[3], PC - 1, 16);
+ } else if ((tok[3] == tNUMBER) || (tok[3] == tDOT)) {
+ if (!is_signed_16(num[3])) die("branch target out of range");
+ emit(instr | TO_I16(num[3]));
+ } else {
+ die("expected branch target, got %s", tnames[tok[1]]);
+ }
+ return;
+ case tLW:
+ case tSW:
+ if (tok[0] == tLW) {
+ instr = 0x20000000;
+ } else {
+ instr = 0x30000000;
+ }
+ expect_register(tok[1]);
+ expect(tCOMMA,tok[2]);
+ expect(tOBRACK,tok[3]);
+ expect_register(tok[4]);
+ if (tok[5] == tCOMMA) {
+ expect(tNUMBER, tok[6]);
+ expect(tCBRACK, tok[7]);
+ tmp = num[6];
+ } else {
+ expect(tCBRACK, tok[5]);
+ tmp = 0;
+ }
+ if (!is_signed_16(tmp)) die("index too large");
+ instr |= TO_B(to_register(tok[1])) | TO_A(to_register(tok[4]) | TO_I16(tmp));
+ emit(instr);
+ return;
+ }
+ if (is_alu_op(tok[0])) {
+ expect_register(tok[1]);
+ expect(tok[2],tCOMMA);
+ expect_register(tok[3]);
+ expect(tok[4],tCOMMA);
+
+ instr = ((tok[0] - FIRST_ALU_OP) << 24) | TO_A(tok[3]);
+
+ if (is_register(tok[5])) {
+ emit(instr | TO_B(to_register(tok[5])) | TO_D(to_register(tok[1])));
+ } else if (tok[5] == tNUMBER) {
+ if (num[5] > 65535) die("immediate too large");
+ emit(instr | 0x10000000 | TO_B(to_register(tok[1])) | TO_I16(num[5]));
+ } else {
+ die("expected register or #, got %s", tnames[tok[5]]);
+ }
+ return;
+ }
+
+ die("HUH");
+
+}
+
+void assemble(const char *fn)
+{
+ FILE *fp;
+ char line[256];
+ int n;
+
+ unsigned tok[MAXTOKEN];
+ unsigned num[MAXTOKEN];
+ char *str[MAXTOKEN];
+ char *s;
+
+ fp = fopen(fn, "r");
+ if (!fp) die("cannot open '%s'", fn);
+
+ while (fgets(line, sizeof(line)-1, fp)) {
+ strcpy(linestring, line);
+ s = linestring;
+ while (*s) {
+ if ((*s == '\r') || (*s == '\n')) *s = 0;
+ else s++;
+ }
+ n = tokenize(line, tok, num, str);
+#if DEBUG
+ {
+ int i
+ printf("%04d: (%02d) ", linenumber, n);
+ for (i = 0; i < n; i++)
+ printf("%s ", tnames[tok[i]]);
+ printf("\n");
+ }
+#endif
+ assemble_line(n, tok, num, str);
+ }
+}
+
+
+int main(int argc, char **argv)
+{
+ const char *outname = "out.hex";
+ filename = argv[1];
+
+ 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/alu.v b/alu.v
@@ -0,0 +1,34 @@
+// CPU32 ALU
+//
+// Copyright 2012, Brian Swetland. Use at your own risk.
+
+module alu (
+ input [3:0] opcode,
+ input [31:0] left,
+ input [31:0] right,
+ output reg [31:0] out
+ );
+
+wire [31:0] rbit;
+assign rbit = (1 << right[4:0]);
+
+always @ (*)
+ case (opcode)
+ 4'b0000: out <= (left | right);
+ 4'b0001: out <= (left & right);
+ 4'b0010: out <= (left + right);
+ 4'b0011: out <= (left - right);
+ 4'b0100: out <= (left << right[4:0]);
+ 4'b0101: out <= (left >> right[4:0]);
+ 4'b0110: out <= (left ^ right);
+ 4'b0111: out <= (left & rbit) ? 1 : 0;
+ 4'b1000: out <= (left == right) ? 1 : 0;
+ 4'b1001: out <= (left < right) ? 1 : 0;
+ 4'b1010: out <= (left > right) ? 1 : 0;
+ 4'b1011: out <= 0;
+ 4'b1100: out <= (left | rbit);
+ 4'b1101: out <= (left & ~rbit);
+ 4'b1110: out <= right;
+ 4'b1111: out <= { right[15:0], left[15:0] };
+ endcase
+endmodule
diff --git a/cpu32.v b/cpu32.v
@@ -0,0 +1,115 @@
+// CPU32 Non-pipelined Core
+//
+// Copyright 2012, Brian Swetland. Use at your own risk.
+
+module cpu32 (
+ input clk,
+ output [31:0] i_addr,
+ input [31:0] i_data,
+ output [31:0] d_addr,
+ output [31:0] d_data_w,
+ input [31:0] d_data_r,
+ output d_we
+ );
+
+wire [31:0] ir, pc;
+
+wire [31:0] next_pc, pc_adjust;
+
+wire [3:0] opcode, opfunc, opsela, opselb, opseld;
+wire [15:0] opimm16;
+
+wire [31:0] adata, bdata, wdata, result;
+wire [3:0] alu_wsel;
+
+assign opcode = ir[31:28];
+assign opfunc = ir[27:24];
+assign opsela = ir[23:20];
+assign opselb = ir[19:16];
+assign opseld = ir[15:12];
+assign opimm16 = ir[15:0];
+
+wire ctl_regs_we; // 1 = write back to register file
+wire ctl_d_or_b; // 0 = write to R[opseld], 1 = R[opselb]
+wire ctl_branch; // 1 = immediate branch
+wire ctl_ram_op;
+wire ctl_imm16; // 0 = bdata, 1 = imm16 -> alu right
+
+wire [3:0] ctl_alu_func;
+
+// cheesy decoder -- TODO: write for real
+assign ctl_regs_we = ((opcode[3:1] == 0) || (opcode == 2));
+assign ctl_d_or_b = ((opcode == 1) || (opcode == 2));
+assign ctl_branch = (opcode == 4);
+assign ctl_ram_rd = (opcode == 2);
+assign ctl_ram_we = (opcode == 3);
+assign ctl_ram_op = ((opcode == 2) || (opcode == 3));
+assign ctl_alu_func = ctl_ram_op ? 4'b0010 : opfunc;
+assign ctl_imm16 = (opcode != 0);
+
+register #(32) PC (
+ .clk(clk),
+ .en(1),
+ .din(next_pc),
+ .dout(pc)
+ );
+
+regfile #(32,4) REGS (
+ .clk(clk),
+ .we(ctl_regs_we),
+ .wsel(alu_wsel), .wdata(wdata),
+ .asel(opsela), .adata(adata),
+ .bsel(opselb), .bdata(bdata)
+ );
+
+mux2 #(32) wdata_mux(
+ .sel(ctl_ram_rd),
+ .in0(result),
+ .in1(d_data_r),
+ .out(wdata)
+ );
+
+assign next_pc = pc + pc_adjust;
+
+wire S;
+assign S = opimm16[15];
+
+mux2 #(32) pc_source(
+ .sel(ctl_branch),
+ .in0(4),
+ .in1( {S,S,S,S,S,S,S,S,S,S,S,S,S,S,opimm16,2'h0} ),
+ .out(pc_adjust)
+ );
+
+assign i_addr = pc;
+assign ir = i_data;
+
+wire [31:0] binput;
+
+mux2 #(32) alu_right_mux(
+ .sel(ctl_imm16),
+ .in0(bdata),
+ .in1({ 16'h0, opimm16 }),
+ .out(binput)
+ );
+
+mux2 #(4) alu_wsel_mux(
+ .sel(ctl_d_or_b),
+ .in0(opseld),
+ .in1(opselb),
+ .out(alu_wsel)
+ );
+
+alu alu(
+ .opcode(ctl_alu_func),
+ .left(adata),
+ .right(binput),
+ .out(result)
+ );
+
+// SW operation always writes Rb (aka Rd)
+assign d_addr = result;
+assign d_data_w = bdata;
+assign d_we = ctl_ram_we;
+
+endmodule
diff --git a/isa.txt b/isa.txt
@@ -0,0 +1,66 @@
+CPU32 Instruction Set Architecture
+----------------------------------
+
+Encoding Formats
+----------------
+
+R OOOOFFFFAAAABBBBDDDDXXXXXXXXXXXX register
+I OOOOFFFFAAAADDDDIIIIIIIIIIIIIIII immediate
+L OOOOFFFFIIIIIIIIIIIIIIIIIIIIIIII large (not currently used)
+
+Core Instruction Set
+--------------------
+
+0X ALU Rd, Ra, Rb see ALU ops below
+1X ALU Rd, Ra, #I
+
+20 LW Rd, [Ra, #I] Rd = M(Ra + I) (TBD F->lane/width select)
+30 SW Rd, [Ra, #I] M(Ra + I) = Rd
+
+41 BZ Ra, rel16 if Ra == 0: PC += I
+42 BNZ Ra, rel16 if Ra != 0: PC += I
+43 B rel16 PC += I
+49 BLZ Ra, rel16 if Ra == 0: R15 = PC + 4, PC += I
+4A BLNZ Ra, rel16 if Ra != 0: R15 = PC + 4, PC += I
+4B BL rel16 R15 = PC + 4, PC += I
+
+Extended Instruction Set
+------------------------
+
+51 BZ Ra, [Rb] if Ra == 0: PC = Rb
+52 BNZ Ra, [Rb] if Ra != 0: PC = Rb
+53 B [Rb] PC = Rb
+59 BNZ Ra, [Rb] if Ra == 0: R15 = PC, PC = Rb
+5A BLNZ Ra, [Rb] if Ra != 0: R15 = PC, PC = Rb
+5B BL [Rb] R15 = PC, PC = Rb
+
+60 J rel24 PC = PC + I
+68 JL rel24 R15 = PC + 4, PC = PC + I
+
+ALU Instructions (replace Rb w/ #I for immediate form)
+------------------------------------------------------
+
+X0 OR Rd, Ra, Rb Rd = Ra | Rb
+X1 AND Rd, Ra, Rb Rd = Ra & Rb
+X2 ADD Rd, Ra, Rb Rd = Ra + Rb
+X3 SUB Rd, Ra, Rb Rd = Ra - Rb
+X4 SHL Rd, Ra, Rb Rd = Ra << Rb[0:4]
+X5 SHR Rd, Ra, Rb Rd = Ra >> Rb[0:4]
+X6 XOR Rd, Ra, Rb Rd = Ra ^ Rb
+X7 TBS Rd, Ra, Rb Rd = Ra & (1 << Rb) ? 1 : 0
+
+X8 SEQ Rd, Ra, Rb Rd = Ra == Rb
+X9 SLT Rd, Ra, Rb Rd = Ra < Rb
+XA SGT Rd, Ra, Rb Rd = Ra > Rb
+XB REV Rd, Ra Rd = reversebits(Ra)
+XC BIS Rd, Ra, Rb Rd = Ra | (1 << Rb)
+XD BIC Rd, Ra, Rb Rd = Ra & ~(1 << Rb)
+XE MOV Rd, Rb Rd = Rb
+XF MHI Rd, Ra, Rb Rd = (Ra & 0xFFFF) | (Rb << 16)
+
+Open Issues
+-----------
+- should ALU IMM16 be signed? If so, how do we load unsigned data?
+- consider dropping REV in favor of MOVU or MOVS?
+- figure out func field coding for variant LW/SW instructions
+
diff --git a/library.v b/library.v
@@ -0,0 +1,145 @@
+// Useful tidbits
+//
+// Copyright 2009, Brian Swetland. Use at your own risk.
+
+module decoder2 (
+ input [1:0] in,
+ output out0, out1, out2, out3
+ );
+
+reg [3:0] out;
+
+assign out0 = out[0];
+assign out1 = out[1];
+assign out2 = out[2];
+assign out3 = out[3];
+
+always @ (*)
+ case (in)
+ 2'b00: out = 4'b0001;
+ 2'b01: out = 4'b0010;
+ 2'b10: out = 4'b0100;
+ 2'b11: out = 4'b1000;
+ endcase
+
+endmodule
+
+
+module decoder8 (input [2:0] in, output reg [7:0] out);
+always @ (*)
+ case (in)
+ 3'b000: out = 8'b00000001;
+ 3'b001: out = 8'b00000010;
+ 3'b010: out = 8'b00000100;
+ 3'b011: out = 8'b00001000;
+ 3'b100: out = 8'b00010000;
+ 3'b101: out = 8'b00100000;
+ 3'b110: out = 8'b01000000;
+ 3'b111: out = 8'b10000000;
+ endcase
+endmodule
+
+module decoder8en (input [2:0] in, input en, output reg [7:0] out);
+always @ (*)
+ if (en)
+ case (in)
+ 3'b000: out = 8'b00000001;
+ 3'b001: out = 8'b00000010;
+ 3'b010: out = 8'b00000100;
+ 3'b011: out = 8'b00001000;
+ 3'b100: out = 8'b00010000;
+ 3'b101: out = 8'b00100000;
+ 3'b110: out = 8'b01000000;
+ 3'b111: out = 8'b10000000;
+ endcase
+ else
+ out = 8'b00000000;
+endmodule
+
+
+module mux2 #(parameter WIDTH=16)
+ (
+ input sel,
+ input [WIDTH-1:0] in0, in1,
+ output [WIDTH-1:0] out
+ );
+
+assign out = sel ? in1 : in0 ;
+
+endmodule
+
+module mux4 #(parameter WIDTH=16)
+ (
+ input [1:0] sel,
+ input [WIDTH-1:0] in0,in1,in2,in3,
+ output reg [WIDTH-1:0] out
+ );
+always @ (*)
+ case (sel)
+ 2'b00: out <= in0;
+ 2'b01: out <= in1;
+ 2'b10: out <= in2;
+ 2'b11: out <= in3;
+ endcase
+endmodule
+
+
+module mux8 #(parameter WIDTH=16) (
+ input [2:0] sel,
+ input [WIDTH-1:0] in0,in1,in2,in3,in4,in5,in6,in7,
+ output reg [WIDTH-1:0] out
+ );
+always @ (*)
+ case (sel)
+ 3'b000: out <= in0;
+ 3'b001: out <= in1;
+ 3'b010: out <= in2;
+ 3'b011: out <= in3;
+ 3'b100: out <= in4;
+ 3'b101: out <= in5;
+ 3'b110: out <= in6;
+ 3'b111: out <= in7;
+ endcase
+endmodule
+
+module mux16 #(parameter WIDTH=16) (
+ input [3:0] sel,
+ input [WIDTH-1:0] in00,in01,in02,in03,in04,in05,in06,in07,
+ input [WIDTH-1:0] in08,in09,in10,in11,in12,in13,in14,in15,
+ output reg [WIDTH-1:0] out
+ );
+always @ (*)
+ case (sel)
+ 4'b0000: out <= in00;
+ 4'b0001: out <= in01;
+ 4'b0010: out <= in02;
+ 4'b0011: out <= in03;
+ 4'b0100: out <= in04;
+ 4'b0101: out <= in05;
+ 4'b0110: out <= in06;
+ 4'b0111: out <= in07;
+ 4'b1000: out <= in08;
+ 4'b1001: out <= in09;
+ 4'b1010: out <= in10;
+ 4'b1011: out <= in11;
+ 4'b1100: out <= in12;
+ 4'b1101: out <= in13;
+ 4'b1110: out <= in14;
+ 4'b1111: out <= in15;
+ endcase
+endmodule
+module register #(parameter WIDTH=16) (
+ input clk,
+ input en,
+ input [WIDTH-1:0] din,
+ output [WIDTH-1:0] dout
+ );
+
+reg [WIDTH-1:0] data;
+initial data = 0;
+always @ (posedge clk)
+ if (en)
+ data <= din;
+assign dout = data;
+endmodule
+
diff --git a/ram.v b/ram.v
@@ -0,0 +1,20 @@
+// RAM - Does not instantiate optimally on Altera FPGAs
+//
+// Copyright 2009, Brian Swetland. Use at your own risk.
+
+module ram #(parameter DWIDTH=16, parameter AWIDTH=3) (
+ input clk, input we,
+ input [AWIDTH-1:0] addr,
+ input [DWIDTH-1:0] wdata,
+ output [DWIDTH-1:0] rdata
+ );
+
+reg [DWIDTH-1:0] R[0:2**AWIDTH-1];
+
+always @ (posedge clk)
+ if (we)
+ R[addr] <= wdata;
+
+assign rdata = R[addr];
+
+endmodule
diff --git a/regfile.v b/regfile.v
@@ -0,0 +1,32 @@
+// Dual-reader / Single-writer Register File
+//
+// Copyright 2009, Brian Swetland. Use at your own risk.
+
+module regfile #(parameter DWIDTH=16, parameter AWIDTH=3) (
+ input clk, input we,
+ input [AWIDTH-1:0] wsel, input [DWIDTH-1:0] wdata,
+ input [AWIDTH-1:0] asel, output [DWIDTH-1:0] adata,
+ input [AWIDTH-1:0] bsel, output [DWIDTH-1:0] bdata
+ );
+
+reg [DWIDTH-1:0] R[0:2**AWIDTH-1];
+
+initial begin
+ R[0] <= 0;
+ R[1] <= 0;
+ R[2] <= 0;
+ R[3] <= 0;
+ R[4] <= 0;
+ R[5] <= 0;
+ R[6] <= 0;
+ R[7] <= 0;
+ end
+
+always @ (posedge clk)
+ if (we)
+ R[wsel] <= wdata;
+
+assign adata = R[asel];
+assign bdata = R[bsel];
+
+endmodule
diff --git a/rom.asm b/rom.asm
@@ -0,0 +1,20 @@
+NOP
+MOV R0, 0x5038
+MHI R14, 0x7777
+MOV R14, 0
+SW R0, [R14, 0x10]
+MOV R15, 0x2222
+NOP
+
+MOV R15, 0xFFD20000
+MOV R0, 0
+bigloop:
+MOV R1, 65536
+loop:
+SUB R1, R1, 1
+BNZ R1, loop
+SW R0, [R15]
+ADD R0, R0, 1
+B bigloop
+
+
diff --git a/rom.v b/rom.v
@@ -0,0 +1,22 @@
+// ROM
+//
+// Copyright 2009, Brian Swetland. Use at your own risk.
+
+`timescale 1ns/1ns
+
+module rom #(parameter DWIDTH=16, parameter AWIDTH=8) (
+ input clk,
+ input [AWIDTH-1:0] addr,
+ output [DWIDTH-1:0] data
+ );
+
+ reg [DWIDTH-1:0] rom[0:2**AWIDTH-1];
+
+ initial
+ $readmemh("rom.txt", rom);
+
+ assign data = rom[addr];
+endmodule
+
+
+
diff --git a/testbench.v b/testbench.v
@@ -0,0 +1,68 @@
+// CPU32 Test Bench - For testing in iverilog.
+//
+// Copyright 2012, Brian Swetland. Use at your own risk.
+
+`timescale 1ns/1ns
+
+module testbench;
+
+reg clk, reset;
+wire [31:0] romaddr, romdata, ramaddr, ramrdata, ramwdata;
+wire ramwe;
+
+initial
+ begin
+ reset = 0;
+ #20
+ reset = 1;
+ end
+
+always
+ begin
+ clk = 0;
+ #10 ;
+ clk = 1;
+ #10 ;
+ end
+
+cpu32 cpu(
+ .clk(clk),
+ .i_addr(romaddr),
+ .i_data(romdata),
+ .d_data_r(ramrdata),
+ .d_data_w(ramwdata),
+ .d_addr(ramaddr),
+ .d_we(ramwe)
+ );
+
+rom #(32,8) rom(
+ .addr(romaddr[9:2]),
+ .data(romdata)
+ );
+
+ram #(32,8) ram(
+ .clk(clk),
+ .addr(ramaddr[9:2]),
+ .rdata(ramrdata),
+ .wdata(ramwdata),
+ .we(ramwe)
+ );
+
+initial begin
+ $dumpfile("testbench.vcd");
+ $dumpvars(0,testbench);
+end
+
+initial #400 $finish;
+
+initial
+ $monitor("%05t: pc=%h ir=%h R> %h %h %h %h",
+ $time, cpu.pc, cpu.ir,
+ cpu.REGS.R[0],
+ cpu.REGS.R[1],
+ cpu.REGS.R[2],
+ cpu.REGS.R[3]
+ );
+
+endmodule
+