gateware

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 011ac73822444155c1e7b9aaa3ce1a9c631a2e3b
Author: Brian Swetland <swetland@frotz.net>
Date:   Sun, 27 Dec 2015 03:09:25 -0800

initial

Diffstat:
A.gitignore | 3+++
AMakefile | 36++++++++++++++++++++++++++++++++++++
Ahdl/cpu/alu.v | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahdl/cpu/cpu.v | 360+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahdl/cpu/flags.v | 38++++++++++++++++++++++++++++++++++++++
Ahdl/cpu/isa.txt | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahdl/cpu/regfile.v | 42++++++++++++++++++++++++++++++++++++++++++
Ahdl/ice40.v | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahdl/nexys4.sv | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahdl/nexys4.xdc | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Ahdl/simram.sv | 30++++++++++++++++++++++++++++++
Ahdl/testbench.sv | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Ahdl/uart.v | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/a16.c | 710+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/d16.c | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/test.s | 32++++++++++++++++++++++++++++++++
Asrc/testbench.cpp | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awave/cpu_alu_op.txt | 16++++++++++++++++
Awave/cpu_sel_bdata.txt | 5+++++
Awave/cpu_sel_branch.txt | 4++++
Awave/cpu_sel_wsel.txt | 4++++
Awave/cpu_state.txt | 4++++
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