commit 011ac73822444155c1e7b9aaa3ce1a9c631a2e3b
Author: Brian Swetland <swetland@frotz.net>
Date: Sun, 27 Dec 2015 03:09:25 -0800
initial
Diffstat:
22 files changed, 2061 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,3 @@
+.*.swp
+out
+synth
diff --git a/Makefile b/Makefile
@@ -0,0 +1,36 @@
+
+SRCS := hdl/testbench.sv
+SRCS += hdl/simram.sv
+SRCS += hdl/cpu/cpu.v hdl/cpu/alu.v hdl/cpu/flags.v hdl/cpu/regfile.v
+
+VERILATOR := VERILATOR_ROOT=/work/verilator /work/verilator/bin/verilator
+
+VOPTS := --top-module testbench --Mdir out --exe ../src/testbench.cpp --cc -CFLAGS -DTRACE --trace
+
+all: out/Vtestbench out/a16
+
+out/Vtestbench: $(SRCS) src/testbench.cpp
+ @mkdir -p out
+ @$(VERILATOR) $(VOPTS) $(SRCS)
+ @make -C out -f Vtestbench.mk
+
+run: out/Vtestbench out/test.hex
+ ./out/Vtestbench -trace out/trace.vcd -dump out/memory.bin -load out/test.hex
+
+out/test.hex: src/test.s out/a16
+ out/a16 src/test.s out/test.hex
+
+#out/test.hex: test.hex
+# cp test.hex out/test.hex
+
+out/a16: src/a16.c src/d16.c
+ @mkdir -p out
+ gcc -Wall -O1 -o out/a16 src/a16.c src/d16.c
+
+out/d16: src/d16.c
+ @mkdir -p out
+ gcc -Wall -O1 -o out/d16 -DSTANDALONE=1 src/d16.c
+
+clean:
+ rm -rf out/
+
diff --git a/hdl/cpu/alu.v b/hdl/cpu/alu.v
@@ -0,0 +1,79 @@
+// Copyright 2015, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+`timescale 1ns / 1ps
+
+`define MINIMAL_ALU
+
+module alu(
+ input [3:0]op,
+ input [DWIDTH-1:0]adata,
+ input [DWIDTH-1:0]bdata,
+ output reg [DWIDTH-1:0]rdata,
+ input [3:0]flags_i,
+ output [3:0]flags_o
+ );
+
+parameter DWIDTH = 16;
+parameter SWIDTH = 4;
+
+wire a_neg = adata[DWIDTH-1];
+wire b_neg = bdata[DWIDTH-1];
+wire r_neg = rdata[DWIDTH-1];
+
+wire N = r_neg;
+wire Z = (rdata == {DWIDTH{1'b0}});
+wire C = (a_neg & b_neg) || ((a_neg & b_neg) && !r_neg);
+wire V = !(a_neg ^ b_neg) && (a_neg ^ r_neg);
+
+`ifndef MINIMAL_ALU
+wire [DWIDTH-1:0]carry = {{(DWIDTH-1){1'b0}}, (flags_i[1] & op[3])};
+wire [DWIDTH-1:0]add = adata + bdata + carry;
+wire [DWIDTH-1:0]sub = adata - bdata - carry;
+wire [DWIDTH-1:0]bits = (1 << bdata[SWIDTH-1:0]);
+`endif
+
+reg nz;
+reg cv;
+
+`ifdef MINIMAL_ALU
+always @(*) begin
+ case (op[2:0])
+ 3'b000: begin nz = 1'b0; cv = 1'b0; rdata = bdata; end
+ 3'b001: begin nz = 1'b1; cv = 1'b0; rdata = adata & bdata; end
+ 3'b010: begin nz = 1'b1; cv = 1'b0; rdata = adata | bdata; end
+ 3'b011: begin nz = 1'b1; cv = 1'b0; rdata = adata ^ bdata; end
+ 3'b100: begin nz = 1'b1; cv = 1'b1; rdata = adata + bdata; end
+ 3'b101: begin nz = 1'b1; cv = 1'b1; rdata = adata - bdata; end
+ 3'b110: begin nz = 1'b1; cv = 1'b0; rdata = bdata << 1; end
+ 3'b111: begin nz = 1'b1; cv = 1'b0; rdata = bdata >> 1; end
+ endcase
+end
+`else
+always @(*) begin
+ case (op)
+ 4'b0000: begin nz = 1'b0; cv = 1'b0; rdata = bdata; end
+ 4'b0001: begin nz = 1'b1; cv = 1'b0; rdata = adata & bdata; end
+ 4'b0010: begin nz = 1'b1; cv = 1'b0; rdata = adata | bdata; end
+ 4'b0011: begin nz = 1'b1; cv = 1'b0; rdata = adata ^ bdata; end
+ 4'b0100: begin nz = 1'b1; cv = 1'b1; rdata = add; end
+ 4'b0101: begin nz = 1'b1; cv = 1'b1; rdata = sub; end
+ 4'b0110: begin nz = 1'b1; cv = 1'b0; rdata = bdata << 1; end
+ 4'b0111: begin nz = 1'b1; cv = 1'b0; rdata = bdata >> 1; end
+ 4'b1000: begin nz = 1'b1; cv = 1'b1; rdata = add; end // adc
+ 4'b1001: begin nz = 1'b1; cv = 1'b1; rdata = sub; end // sbc
+ 4'b1010: begin nz = 1'b1; cv = 1'b0; rdata = bdata << 4; end
+ 4'b1011: begin nz = 1'b1; cv = 1'b0; rdata = bdata >> 4; end
+ 4'b1100: begin nz = 1'b1; cv = 1'b0; rdata = adata | bits; end // bis
+ 4'b1101: begin nz = 1'b1; cv = 1'b0; rdata = adata & (~bits); end // bic
+ 4'b1110: begin nz = 1'b1; cv = 1'b0; rdata = adata & bits; end // tbs
+ 4'b1111: begin nz = 1'b1; cv = 1'b0; rdata = adata * bdata; end
+ endcase
+end
+`endif
+
+assign flags_o[3] = nz ? N : flags_i[3];
+assign flags_o[2] = nz ? Z : flags_i[2];
+assign flags_o[1] = cv ? C : flags_i[1];
+assign flags_o[0] = cv ? V : flags_i[0];
+endmodule
diff --git a/hdl/cpu/cpu.v b/hdl/cpu/cpu.v
@@ -0,0 +1,360 @@
+// Copyright 2015, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+`timescale 1ns / 1ps
+
+module cpu(
+ input clk,
+ output [15:0]mem_raddr_o,
+ input [15:0]mem_rdata_i,
+ output [15:0]mem_waddr_o,
+ output [15:0]mem_wdata_o,
+ output mem_wr_o,
+ output mem_rd_o
+ );
+
+parameter RWIDTH = 16;
+parameter SWIDTH = 4;
+
+localparam AWIDTH = 16;
+localparam DWIDTH = 16;
+localparam IWIDTH = 16;
+
+localparam S_DECODE = 2'd0;
+localparam S_IMMEDIATE = 2'd1;
+localparam S_EXEC = 2'd2;
+localparam S_LOAD = 2'd3;
+
+// control signals
+reg do_fetch;
+reg do_load;
+reg do_store;
+reg do_wreg;
+reg do_load_adata;
+reg do_load_bdata;
+reg do_load_flags;
+reg do_load_wsel;
+reg [2:0]do_sel_bdata;
+reg [1:0]do_sel_wsel;
+reg [1:0]do_sel_branch;
+reg do_alu_op_mov; // override ALU opcode
+reg do_1mem_0alu; // select input to regs.wdata
+
+wire ir_cond_true;
+
+// processor registers (fetch unit)
+reg [AWIDTH-1:0]pc = 16'hFFFF; //16'b0;
+reg [15:0]ir = 16'b0;
+reg [15:0]ir_next;
+reg ir_valid = 1'b0;
+reg ir_valid_next;
+reg ir_loading = 1'b0;
+reg ir_loading_next;
+
+// processor registers (main)
+reg cpu_reset = 1'b1;
+reg [1:0]state = S_DECODE;
+reg [1:0]state_next;
+reg [3:0]flags = 4'b0;
+
+// various registers loaded during DECODE for use in EXEC/LOAD/STORE
+reg [3:0]alu_op = 4'b0;
+reg [RWIDTH-1:0]adata = 16'b0;
+reg [RWIDTH-1:0]bdata = 16'b0;
+reg [3:0]wsel = 4'b0;
+
+// in/out of alu
+wire [RWIDTH-1:0]alu_adata = adata;
+wire [RWIDTH-1:0]alu_bdata = bdata;
+wire [RWIDTH-1:0]alu_rdata;
+wire [3:0]alu_flags;
+
+// in/out of reg file
+wire [3:0]regs_asel = ir[11:8];
+wire [3:0]regs_bsel = ir[7:4];
+wire [RWIDTH-1:0]regs_wdata = do_1mem_0alu ? { {(RWIDTH-DWIDTH){1'b0}}, mem_rdata_i } : alu_rdata;
+wire [RWIDTH-1:0]regs_adata;
+wire [RWIDTH-1:0]regs_bdata;
+
+// values computed
+wire [RWIDTH-1:0]ir_imm_s4mem = { {(RWIDTH-4){ir[3]}}, ir[3:0] };
+wire [RWIDTH-1:0]ir_imm_s4alu = { {(RWIDTH-4){ir[7]}}, ir[7:4] };
+wire [RWIDTH-1:0]ir_imm_s8 = { {(RWIDTH-8){ir[7]}}, ir[7:0] };
+wire [RWIDTH-1:0]ir_imm_s12 = { {(RWIDTH-12){ir[11]}}, ir[11:0] };
+
+wire [RWIDTH-1:0]load_store_addr = regs_bdata + ir_imm_s4mem;
+
+// for trace convenience
+`ifdef verilator
+wire [3:0]ir_opcode = ir[15:12];
+wire [3:0]ir_aluop = (ir[15:12] == 4'b0010) ? ir[7:4] : ir[3:0];
+`endif
+
+wire [AWIDTH-1:0]inst_addr = do_load_pc ? pc_next : pc;
+
+// memory interface
+assign mem_wr_o = do_store;
+assign mem_rd_o = 1;
+assign mem_raddr_o = do_load ? load_store_addr[AWIDTH-1:0] : inst_addr;
+assign mem_waddr_o = load_store_addr[AWIDTH-1:0];
+assign mem_wdata_o = adata[AWIDTH-1:0];
+
+
+localparam BR_NONE = 2'b00; // PC + 1 the normal fetch
+localparam BR_REL_S8 = 2'b01; // PC + S8 a short branch
+localparam BR_REL_S12 = 2'b10; // PC + S12 a long branch
+localparam BR_ABS_REG = 2'b11; // [RB] an indirect branch
+
+reg [AWIDTH-1:0]pc_next;
+always @(*) begin
+ case (do_sel_branch)
+ BR_NONE: pc_next = pc + { {(AWIDTH-1){1'b0}}, 1'b1 };
+ BR_REL_S8: pc_next = pc + ir_imm_s8;
+ BR_REL_S12: pc_next = pc + ir_imm_s12;
+ BR_ABS_REG: pc_next = regs_bdata;
+ endcase
+end
+
+reg do_load_pc;
+
+always @(*) begin
+ ir_next = ir;
+ ir_valid_next = ir_valid;
+ ir_loading_next = ir_loading;
+
+ do_load_pc = 1'b0;
+
+ // we try to read an instruction every cycle
+ // unless we're pre-empted by a data load
+ //XXX don't issue a read if we know it's useless?
+ ir_loading_next = ~do_load;
+
+ if (do_sel_branch != BR_NONE) begin
+ // branch is always highest priority
+ ir_valid_next = 1'b0;
+ do_load_pc = 1'b1;
+ end else if (ir_loading) begin
+ // we've read an instruction
+ if ((~ir_valid) | do_fetch) begin
+ // ir was empty or is being consumed
+ // fill it with the just-read instruction
+ // and advance the pc
+ ir_next = mem_rdata_i;
+ ir_valid_next = 1'b1;
+ do_load_pc = 1'b1;
+ end else if (do_fetch) begin
+ // ir has been consumed if it was non-empty
+ ir_valid_next = 1'b0;
+ end
+ end
+end
+
+always @(posedge clk) begin
+ if (cpu_reset) begin
+ pc <= {AWIDTH{1'b0}};
+ ir_valid <= 1'b0;
+ ir_loading <= 1'b0;
+ end else begin
+ pc <= do_load_pc ? pc_next : pc;
+ ir_valid <= ir_valid_next;
+ ir_loading <= ir_loading_next;
+ end
+ ir <= ir_next;
+end
+
+localparam BDATA_RB = 3'b000;
+localparam BDATA_IR = 3'b001;
+localparam BDATA_PC = 3'b010;
+localparam BDATA_S4 = 3'b011;
+localparam BDATA_S8 = 3'b111;
+
+reg [RWIDTH-1:0]bdata_mux;
+always @(*) begin
+ case (do_sel_bdata[1:0])
+ 2'b00: bdata_mux = regs_bdata;
+ 2'b01: bdata_mux = ir;
+ 2'b10: bdata_mux = pc;
+ 2'b11: bdata_mux = do_sel_bdata[2] ? ir_imm_s8 : ir_imm_s4alu;
+ endcase
+end
+
+localparam WSEL_RA = 2'b00;
+localparam WSEL_RB = 2'b01;
+localparam WSEL_OP = 2'b10;
+localparam WSEL_LR = 2'b11;
+
+reg [3:0]wsel_mux;
+always @(*) begin
+ case (do_sel_wsel)
+ WSEL_RA: wsel_mux = ir[11:8];
+ WSEL_RB: wsel_mux = ir[7:4];
+ WSEL_OP: wsel_mux = { 2'b0, ir[13:12] };
+ WSEL_LR: wsel_mux = 4'd14;
+ endcase
+end
+
+always @(*) begin
+ state_next = state;
+
+ // default actions
+ do_fetch = 1'b0;
+ do_load = 1'b0;
+ do_store = 1'b0;
+ do_wreg = 1'b0;
+ do_alu_op_mov = 1'b0;
+ do_1mem_0alu = 1'b0;
+ do_load_adata = 1'b0;
+ do_load_bdata = 1'b0;
+ do_load_flags = 1'b0;
+ do_load_wsel = 1'b0;
+ do_sel_branch = BR_NONE;
+ do_sel_bdata = BDATA_RB;
+ do_sel_wsel = WSEL_RA;
+
+ case (state)
+ S_IMMEDIATE: begin
+ do_fetch = 1'b1;
+ if (ir_valid) begin
+ state_next = S_EXEC;
+ do_sel_bdata = BDATA_IR;
+ do_load_bdata = 1'b1;
+ do_alu_op_mov = 1'b1;
+ end
+ end
+ S_DECODE: begin
+ do_fetch = 1'b1;
+ state_next = S_EXEC;
+ do_sel_wsel = WSEL_RA;
+ do_sel_bdata = BDATA_RB;
+ do_load_adata = 1'b1;
+ do_load_bdata = 1'b1;
+ do_load_wsel = 1'b1;
+ casez ({ir_valid, ir[15:12]})
+ 5'b0????: begin
+ // ir is invalid (wait state)
+ // try again next cycle
+ state_next = S_DECODE;
+ end
+ 5'b10000: begin // alu Ra, Ra, Rb
+ state_next = S_EXEC;
+ end
+ 5'b10001: begin // mov immediate
+ state_next = S_EXEC;
+ do_alu_op_mov = 1'b1;
+ do_sel_bdata = BDATA_S8;
+ end
+ 5'b10010: begin // alu Ra, Ra, imm4
+ state_next = S_EXEC;
+ do_sel_bdata = BDATA_S4;
+ end
+ 5'b10011: begin // alu Rb, Ra, imm16
+ state_next = S_IMMEDIATE;
+ do_sel_wsel = WSEL_RB;
+ end
+ 5'b101??: begin // alu Rd, Ra, Rb
+ state_next = S_EXEC;
+ do_sel_wsel = WSEL_OP;
+ end
+ 5'b11000: begin // lw Ra, [Rb, imm]
+ state_next = S_LOAD;
+ do_load = 1'b1;
+ do_alu_op_mov = 1'b1;
+ end
+ 5'b11001: begin // sw Ra, [Rb, imm]
+ state_next = S_DECODE;
+ do_store = 1'b1;
+ end
+ 5'b11010: begin // bC imm8
+ state_next = S_DECODE;
+ if (ir_cond_true) begin
+ do_sel_branch = BR_REL_S8;
+ end
+ end
+ 5'b11011: begin // bC [Rb] / blC [Rb]
+ state_next = S_DECODE;
+ if (ir_cond_true) begin
+ do_sel_branch = BR_ABS_REG;
+ if (ir[3]) begin
+ // arrange to write PC to LR
+ state_next = S_EXEC;
+ do_sel_bdata = BDATA_PC;
+ do_alu_op_mov = 1'b1;
+ do_sel_wsel = WSEL_LR;
+ end
+ end
+ end
+ 5'b11100: begin // b rel12
+ do_sel_branch = BR_REL_S12;
+ state_next = S_DECODE;
+ end
+ 5'b11101: begin // bl rel12
+ do_sel_branch = BR_REL_S12;
+ // arrange to write PC to LR
+ state_next = S_EXEC;
+ do_sel_bdata = BDATA_PC;
+ do_alu_op_mov = 1'b1;
+ do_sel_wsel = WSEL_LR;
+ end
+ default: begin
+ // treat undefined as NOP
+ state_next = S_DECODE;
+ end
+ endcase
+ end
+ S_EXEC: begin
+ state_next = S_DECODE;
+ do_load_flags = 1'b1;
+ do_wreg = 1'b1;
+ do_1mem_0alu = 1'b0;
+ end
+ S_LOAD: begin
+ state_next = S_DECODE;
+ do_wreg = 1'b1;
+ do_1mem_0alu = 1'b1;
+ end
+ endcase
+end
+
+always @(posedge clk) begin
+ state <= state_next;
+ flags <= do_load_flags ? alu_flags : flags;
+ alu_op <= do_alu_op_mov ? 4'b0000 : ir[3:0];
+ adata <= do_load_adata ? regs_adata : adata;
+ bdata <= do_load_bdata ? bdata_mux : bdata;
+ wsel <= do_load_wsel ? wsel_mux : wsel;
+ cpu_reset <= 1'b0;
+end
+
+regfile #(
+ .DWIDTH(RWIDTH)
+ )regs(
+ .clk(clk),
+ .asel(regs_asel),
+ .bsel(regs_bsel),
+ .adata(regs_adata),
+ .bdata(regs_bdata),
+ .wsel(wsel),
+ .wdata(regs_wdata),
+ .wreg(do_wreg)
+ );
+
+alu #(
+ .DWIDTH(RWIDTH),
+ .SWIDTH(SWIDTH)
+ )alu0(
+ .op(alu_op),
+ .adata(alu_adata),
+ .bdata(alu_bdata),
+ .rdata(alu_rdata),
+ .flags_o(alu_flags),
+ .flags_i(flags)
+ );
+
+check_cond check(
+ .flags_i(flags),
+ .cond_i(ir[11:8]),
+ .is_true_o(ir_cond_true)
+ );
+
+endmodule
+
diff --git a/hdl/cpu/flags.v b/hdl/cpu/flags.v
@@ -0,0 +1,38 @@
+// Copyright 2015, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+`timescale 1ns / 1ps
+
+module check_cond(
+ input [3:0]flags_i,
+ input [3:0]cond_i,
+ output reg is_true_o
+ );
+
+ wire N = flags_i[3];
+ wire Z = flags_i[2];
+ wire C = flags_i[1];
+ wire V = flags_i[0];
+
+always @(*)
+ case (cond_i)
+ 4'b0000: is_true_o = Z; // eq|z
+ 4'b0001: is_true_o = !Z; // ne|nz
+ 4'b0010: is_true_o = C; // cs|hs
+ 4'b0011: is_true_o = !C; // cc|lo
+ 4'b0100: is_true_o = N; // mi
+ 4'b0101: is_true_o = !N; // pl
+ 4'b0110: is_true_o = V; // vs
+ 4'b0111: is_true_o = !V; // vc
+ 4'b1000: is_true_o = V && !Z; // hi
+ 4'b1001: is_true_o = !V || Z; // ls
+ 4'b1010: is_true_o = N == V; // ge
+ 4'b1011: is_true_o = N != V; // lt
+ 4'b1100: is_true_o = !Z && (N == V); // gt
+ 4'b1101: is_true_o = Z || (N != V); // le
+ 4'b1110: is_true_o = 1; // al
+ 4'b1111: is_true_o = 0; // nv
+ endcase
+endmodule
+
+
diff --git a/hdl/cpu/isa.txt b/hdl/cpu/isa.txt
@@ -0,0 +1,60 @@
+
+0000 aaaa bbbb ffff alu Ra, Ra, Rb
+0001 aaaa iiii iiii mov Ra, si8
+0010 aaaa iiii ffff alu Ra, Ra, si4
+0011 aaaa bbbb ffff alu Rb, Ra, si16 (fetches imm after)
+0100 aaaa bbbb ffff alu R0, Ra, Rb
+0101 aaaa bbbb ffff alu R1, Ra, Rb
+0110 aaaa bbbb ffff alu R2, Ra, Rb
+0111 aaaa bbbb ffff alu R3, Ra, Rb
+
+1000 aaaa bbbb iiii lw Ra, [Rb, si4]
+1001 aaaa bbbb iiii sw Ra, [Rb, si4]
+1010 cccc iiii iiii bC rel8
+1010 1111 xxxx xxxx ? undefined
+1011 cccc bbbb 0000 bC Rb
+1011 cccc bbbb 1000 blC Rb
+1011 1111 bbbb 0000 * iret
+1011 1111 xxxx 1000 ? nop
+1011 xxxx xxxx 0001 ? undefined
+1011 nnnn bbbb 0010 * mov Rb, Sn
+1011 nnnn bbbb 0011 * mov Sn, Rb
+1011 nnnn nnnn 0100 * set <flags> (sysflags |= n)
+1011 nnnn nnnn 0101 * clr <flags> (sysflags &= n)
+1011 nnnn nnnn 0110 * syscall n
+1011 nnnn nnnn 0111 break n
+1100 iiii iiii iiii b pc + si12
+1101 iiii iiii iiii bl pc + si12
+1110 aaaa bbbb 0000 * mov Ra', Rb
+1110 aaaa bbbb 0001 * mov Ra, Rb'
+1111 xxxx xxxx xxxx ? undefined
+
+alu opcodes
+-----------
+0000 mov r = b
+0001 and r = a & b
+0010 orr r = a | b
+0011 xor r = a ^ b
+0100 add r = a + b
+0101 sub r = a - b
+0110 >>1 r = b >> 1
+0111 <<1 r = b << 1
+
+1000 adc * r = a + b + c
+1001 sbc * r = a - b - c
+1010 >>4 * r = b >> 4
+1011 <<4 * r = b << 4
+1100 bis * r = a | (1 << b[3:0])
+1101 bic * r = a & (~(1 << b[3:0]))
+1110 tbs * r = a & (1 << b[3:0])
+1111 mul * r = a * b
+
+* = only in "large" version of cpu
+
+r0 - r12 general use
+r13 stack (by convention only)
+r14 link register (for BL ops)
+r15 scratch (for assembler use)
+s0 - s15 system registers
+f0 - f7 system flags
+
diff --git a/hdl/cpu/regfile.v b/hdl/cpu/regfile.v
@@ -0,0 +1,42 @@
+// Copyright 2015, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+`timescale 1ns / 1ps
+
+// Lattice BRAMs are synchronous only, but we want this to work
+// like a register file (but without eating up tons of DFFs), so
+// we invert the read clock, which is supported and gets "good
+// enough" behavior (since the path from regfile -> exec stage
+// register is pretty short)
+
+module regfile(
+ input clk,
+ input [AWIDTH-1:0]asel,
+ input [AWIDTH-1:0]bsel,
+ input [AWIDTH-1:0]wsel,
+ input wreg,
+ output [DWIDTH-1:0]adata,
+ output [DWIDTH-1:0]bdata,
+ input [DWIDTH-1:0]wdata
+ );
+
+parameter AWIDTH = 4;
+parameter DWIDTH = 16;
+
+reg [DWIDTH-1:0] R[0:((1<<AWIDTH)-1)];
+
+always @(posedge clk) begin
+ if (wreg)
+ R[wsel] <= wdata;
+end
+`ifdef NEGSYNC
+always @(negedge clk) begin
+ adata = R[asel];
+ bdata = R[bsel];
+end
+`else
+assign adata = R[asel];
+assign bdata = R[bsel];
+`endif
+
+endmodule
diff --git a/hdl/ice40.v b/hdl/ice40.v
@@ -0,0 +1,88 @@
+// Copyright 2015, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+`timescale 1ns / 1ps
+
+module top(
+ input clk,
+ output [15:0]trace
+ );
+
+wire [15:0]waddr /* synthesis syn_keep=1 */;
+wire [15:0]wdata /* synthesis syn_keep=1 */;
+wire we /* synthesis syn_keep=1 */;
+wire [15:0]raddr /* synthesis syn_keep=1 */;
+wire [15:0]rdata /* synthesis syn_keep=1 */;
+wire re /* synthesis syn_keep=1 */;
+
+cpu #(
+ .RWIDTH(16),
+ .SWIDTH(4)
+ )cpu0(
+ .clk(clk),
+ .mem_waddr_o(waddr),
+ .mem_wdata_o(wdata),
+ .mem_wr_o(we),
+ .mem_raddr_o(raddr),
+ .mem_rdata_i(rdata),
+ .mem_rd_o(re)
+ ) /* synthesis syn_keep=1 */;
+
+assign trace = waddr;
+
+wire cs0r = ~raddr[8];
+wire cs0w = ~waddr[8];
+wire cs1r = raddr[8];
+wire cs1w = waddr[8];
+
+wire [15:0]rdata0;
+wire [15:0]rdata1;
+
+assign rdata = cs0r ? rdata0 : rdata1;
+
+sram ram0(
+ .clk(clk),
+ .raddr(raddr),
+ .rdata(rdata0),
+ .re(re & cs0r),
+ .waddr(waddr),
+ .wdata(wdata),
+ .we(we & cs0w)
+ );
+
+sram ram1(
+ .clk(clk),
+ .raddr(raddr),
+ .rdata(rdata1),
+ .re(re & cs0r),
+ .waddr(waddr),
+ .wdata(wdata),
+ .we(we & cs0w)
+ );
+
+endmodule
+
+module sram(
+ input clk,
+ input [15:0]raddr,
+ output [15:0]rdata,
+ input re,
+ input [15:0]waddr,
+ input [15:0]wdata,
+ input we
+ );
+
+SB_RAM256x16 sram_inst(
+ .RDATA(rdata),
+ .RADDR(raddr[7:0]),
+ .RCLK(clk),
+ .RCLKE(1'b1),
+ .RE(re),
+ .WADDR(waddr[7:0]),
+ .WDATA(wdata),
+ .WCLK(clk),
+ .WCLKE(1'b1),
+ .WE(we)
+ );
+
+endmodule
diff --git a/hdl/nexys4.sv b/hdl/nexys4.sv
@@ -0,0 +1,66 @@
+// Copyright 2015, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+module top(
+ input clk,
+ output reg[15:0]led
+ );
+
+wire [15:0]wdata;
+wire [15:0]waddr;
+wire [15:0]raddr;
+wire [15:0]rdata;
+wire wr;
+wire rd;
+
+assign led = raddr;
+
+sram ram0(
+ .clk(clk),
+ .waddr(waddr),
+ .wdata(wdata),
+ .we(wr),
+ .raddr(raddr),
+ .rdata(rdata),
+ .re(rd)
+ );
+
+cpu
+`ifdef BIGCPU
+ #(
+ .RWIDTH(32),
+ .SWIDTH(5)
+ )
+`endif
+ cpu0(
+ .clk(clk),
+ .mem_raddr_o(raddr),
+ .mem_rdata_i(rdata),
+ .mem_waddr_o(waddr),
+ .mem_wdata_o(wdata),
+ .mem_wr_o(wr),
+ .mem_rd_o(rd)
+ );
+
+endmodule
+
+module sram(
+ input clk,
+ input [15:0]waddr,
+ input [15:0]wdata,
+ input [15:0]raddr,
+ output reg [15:0]rdata,
+ input we,
+ input re
+ );
+
+reg [15:0]mem[0:4095];
+
+always @(posedge clk) begin
+ if (we)
+ mem[waddr[11:0]] <= wdata;
+ if (re)
+ rdata <= mem[raddr[11:0]];
+end
+
+endmodule
diff --git a/hdl/nexys4.xdc b/hdl/nexys4.xdc
@@ -0,0 +1,50 @@
+
+##Bank = 35, Pin name = IO_L12P_T1_MRCC_35, Sch name = CLK100MHZ
+set_property PACKAGE_PIN E3 [get_ports clk]
+set_property IOSTANDARD LVCMOS33 [get_ports clk]
+
+# 100MHz
+create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports clk]
+
+# 250MHz
+#create_clock -add -name sys_clk_pin -period 4.00 -waveform {0 2} [get_ports clk]
+
+# 200MHz
+#create_clock -add -name sys_clk_pin -period 5.00 -waveform {0 2.5} [get_ports clk]
+
+# 150MHz
+#create_clock -add -name sys_clk_pin -period 6.6666 -waveform {0 3.3333} [get_ports clk]
+
+set_property PACKAGE_PIN T8 [get_ports {led[0]}]
+set_property IOSTANDARD LVCMOS33 [get_ports {led[0]}]
+set_property PACKAGE_PIN V9 [get_ports {led[1]}]
+set_property IOSTANDARD LVCMOS33 [get_ports {led[1]}]
+set_property PACKAGE_PIN R8 [get_ports {led[2]}]
+set_property IOSTANDARD LVCMOS33 [get_ports {led[2]}]
+set_property PACKAGE_PIN T6 [get_ports {led[3]}]
+set_property IOSTANDARD LVCMOS33 [get_ports {led[3]}]
+set_property PACKAGE_PIN T5 [get_ports {led[4]}]
+set_property IOSTANDARD LVCMOS33 [get_ports {led[4]}]
+set_property PACKAGE_PIN T4 [get_ports {led[5]}]
+set_property IOSTANDARD LVCMOS33 [get_ports {led[5]}]
+set_property PACKAGE_PIN U7 [get_ports {led[6]}]
+set_property IOSTANDARD LVCMOS33 [get_ports {led[6]}]
+set_property PACKAGE_PIN U6 [get_ports {led[7]}]
+set_property IOSTANDARD LVCMOS33 [get_ports {led[7]}]
+set_property PACKAGE_PIN V4 [get_ports {led[8]}]
+set_property IOSTANDARD LVCMOS33 [get_ports {led[8]}]
+set_property PACKAGE_PIN U3 [get_ports {led[9]}]
+set_property IOSTANDARD LVCMOS33 [get_ports {led[9]}]
+set_property PACKAGE_PIN V1 [get_ports {led[10]}]
+set_property IOSTANDARD LVCMOS33 [get_ports {led[10]}]
+set_property PACKAGE_PIN R1 [get_ports {led[11]}]
+set_property IOSTANDARD LVCMOS33 [get_ports {led[11]}]
+set_property PACKAGE_PIN P5 [get_ports {led[12]}]
+set_property IOSTANDARD LVCMOS33 [get_ports {led[12]}]
+set_property PACKAGE_PIN U1 [get_ports {led[13]}]
+set_property IOSTANDARD LVCMOS33 [get_ports {led[13]}]
+set_property PACKAGE_PIN R2 [get_ports {led[14]}]
+set_property IOSTANDARD LVCMOS33 [get_ports {led[14]}]
+set_property PACKAGE_PIN P2 [get_ports {led[15]}]
+set_property IOSTANDARD LVCMOS33 [get_ports {led[15]}]
+
diff --git a/hdl/simram.sv b/hdl/simram.sv
@@ -0,0 +1,30 @@
+// Copyright 2015, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+`timescale 1ns / 1ps
+
+import "DPI-C" function void dpi_mem_write(integer addr, integer data);
+import "DPI-C" function void dpi_mem_read(integer addr, output integer data);
+
+module simram(
+ input clk,
+ input [15:0]waddr,
+ input [15:0]wdata,
+ input we,
+ input [15:0]raddr,
+ output reg [15:0]rdata,
+ input re
+ );
+
+ wire [31:0]rawdata;
+
+ always @(posedge clk) begin
+ if (we) begin
+ dpi_mem_write({16'd0, waddr}, {16'd0, wdata});
+ end
+ if (re) begin
+ dpi_mem_read({16'd0, raddr}, rawdata);
+ rdata <= rawdata[15:0];
+ end
+ end
+endmodule
diff --git a/hdl/testbench.sv b/hdl/testbench.sv
@@ -0,0 +1,51 @@
+// Copyright 2015, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+`timescale 1ns / 1ps
+
+module testbench(
+ input clk
+ );
+
+reg [15:0]count = 16'd0;
+
+always @(posedge clk) begin
+ count <= count + 16'd1;
+ if (count == 16'hFFFF) $finish;
+end
+
+wire [15:0]wdata;
+wire [15:0]waddr;
+wire [15:0]raddr;
+wire [15:0]rdata;
+wire wr;
+wire rd;
+
+simram dram(
+ .clk(clk),
+ .waddr(waddr),
+ .wdata(wdata),
+ .we(wr),
+ .raddr(raddr),
+ .rdata(rdata),
+ .re(rd)
+ );
+
+cpu
+`ifdef BIGCPU
+ #(
+ .RWIDTH(32),
+ .SWIDTH(5)
+ )
+`endif
+ cpu0(
+ .clk(clk),
+ .mem_raddr_o(raddr),
+ .mem_rdata_i(rdata),
+ .mem_waddr_o(waddr),
+ .mem_wdata_o(wdata),
+ .mem_wr_o(wr),
+ .mem_rd_o(rd)
+ );
+
+endmodule
diff --git a/hdl/uart.v b/hdl/uart.v
@@ -0,0 +1,76 @@
+// Copyright 2015, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+// more like a UAT at the moment...
+
+`timescale 1ns/1ns
+
+module uart(
+ input clk,
+ input [7:0]wdata,
+ output [7:0]rdata,
+ output busy,
+ input we,
+ output tx
+ );
+
+parameter DIVISOR = 416;
+
+reg out = 1'b1;
+reg busy = 0'b1;
+reg [7:0] data = 8'hFF;
+reg [3:0] state = 4'b0010;
+wire next_bit;
+
+uart_bit_counter counter(
+ .clk(clk),
+ .max(DIVISOR),
+ .overflow(next_bit)
+ );
+
+assign tx = out;
+assign rdata = { 7'b0, busy };
+
+always @(posedge clk) begin
+ if (!busy) begin
+ if (we) begin
+ data <= wdata;
+ busy <= 1'b1;
+ end
+ end else if (next_bit) begin
+ case (state)
+ 4'b0000: begin state <= busy ? 4'b0001 : 4'b0000; out <= 1'b1; end
+ 4'b0001: begin state <= 4'b0010; out <= 1'b0; end
+ 4'b0010: begin state <= 4'b0011; out <= data[0]; end
+ 4'b0011: begin state <= 4'b0100; out <= data[1]; end
+ 4'b0100: begin state <= 4'b0101; out <= data[2]; end
+ 4'b0101: begin state <= 4'b0110; out <= data[3]; end
+ 4'b0110: begin state <= 4'b0111; out <= data[4]; end
+ 4'b0111: begin state <= 4'b1000; out <= data[5]; end
+ 4'b1000: begin state <= 4'b1001; out <= data[6]; end
+ 4'b1001: begin state <= 4'b1010; out <= data[7]; end
+ 4'b1010: begin state <= 4'b1011; out <= 1'b1; end
+ 4'b1011: begin state <= 4'b0000; out <= 1'b1; busy <= 1'b0; end
+ endcase
+ end
+end
+
+endmodule
+
+module uart_bit_counter(
+ input clk,
+ input [15:0] max,
+ output overflow
+ );
+
+reg [15:0] count = 16'b0;
+
+assign overflow = (count == max);
+
+always @(posedge clk) begin
+ count <= overflow ? 16'b0 : (count + 16'b1);
+end
+
+endmodule
+
+
diff --git a/src/a16.c b/src/a16.c
@@ -0,0 +1,710 @@
+// Copyright 2015, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <strings.h>
+#include <string.h>
+
+typedef unsigned u32;
+typedef unsigned short u16;
+
+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_signed4(unsigned n) {
+ if (n <= 0x7) return 1;
+ if ((n & 0xFFFFFFF8) == 0xFFFFFFF8) return 1;
+ return 0;
+}
+int is_signed8(unsigned n) {
+ if (n <= 0xFF) return 1;
+ if ((n & 0xFFFFFF80) == 0xFFFFFF80) return 1;
+ return 0;
+}
+int is_signed12(unsigned n) {
+ if (n <= 0x7FF) return 1;
+ if ((n & 0xFFFFF800) == 0xFFFFF800) return 1;
+ return 0;
+}
+int is_signed16(unsigned n) {
+ if (n < 0xFFFF) return 1;
+ if ((n & 0xFFF8000) == 0xFFFF8000) return 1;
+ return 0;
+}
+
+u16 rom[65535];
+u16 PC = 0;
+
+#define TYPE_PCREL_S8 1
+#define TYPE_PCREL_S12 2
+#define TYPE_ABS_U16 3
+
+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;
+
+void fixup_branch(const char *name, int addr, int btarget, int type) {
+ unsigned n;
+
+ switch(type) {
+ case TYPE_PCREL_S8:
+ n = btarget - addr - 1;
+ if (!is_signed8(n)) break;
+ rom[addr] = (rom[addr] & 0xFF00) | (n & 0x00FF);
+ return;
+ case TYPE_PCREL_S12:
+ n = btarget - addr - 1;
+ if (!is_signed12(n)) break;
+ rom[addr] = (rom[addr] & 0xF000) | (n & 0x0FFF);
+ return;
+ case TYPE_ABS_U16:
+ rom[addr] = btarget;
+ return;
+ default:
+ die("unknown branch type %d\n",type);
+ }
+ die("label '%s' at %08x is out of range of %08x\n", name, btarget, addr);
+}
+
+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;
+}
+
+const char *getlabel(unsigned pc) {
+ struct label *l;
+ for (l = labels; l; l = l->next)
+ if (l->pc == pc)
+ return l->name;
+ return 0;
+}
+
+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);
+ }
+ }
+}
+
+int disassemble(char *buf, unsigned pc, unsigned instr, unsigned next_instr);
+
+void emit(unsigned instr) {
+ rom[PC++] = instr;
+}
+
+void save(const char *fn) {
+ const char *name;
+ unsigned n;
+ char dis[128];
+ int next_is_imm = 0;
+
+ FILE *fp = fopen(fn, "w");
+ if (!fp) die("cannot write to '%s'", fn);
+ for (n = 0; n < PC; n++) {
+ if (next_is_imm) {
+ next_is_imm = 0;
+ fprintf(fp, "%04x // %04x:\n", rom[n], n);
+ continue;
+ }
+ if (disassemble(dis, n, rom[n], rom[n+1]) == 2) {
+ next_is_imm = 1;
+ }
+ name = getlabel(n);
+ if (name) {
+ fprintf(fp, "%04x // %04x: %-25s <- %s\n", rom[n], n, dis, name);
+ } else {
+ fprintf(fp, "%04x // %04x: %s\n", rom[n], n, dis);
+ }
+ }
+ fclose(fp);
+}
+
+#define MAXTOKEN 32
+
+enum tokens {
+ tEOL,
+ tCOMMA, tCOLON, tOBRACK, tCBRACK, tDOT, tHASH, tSTRING, tNUMBER,
+ tMOV, tAND, tORR, tXOR, tADD, tSUB, tSHR, tSHL,
+// tADC, tSBC, tSR4, tSL4, tBIS, tBIC, tTBS, tMUL,
+ tLW, tSW, tNOP, tNOT,
+ tBEQ, tBNE, tBCS, tBCC, tBMI, tBPL, tBVS, tBVC,
+ tBHI, tBLS, tBGE, tBLT, tBGT, tBLE, tB, tBNV,
+ tBLEQ, tBLNE, tBLCS, tBLCC, tBLMI, tBLPL, tBLVS, tBLVC,
+ tBLHI, tBLLS, tBLGE, tBLLT, tBLGT, tBLLE, tBL, tBLNV,
+ tBLZ, tBLNZ, tBZ, tBNZ,
+ tR0, tR1, tR2, tR3, tR4, tR5, tR6, tR7,
+ rR8, rR9, rR10, rR11, rR12, tR13, tR14, tR15,
+ tSP, tLR,
+ tEQU, tWORD, tASCII, tASCIIZ,
+ NUMTOKENS,
+};
+
+char *tnames[] = {
+ "<EOL>",
+ ",", ":", "[", "]", ".", "#", "<STRING>", "<NUMBER>",
+ "MOV", "AND", "ORR", "XOR", "ADD", "SUB", "SHR", "SHL",
+// "ADC", "SBC", "SR4", "SL4", "BIS", "BIC", "TBS", "MUL",
+ "LW", "SW", "NOP", "NOT",
+ "BEQ", "BNE", "BCS", "BCC", "BMI", "BPL", "BVS", "BVC",
+ "BHI", "BLS", "BGE", "BLT", "BGT", "BLE", "B", "BNV",
+ "BLEQ", "BLNE", "BLCS", "BLCC", "BLMI", "BLPL", "BLVS", "BLVC",
+ "BLHI", "BLLS", "BLGE", "BLLT", "BLGT", "BLLE", "BL", "BLNV",
+ "BLZ", "BLNZ", "BZ", "BNZ",
+ "R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7",
+ "R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15",
+ "SP", "LR",
+ "EQU", "WORD", "STRING", "ASCIIZ"
+};
+
+#define FIRST_ALU_OP tMOV
+#define LAST_ALU_OP tSHL
+#define FIRST_REGISTER tR0
+#define LAST_REGISTER tLR
+#define FIRST_BR_OP tBEQ
+#define LAST_BR_OP tBNZ
+
+int is_branch(unsigned tok) {
+ return ((tok >= FIRST_BR_OP) && (tok <= LAST_BR_OP));
+}
+
+#define COND_EQ 0
+#define COND_NE 1
+#define COND_AL 14
+#define COND_NV 15
+
+unsigned to_cc(unsigned tok) {
+ switch (tok) {
+ case tBNZ: case tBLNZ:
+ return COND_NE;
+ case tB: case tBL:
+ return COND_AL;
+ default:
+ return (tok - FIRST_BR_OP) & 15;
+ }
+}
+
+int is_branch_link(unsigned tok) {
+ return ((tok >= tBLEQ) && (tok <= tBLNZ));
+}
+
+int is_conditional(unsigned tok) {
+ if (is_branch(tok)) {
+ if ((tok == tB) | (tok == tBL)) {
+ return 0;
+ } else {
+ return 1;
+ }
+ } else {
+ return 0;
+ }
+}
+
+int is_reg(unsigned tok) {
+ return ((tok >= FIRST_REGISTER) && (tok <= LAST_REGISTER));
+}
+
+int is_alu_op(unsigned tok) {
+ return ((tok >= FIRST_ALU_OP) && (tok <= LAST_ALU_OP));
+}
+
+unsigned to_func(unsigned tok) {
+ return tok - FIRST_ALU_OP;
+}
+
+unsigned to_reg(unsigned tok) {
+ if (tok == tLR) return 14;
+ if (tok == tSP) return 13;
+ 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 '.':
+ case '"':
+ case '#':
+ return 1;
+ default:
+ return 0;
+ }
+}
+int is_eoschar(unsigned x) {
+ switch (x) {
+ case 0:
+ case '\t':
+ case '\r':
+ 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;
+ case '#':
+ str[count] = "#";
+ num[count] = 0;
+ tok[count++] = tHASH;
+ line++;
+ continue;
+ case '"':
+ str[count] = ++line;
+ num[count] = 0;
+ tok[count++] = tSTRING;
+ while (!is_eoschar(*line)) line++;
+ if (*line != '"')
+ die("unterminated string");
+ *line++ = 0;
+ 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_reg(got))
+ die("expected register, got %s", tnames[got]);
+}
+
+#define REG(n) (tnames[FIRST_REGISTER + (n)])
+
+// various fields
+#define _OP(n) (((n) & 15) << 12)
+#define _A(n) (((n) & 15) << 8)
+#define _C(n) (((n) & 15) << 8)
+#define _B(n) (((n) & 15) << 4)
+#define _F(n) (((n) & 15) << 0)
+#define _I4ALU(n) (((n) & 15) << 4)
+#define _I4MEM(n) (((n) & 15) << 0)
+#define _I8(n) ((n) & 0xFF)
+#define _I12(n) ((n) & 0x7FF)
+#define _N(n) (((n) & 0xFF) << 4)
+
+#define OP_ALU_RA_RA_RB 0x0000
+#define OP_MOV_RA_S8 0x1000
+#define OP_ALU_RA_RA_S4 0x2000
+#define OP_ALU_RB_RA_U16 0x3000
+#define OP_ALU_R0_RA_RB 0x4000
+#define OP_ALU_R1_RA_RB 0x5000
+#define OP_ALU_R2_RA_RB 0x6000
+#define OP_ALU_R3_RA_RB 0x7000
+#define OP_LW_RB_S4 0x8000
+#define OP_SW_RB_S4 0x9000
+#define OP_B_C_S8 0xA000
+#define OP_B_C_RB 0xB000
+#define OP_BL_C_RB 0xB008
+#define OP_IRET_RB 0xBF00
+#define OP_MOV_RB_SA 0xB002
+#define OP_MOV_SA_RB 0xB003
+#define OP_SET 0xB004
+#define OP_CLR 0xB005
+#define OP_SYSCALL 0xB006
+#define OP_BREAK 0xB007
+#define OP_B_S12 0xC000
+#define OP_BL_S12 0xD000
+#define OP_MOV_XA_RB 0xE000
+#define OP_MOV_RA_XB 0xE001
+#define OP_UNKNONW 0xF000
+
+#define ALU_MOV 0
+#define ALU_AND 1
+#define ALU_ORR 2
+#define ALU_XOR 3
+#define ALU_ADD 4
+#define ALU_SUB 5
+#define ALU_SHR 6
+#define ALU_SHL 7
+
+#define T0 tok[0]
+#define T1 tok[1]
+#define T2 tok[2]
+#define T3 tok[3]
+#define T4 tok[4]
+#define T5 tok[5]
+#define T6 tok[6]
+#define T7 tok[7]
+
+void assemble_line(int n, unsigned *tok, unsigned *num, char **str) {
+ unsigned instr = 0;
+ unsigned tmp;
+ if (T0 == tSTRING) {
+ if (T1 == tCOLON) {
+ setlabel(str[0], PC);
+ tok += 2;
+ num += 2;
+ str += 2;
+ n -= 2;
+ } else {
+ die("unexpected identifier '%s'", str[0]);
+ }
+ }
+
+ switch(T0) {
+ case tEOL:
+ /* blank lines are fine */
+ return;
+ case tNOP:
+ /* MOV r0, r0, r0 */
+ emit(0);
+ return;
+ case tNOT:
+ /* XOR rX, rX, -1 */
+ expect_register(T1);
+ emit(OP_ALU_RA_RA_S4 | _A(to_reg(T1)) | _I4ALU(-1) | ALU_XOR);
+ return;
+ case tMOV:
+ expect_register(T1);
+ expect(tCOMMA, T2);
+ if (is_reg(T3)) {
+ emit(OP_ALU_RA_RA_RB | _A(to_reg(T1)) | _B(to_reg(T3)) | ALU_MOV);
+ return;
+ }
+ expect(tHASH, T3);
+ expect(tNUMBER, T4);
+ if (is_signed8(num[4])) {
+ emit(OP_MOV_RA_S8 | _A(to_reg(T1)) | _I8(num[4]));
+ return;
+ }
+ emit(OP_ALU_RB_RA_U16 | _B(to_reg(T1)) | ALU_MOV);
+ emit(num[4]);
+ return;
+ case tLW:
+ case tSW:
+ instr = (T0 == tLW ? OP_LW_RB_S4 : OP_SW_RB_S4);
+ expect_register(T1);
+ expect(tCOMMA, T2);
+ expect(tOBRACK, T3);
+ expect_register(T4);
+ if (T5 == tCOMMA) {
+ expect(tNUMBER, T6);
+ expect(tCBRACK, T7);
+ tmp = num[6];
+ } else {
+ expect(tCBRACK, T5);
+ tmp = 0;
+ }
+ if (!is_signed8(tmp)) die("index too large");
+ emit(instr | _A(to_reg(T1)) | _B(to_reg(T4)) | _I4MEM(tmp));
+ return;
+ case tWORD:
+ tmp = 1;
+ for (;;) {
+ expect(tNUMBER, tok[tmp]);
+ emit(num[tmp++]);
+ if (tok[tmp] != tCOMMA)
+ break;
+ tmp++;
+ }
+ return;
+ case tASCII:
+ case tASCIIZ: {
+ unsigned n = 0, c = 0;
+ const unsigned char *s = (void*) str[1];
+ expect(tSTRING, tok[1]);
+ while (*s) {
+ n |= ((*s) << (c++ * 8));
+ if (c == 2) {
+ emit(n);
+ n = 0;
+ c = 0;
+ }
+ s++;
+ }
+ emit(n);
+ return;
+ }
+ }
+ if (is_branch(T0)) {
+ unsigned cc = to_cc(T0);
+ int link = is_branch_link(T0);
+ if (is_reg(T1)) {
+ emit((link ? OP_BL_C_RB : OP_B_C_RB) | _B(to_reg(T1)) | _C(cc));
+ } else if (T1 == tSTRING) {
+ if (cc == COND_AL) {
+ emit(link ? OP_BL_S12 : OP_B_S12);
+ uselabel(str[1], PC - 1, TYPE_PCREL_S12);
+ } else {
+ if (link) die("conditional, relative, linked branches unsupported");
+ emit(OP_B_C_S8 | _C(cc));
+ uselabel(str[1], PC - 1, TYPE_PCREL_S8);
+ }
+ } else if (T1 == tDOT) {
+ if (link) die("nope");
+ emit(OP_B_C_S8 | _C(cc) | _I8(-1));
+ }
+ return;
+ }
+ if (is_alu_op(T0)) {
+ expect_register(T1);
+ expect(T2, tCOMMA);
+ expect_register(T3);
+ expect(T4, tCOMMA);
+ if ((T5 == tHASH) && (T6 == tNUMBER)) {
+ if (is_signed4(num[6]) && (T1 == T3)) {
+ emit(OP_ALU_RA_RA_S4 | _A(to_reg(T1)) | _F(to_func(T0)) | _I4ALU(num[6]));
+ return;
+ }
+ emit(OP_ALU_RB_RA_U16 | _B(to_reg(T1)) | _A(to_reg(T3)) | _F(to_func(T0)));
+ emit(num[6]);
+ } else if (is_reg(T5)) {
+ if (T1 == T3) {
+ emit(OP_ALU_RA_RA_RB | _A(to_reg(T1)) | _B(to_reg(T5)) | _F(to_func(T0)));
+ return;
+ }
+ switch (T1) {
+ case tR0: instr = OP_ALU_R0_RA_RB; break;
+ case tR1: instr = OP_ALU_R1_RA_RB; break;
+ case tR2: instr = OP_ALU_R2_RA_RB; break;
+ case tR3: instr = OP_ALU_R3_RA_RB; break;
+ default:
+ die("three-reg ALU ops require R0-R3 for the first register");
+ }
+ emit(instr | _A(to_reg(T3)) | _B(to_reg(T5)) | _F(to_func(T0)));
+ } 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/src/d16.c b/src/d16.c
@@ -0,0 +1,142 @@
+// Copyright 2015, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <strings.h>
+#include <string.h>
+
+typedef unsigned u32;
+typedef unsigned short u16;
+
+char *append(char *buf, const char *s) {
+ while (*s)
+ *buf++ = *s++;
+ return buf;
+}
+char *append_u16(char *buf, unsigned n) {
+ sprintf(buf, "%04x", n & 0xFFFF);
+ return buf + strlen(buf);
+}
+char *append_int(char *buf, int n) {
+ sprintf(buf, "%d", n);
+ return buf + strlen(buf);
+}
+
+const char *condcode[] = {
+ "EQ", "NE", "CS", "CC", "MI", "PL", "VS", "VC",
+ "HI", "LS", "GE", "LT", "GT", "LE", "", "NV",
+};
+
+const char *regname[] = {
+ "R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7",
+ "R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15"
+};
+
+const char *alufunc[] = {
+ "MOV", "AND", "ORR", "XOR", "ADD", "SUB", "SHR", "SHL",
+};
+
+int printinst(char *buf, unsigned pc, unsigned instr, unsigned next, const char *fmt) {
+ int words = 1;
+ unsigned a = (instr >> 8) & 15;
+ unsigned b = (instr >> 4) & 15;
+ unsigned f = (instr >> 0) & 15;
+ int s4alu = (b & 0x8) ? (b | 0xFFFFFFF0) : (b & 0xF);
+ int s4mem = (instr & 0x8) ? (instr | 0xFFFFFFF0) : (instr & 0xF);
+ int s8 = (instr & 0x80) ? (instr | 0xFFFFFF00) : (instr & 0xFF);
+ int s12 = (instr & 0x800) ? (instr | 0xFFFF800) : (instr & 0xFFF);
+
+ while (*fmt) {
+ if (*fmt != '@') {
+ *buf++ = *fmt++;
+ continue;
+ }
+ switch (*++fmt) {
+ case 'A':
+ buf = append(buf, regname[a]);
+ break;
+ case 'B':
+ buf = append(buf, regname[b]);
+ break;
+ case 'C':
+ buf = append(buf, condcode[a]);
+ break;
+ case 'F':
+ buf = append(buf, alufunc[f]);
+ break;
+ case 'f': // alt alu func
+ buf = append(buf, alufunc[b]);
+ break;
+ case 'i':
+ buf = append_int(buf, s4alu);
+ break;
+ case '4':
+ buf = append_int(buf, s4mem);
+ break;
+ case '8':
+ buf = append_int(buf, s8);
+ break;
+ case 's':
+ buf = append_int(buf, s12);
+ break;
+ case 'U':
+ words = 2;
+ buf = append(buf, "0x");
+ buf = append_u16(buf, next);
+ break;
+ case 0:
+ goto done;
+ }
+ fmt++;
+ }
+done:
+ *buf = 0;
+ return words;
+}
+
+struct {
+ u16 mask;
+ u16 value;
+ const char *fmt;
+} decode[] = {
+ { 0b1111111111111111, 0b0000000000000000, "NOP" },
+ { 0b1111000000001111, 0b0000000000000000, "MOV @A, @B" },
+ { 0b1111000000000000, 0b0000000000000000, "@F @A, @A, @B" },
+ { 0b1111000000000000, 0b0001000000000000, "MOV @A, #@8" },
+ { 0b1111000000001111, 0b0010000000000000, "MOV @A, #@i" },
+ { 0b1111000000000000, 0b0010000000000000, "@F @A, @A, #@i" },
+ { 0b1111000000001111, 0b0011000000000000, "MOV @B, #@U" },
+ { 0b1111000000000000, 0b0011000000000000, "@F @B, @A, #@U" },
+ { 0b1111000000001111, 0b0100000000000000, "MOV R0, @B" },
+ { 0b1111000000000000, 0b0100000000000000, "@F R0, @A, @B" },
+ { 0b1111000000001111, 0b0101000000000000, "MOV R1, @B" },
+ { 0b1111000000000000, 0b0101000000000000, "@F R1, @A, @B" },
+ { 0b1111000000001111, 0b0110000000000000, "MOV R2, @B" },
+ { 0b1111000000000000, 0b0110000000000000, "@F R2, @A, @B" },
+ { 0b1111000000001111, 0b0111000000000000, "MOV R3, @B" },
+ { 0b1111000000000000, 0b0111000000000000, "@F R3, @A, @B" },
+ { 0b1111000000001111, 0b1000000000000000, "LW @A, [@B]" },
+ { 0b1111000000000000, 0b1000000000000000, "LW @A, [@B, @4]" },
+ { 0b1111000000001111, 0b1001000000000000, "SW @A, [@B]" },
+ { 0b1111000000000000, 0b1001000000000000, "SW @A, [@B, @4]" },
+ { 0b1111000000000000, 0b1010000000000000, "B@C @8" },
+ { 0b1111000000001000, 0b1011000000000000, "B@C @B" },
+ { 0b1111000000001000, 0b1011000000001000, "BL@C @B" },
+ { 0b1111000000000000, 0b1100000000000000, "B @s" },
+ { 0b1111000000000000, 0b1101000000000000, "BL @s" },
+ { 0b0000000000000000, 0b0000000000000000, "UNDEFINED" },
+};
+
+int disassemble(char *buf, unsigned pc, unsigned instr, unsigned next) {
+ int n = 0;
+ for (n = 0 ;; n++) {
+ if ((instr & decode[n].mask) == decode[n].value) {
+ return printinst(buf, pc, instr, next, decode[n].fmt);
+ }
+ }
+ return 1;
+}
+
diff --git a/src/test.s b/src/test.s
@@ -0,0 +1,32 @@
+ mov r0, #0
+ mov r1, #1
+ mov r2, #2
+ mov r3, #3
+ mov r3, #0xFEED
+
+ add r2, r2, #1
+ add r2, r2, #1
+ add r2, r2, #1
+ add r2, r2, #1
+
+ mov r0, #0xE000
+ mov r1, #0x1234
+ mov r2, #5
+ bl fill
+
+ mov r0, #0xE000
+ mov r1, #0x4321;
+ mov r2, #5
+ bl fill
+
+ mov r0, #0xE000
+ lw r3, [r0]
+ mov r0, #0xDEAD
+ b .
+
+fill: // r0=addr r1=value r2=count
+ sw r1, [r0]
+ add r0, r0, #1
+ sub r2, r2, #1
+ bnz fill
+ b lr
diff --git a/src/testbench.cpp b/src/testbench.cpp
@@ -0,0 +1,165 @@
+/* Copyright 2014 Brian Swetland <swetland@frotz.net>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* reusable verilator testbench driver
+ * - expects the top module to be testbench(clk);
+ * - provides clk to module
+ * - handles vcd tracing if compiled with TRACE
+ * - allows tracefilename to be specified via -o
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "Vtestbench.h"
+#include "verilated.h"
+#include <verilated_vcd_c.h>
+
+static unsigned memory[4096];
+
+void dpi_mem_write(int addr, int data) {
+ memory[addr & 0xFFF] = data;
+}
+
+void dpi_mem_read(int addr, int *data) {
+ *data = (int) memory[addr & 0xFFF];
+}
+
+void loadmem(const char *fn) {
+ unsigned a = 0;
+ FILE *fp = fopen(fn, "r");
+ char buf[128];
+ if (fp == NULL) {
+ fprintf(stderr, "warning: cannot load memory from '%s'\n", fn);
+ return;
+ }
+ while (fgets(buf, 128, fp) != NULL) {
+ unsigned n;
+ char *x = buf;
+ while (isspace(*x)) x++;
+ if (*x == '#') continue;
+ if ((x[0] == '/') && (x[1] == '/')) continue;
+ n = 0;
+ if (x[0] == '.') {
+ x++;
+ while (isdigit(*x)) {
+ n <<= 1;
+ if (*x == '1') {
+ n |= 1;
+ }
+ x++;
+ }
+ } else {
+ sscanf(x, "%x", &n);
+ }
+ memory[a++] = n;
+ if (a == 4096) break;
+ }
+ fprintf(stderr, "loaded %d words from '%s'\n", a, fn);
+}
+
+#ifdef TRACE
+static vluint64_t now = 0;
+
+double sc_time_stamp() {
+ return now;
+}
+#endif
+
+int main(int argc, char **argv) {
+ const char *vcdname = "trace.vcd";
+ const char *memname = NULL;
+ int fd;
+
+ while (argc > 1) {
+ if (!strcmp(argv[1], "-trace")) {
+#ifdef TRACE
+ if (argc < 3) {
+ fprintf(stderr,"error: -trace requires argument\n");
+ return -1;
+ }
+ vcdname = argv[2];
+ argv += 2;
+ argc -= 2;
+ continue;
+#else
+ fprintf(stderr,"error: no trace support\n");
+ return -1;
+#endif
+ } else if (!strcmp(argv[1], "-dump")) {
+ if (argc < 3) {
+ fprintf(stderr, "error: -dump requires argument\n");
+ return -1;
+ }
+ memname = argv[2];
+ argv += 2;
+ argc -= 2;
+ } else if (!strcmp(argv[1], "-load")) {
+ if (argc < 3) {
+ fprintf(stderr, "error: -load requires argument\n");
+ return -1;
+ }
+ loadmem(argv[2]);
+ argv += 2;
+ argc -= 2;
+ } else {
+ break;
+ }
+ }
+
+ Verilated::commandArgs(argc, argv);
+ Verilated::debug(0);
+ Verilated::randReset(2);
+
+ Vtestbench *testbench = new Vtestbench;
+ testbench->clk = 0;
+
+#ifdef TRACE
+ Verilated::traceEverOn(true);
+ VerilatedVcdC* tfp = new VerilatedVcdC;
+ testbench->trace(tfp, 99);
+ tfp->open(vcdname);
+#endif
+
+ while (!Verilated::gotFinish()) {
+ testbench->eval();
+#ifdef TRACE
+ tfp->dump(now);
+ now += 5;
+#endif
+ testbench->clk = !testbench->clk;
+ }
+#ifdef TRACE
+ tfp->close();
+#endif
+ testbench->final();
+ delete testbench;
+
+ if (memname != NULL) {
+ fd = open(memname, O_WRONLY | O_CREAT | O_TRUNC, 0640);
+ if (fd < 0) {
+ fprintf(stderr, "cannot open '%s' for writing\n", memname);
+ return -1;
+ }
+ write(fd, memory, sizeof(memory));
+ close(fd);
+ }
+ return 0;
+}
+
diff --git a/wave/cpu_alu_op.txt b/wave/cpu_alu_op.txt
@@ -0,0 +1,16 @@
+0 MOV
+1 AND
+2 ORR
+3 XOR
+4 ADD
+5 SUB
+6 SHR
+7 SHL
+8 ADC
+9 SBC
+10 SR4
+11 SL4
+12 BIS
+13 BIC
+14 TBS
+15 MUL
diff --git a/wave/cpu_sel_bdata.txt b/wave/cpu_sel_bdata.txt
@@ -0,0 +1,5 @@
+0 RB
+1 IR
+2 PC
+3 S4
+7 S8
diff --git a/wave/cpu_sel_branch.txt b/wave/cpu_sel_branch.txt
@@ -0,0 +1,4 @@
+0 INC
+1 S8
+2 S12
+3 REG
diff --git a/wave/cpu_sel_wsel.txt b/wave/cpu_sel_wsel.txt
@@ -0,0 +1,4 @@
+0 RA
+1 RB
+2 OP
+3 LR
diff --git a/wave/cpu_state.txt b/wave/cpu_state.txt
@@ -0,0 +1,4 @@
+0 DEC
+1 IMM
+2 EXE
+3 LOD