416 lines
14 KiB
Verilog
416 lines
14 KiB
Verilog
`timescale 1ns / 1ps
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
|
|
module stackcpu #(parameter ADDR_WIDTH = 32, WIDTH = 32,
|
|
WORDSIZE = 4, WORDSIZE_SHIFT = 2) (
|
|
input wire clk,
|
|
input wire rst,
|
|
|
|
input wire irq,
|
|
|
|
output reg [ADDR_WIDTH-1:0] addr,
|
|
input wire [WIDTH-1:0] data_in,
|
|
output wire read_enable,
|
|
output wire read_ins,
|
|
output wire [WIDTH-1:0] data_out,
|
|
output wire write_enable,
|
|
input wire mem_wait,
|
|
|
|
output wire debug1,
|
|
output wire debug2,
|
|
output wire debug3
|
|
);
|
|
|
|
localparam EVAL_STACK_INDEX_WIDTH = 6;
|
|
|
|
wire reset = !rst;
|
|
|
|
(* KEEP *) reg [1:0] seq_state;
|
|
localparam FETCH = 2'b00; localparam DECODE = 2'b01; localparam EXEC = 2'b10; localparam MEM = 2'b11;
|
|
|
|
(* KEEP*) reg [WIDTH-1:0] X, nX;
|
|
wire [WIDTH-1:0] Y;
|
|
(* KEEP *) reg [WIDTH-1:0] PC, nPC;
|
|
reg [WIDTH-1:0] RP, nRP;
|
|
reg [WIDTH-1:0] FP, BP;
|
|
reg [WIDTH-1:0] IV,IR;
|
|
|
|
wire [WIDTH-1:0] pc_next_ins = PC + 2;
|
|
|
|
reg [EVAL_STACK_INDEX_WIDTH-1:0] ESP, nESP;
|
|
reg stack_write;
|
|
|
|
reg irq_pending;
|
|
|
|
// eval stack
|
|
stack #(.ADDR_WIDTH(EVAL_STACK_INDEX_WIDTH), .DATA_WIDTH(WIDTH)) estack (
|
|
.clk(clk),
|
|
.rd_addr(ESP),
|
|
.wr_addr(nESP),
|
|
.wr_enable(stack_write),
|
|
.rd_data(Y),
|
|
.wr_data(X)
|
|
);
|
|
|
|
reg [15:0] ins;
|
|
|
|
wire [WIDTH-1:0] operand;
|
|
|
|
// decoded instructions
|
|
wire ins_loadrel;
|
|
wire ins_load;
|
|
wire ins_loadc;
|
|
wire ins_store;
|
|
wire ins_aluop;
|
|
wire ins_ext;
|
|
wire ins_xfer;
|
|
wire ins_branch;
|
|
wire ins_cbranch;
|
|
|
|
// decoded extended instructions
|
|
wire ins_mem, ins_loadi, ins_storei;
|
|
wire ins_fpadj;
|
|
wire ins_reg, ins_loadreg, ins_storereg;
|
|
wire ins_reg_fp, ins_reg_bp, ins_reg_rp;
|
|
wire ins_reg_iv, ins_reg_ir;
|
|
wire ins_reg_esp;
|
|
wire loadstore_base;
|
|
wire cbranch_n;
|
|
|
|
wire xfer_x2p, xfer_r2p, xfer_p2r;
|
|
wire [1:0] xfer_rs;
|
|
|
|
wire [3:0] aluop;
|
|
wire [1:0] aluop_sd;
|
|
wire aluop_x2y, aluop_ext;
|
|
|
|
wire cmp_i, cmp_e, cmp_l;
|
|
|
|
wire mem_read;
|
|
wire mem_write;
|
|
|
|
wire x_is_zero;
|
|
|
|
wire x_equals_y = X == Y;
|
|
wire y_lessthan_x = $signed(Y) < $signed(X);
|
|
wire yx_unsigned_less = Y < X;
|
|
|
|
reg [WIDTH-1:0] mem_write_data;
|
|
|
|
wire mem_read_enable, mem_write_enable;
|
|
|
|
assign read_enable = mem_read_enable;
|
|
assign data_out = mem_write_data;
|
|
assign write_enable = mem_write_enable;
|
|
|
|
// debug output ------------------------------------------------------------------------------------
|
|
assign debug1 = reset;
|
|
assign debug2 = ins_loadc;
|
|
assign debug3 = ins_branch;
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
|
|
// instruction decoding
|
|
assign ins_branch = (ins[15:13] == 3'b000);
|
|
assign ins_aluop = (ins[15:13] == 3'b001);
|
|
assign ins_store = (ins[15:13] == 3'b010);
|
|
assign ins_xfer = (ins[15:13] == 3'b011);
|
|
assign ins_load = (ins[15:13] == 3'b100);
|
|
assign ins_cbranch = (ins[15:13] == 3'b101);
|
|
assign ins_loadc = (ins[15:13] == 3'b110);
|
|
assign ins_ext = (ins[15:13] == 3'b111);
|
|
|
|
// sub-decode LOAD/STORE
|
|
assign loadstore_base = ins[0];
|
|
|
|
// sub-decode CBRANCH
|
|
assign cbranch_n = ins[0];
|
|
|
|
// sub-decode XFER
|
|
assign xfer_x2p = ins[0];
|
|
assign xfer_r2p = ins[7];
|
|
assign xfer_p2r = ins[6];
|
|
assign xfer_rs = ins[9:8];
|
|
|
|
// sub-decode OP
|
|
assign aluop = ins[12:9];
|
|
assign aluop_x2y = ins[6];
|
|
assign aluop_sd = ins[5:4];
|
|
assign aluop_ext = ins[7];
|
|
|
|
// sub-decode OP.CMP
|
|
assign cmp_i = ins[2];
|
|
assign cmp_e = ins[1];
|
|
assign cmp_l = ins[0];
|
|
|
|
assign x_is_zero = X == {WIDTH{1'b0}};
|
|
|
|
// decode extended instructions
|
|
assign ins_reg = (ins_ext && ins[12:10] == 3'b000);
|
|
assign ins_mem = (ins_ext && ins[12:10] == 3'b001);
|
|
assign ins_loadi = (ins_mem && ins[9] == 1'b0);
|
|
assign ins_storei = (ins_mem && ins[9] == 1'b1);
|
|
assign ins_fpadj = (ins_ext && ins[12:10] == 3'b011);
|
|
assign ins_loadrel= (ins_ext && ins[12:10] == 3'b101);
|
|
|
|
// sub-decode LOADREG/STOREREG
|
|
assign ins_loadreg = (ins_reg && ins[9] == 1'b0);
|
|
assign ins_storereg = (ins_reg && ins[9] == 1'b1);
|
|
assign ins_reg_fp = (ins_reg && ins[3:0] == 4'b0000);
|
|
assign ins_reg_bp = (ins_reg && ins[3:0] == 4'b0001);
|
|
assign ins_reg_rp = (ins_reg && ins[3:0] == 4'b0010);
|
|
assign ins_reg_iv = (ins_reg && ins[3:0] == 4'b0011);
|
|
assign ins_reg_ir = (ins_reg && ins[3:0] == 4'b0100);
|
|
assign ins_reg_esp = (ins_reg && ins[3:0] == 4'b0101);
|
|
|
|
assign mem_read = ins_loadi || ins_load || ins_loadrel || (ins_xfer && xfer_r2p);
|
|
assign mem_write = ins_storei || ins_store || (ins_xfer && xfer_p2r);
|
|
|
|
assign mem_read_enable = (seq_state == FETCH) || (seq_state == EXEC && mem_read);
|
|
assign mem_write_enable = (seq_state == MEM && mem_write);
|
|
|
|
assign read_ins = (seq_state == FETCH) || (seq_state == DECODE);
|
|
|
|
initial
|
|
begin
|
|
PC <= 0; nPC <= 0; seq_state <= MEM;
|
|
ESP <= -1; nESP <= -1;
|
|
addr <= 0;
|
|
FP <= 0; BP <= 0; RP <= 0; nRP <= 0;
|
|
IV <= 0; IR <= 0;
|
|
irq_pending <= 0;
|
|
end
|
|
|
|
// instruction sequencer
|
|
always @(posedge clk)
|
|
begin
|
|
if(reset)
|
|
seq_state <= MEM;
|
|
else if(mem_wait == 1'b0)
|
|
case(seq_state)
|
|
FETCH: seq_state <= DECODE;
|
|
DECODE: seq_state <= EXEC;
|
|
EXEC: seq_state <= MEM;
|
|
MEM: seq_state <= FETCH;
|
|
default: seq_state <= FETCH;
|
|
endcase
|
|
end
|
|
|
|
// operand register
|
|
assign operand =
|
|
(ins_load || ins_store || ins_branch || ins_cbranch) ?
|
|
{ {(WIDTH-13){ins[12]}}, ins[12:1], 1'b0 }
|
|
: (ins_loadc) ? { {(WIDTH-13){ins[12]}}, ins[12:0] } // sign extend
|
|
: (ins_aluop || ins_mem) ?
|
|
{ {(WIDTH-4){1'b0}}, ins[3:0] }
|
|
: (ins_loadrel) ? { {(WIDTH-10){1'b0}}, ins[9:0] }
|
|
: (ins_fpadj) ? { {(WIDTH-10){ins[9]}}, ins[9:0] } // sign extend
|
|
: { {WIDTH{1'b0}} };
|
|
|
|
// program counter
|
|
always @(posedge clk)
|
|
begin
|
|
if(reset) nPC <= 0;
|
|
else
|
|
case(seq_state)
|
|
EXEC:
|
|
if(ins_xfer && xfer_x2p) nPC <= X;
|
|
else if(ins_branch || (ins_cbranch && (x_is_zero != cbranch_n))) nPC <= PC + operand;
|
|
else nPC <= pc_next_ins;
|
|
MEM:
|
|
if(ins_xfer && xfer_r2p) nPC <= data_in;
|
|
else if(irq_pending) nPC <= IV;
|
|
endcase
|
|
end
|
|
|
|
// return stack pointer
|
|
always @*
|
|
begin
|
|
if(seq_state == EXEC || seq_state == DECODE || seq_state == MEM)
|
|
begin
|
|
if (ins_xfer) nRP <= RP +
|
|
({ {(ADDR_WIDTH-3){xfer_rs[1]}},xfer_rs} << WORDSIZE_SHIFT);
|
|
// sign extend xfer_rs and multiply by word size
|
|
else if (ins_storereg && ins_reg_rp) nRP <= X;
|
|
else nRP <= RP;
|
|
end
|
|
else nRP <= nRP;
|
|
end
|
|
|
|
// instruction fetch
|
|
// depending on bit 1 of the PC, read either the upper or lower half word as an instruction
|
|
always @* if(seq_state == DECODE) ins <= PC[1] ? data_in[15:0] : data_in[31:16];
|
|
|
|
// RAM read/write
|
|
always @(posedge clk)
|
|
begin
|
|
if(reset)
|
|
begin
|
|
addr <= 0;
|
|
mem_write_data <= 0;
|
|
end
|
|
else
|
|
case(seq_state)
|
|
DECODE:
|
|
if(ins_load || ins_store) // read from address in BP/FP + offset
|
|
addr <= operand + ( loadstore_base ? BP: FP);
|
|
else if (ins_loadi) // read from address in X
|
|
addr <= X;
|
|
else if (ins_storei) // write to address in Y
|
|
addr <= Y;
|
|
else if (ins_loadrel) // read from address next to current instruction
|
|
addr <= PC + operand;
|
|
else if (ins_xfer && xfer_r2p) // read from return stack
|
|
addr <= RP; // use the current RP
|
|
else if (ins_xfer && xfer_p2r) // write to return stack
|
|
addr <= nRP; // use the new RP
|
|
EXEC:
|
|
begin
|
|
if (ins_store)
|
|
mem_write_data <= X;
|
|
else if (ins_storei)
|
|
mem_write_data <= X;
|
|
else if (ins_xfer && xfer_p2r)
|
|
mem_write_data <= pc_next_ins;
|
|
else
|
|
mem_write_data <= 0;
|
|
end
|
|
MEM:
|
|
if(!mem_wait) // do not change the address if mem_wait is active
|
|
begin
|
|
if(ins_xfer && xfer_r2p) addr <= data_in; // on RET take addr for next instruction from the data we just read from mem
|
|
else addr <= irq_pending ? IV : nPC; // prepare fetch cycle
|
|
end
|
|
endcase
|
|
end
|
|
|
|
// X/ToS-Register
|
|
always @(posedge clk)
|
|
begin
|
|
if(reset) nX <= 0;
|
|
else
|
|
case(seq_state)
|
|
// default: nX <= X;
|
|
FETCH, DECODE:;
|
|
EXEC:
|
|
if(ins_loadc) nX <= operand;
|
|
else if(ins_cbranch || ins_store || ins_storereg || (ins_xfer && xfer_x2p)) nX <= Y;
|
|
else if(ins_storei) nX <= Y + operand;
|
|
else if(ins_loadreg && ins_reg_fp) nX <= FP;
|
|
else if(ins_loadreg && ins_reg_bp) nX <= BP;
|
|
else if(ins_loadreg && ins_reg_rp) nX <= RP;
|
|
else if(ins_loadreg && ins_reg_iv) nX <= IV;
|
|
else if(ins_loadreg && ins_reg_ir) nX <= IR;
|
|
else if(ins_loadreg && ins_reg_esp) nX <= ESP;
|
|
else if(ins_aluop)
|
|
begin
|
|
case(aluop)
|
|
4'b0000: nX = X + Y; // ADD
|
|
4'b0001: nX = Y - X; // SUB
|
|
4'b0010: nX = ~X; // NOT
|
|
4'b0011: nX = X & Y; // AND
|
|
4'b0100: nX = X | Y; // OR
|
|
4'b0101: nX = X ^ Y; // XOR
|
|
4'b0110: nX = // CMP
|
|
cmp_i ^ ((cmp_e && x_equals_y) || (cmp_l && y_lessthan_x));
|
|
4'b0111: nX = Y; // Y
|
|
4'b1000: nX = aluop_ext ? X >>> 1 : X >> 1; // SHR
|
|
4'b1001: nX = operand[1] ? X << 2 : X << 1; // SHL
|
|
4'b1010: nX = X + operand; // INC
|
|
4'b1011: nX = X - operand; // DEC
|
|
4'b1100: nX = // CMPU
|
|
cmp_i ^ ((cmp_e && x_equals_y) || (cmp_l && yx_unsigned_less));
|
|
// 4'b1101: nX = X[7:0] << ((3 - Y[1:0]) << 3); // BPLC
|
|
4'b1101: nX = Y[1:0] == 0 ? { X[7:0], 24'b0 } :
|
|
Y[1:0] == 1 ? { 8'b0, X[7:0], 16'b0 } :
|
|
Y[1:0] == 2 ? { 16'b0, X[7:0], 8'b0 } :
|
|
{ 24'b0, X[7:0]}; // BPLC
|
|
4'b1110: nX = { X[23:16], X[15:8], X[7:0], X[31:24] }; // BROT
|
|
4'b1111: nX = { 24'b0, Y[1:0] == 0 ? X[31:24] : Y[1:0] == 1 ? X[23:16] :
|
|
Y[1:0] == 2 ? X[15: 8] : X[7:0] }; // BSEL
|
|
// 4'b1110: nX = X * Y; // MUL
|
|
// 4'b1111: nX = X / Y; // DIV
|
|
default: nX = X;
|
|
endcase
|
|
end
|
|
MEM:
|
|
if (ins_loadi || ins_load || ins_loadrel)
|
|
nX = data_in;
|
|
endcase
|
|
end
|
|
|
|
// estack movement
|
|
wire [EVAL_STACK_INDEX_WIDTH-1:0] delta =
|
|
((ins_load || ins_loadc || ins_loadreg || ins_loadrel)) ? 1
|
|
: ((ins_aluop || ins_loadi || ins_storei || ins_xfer)) ?
|
|
{ {(EVAL_STACK_INDEX_WIDTH-2){aluop_sd[1]}},aluop_sd} // sign extend
|
|
: ((ins_store || ins_cbranch || ins_xfer || ins_storereg)) ? -1
|
|
: 0;
|
|
|
|
|
|
always @*
|
|
begin
|
|
if(reset)
|
|
nESP <= 0;
|
|
else
|
|
if(seq_state == EXEC)
|
|
begin
|
|
nESP = ESP + delta;
|
|
end
|
|
end
|
|
|
|
always @(posedge clk)
|
|
begin
|
|
// when to write (old) X back to stack (new Y)
|
|
// stack write is a reg so it is 1 in the next cycle i.e. MEM state
|
|
stack_write <= (seq_state == EXEC &&
|
|
(ins_load || ins_loadc || ins_loadrel || ins_loadreg
|
|
|| ((ins_loadi || ins_storei || ins_aluop) && aluop_x2y)));
|
|
end
|
|
|
|
// FP register
|
|
always @(posedge clk)
|
|
begin
|
|
if(seq_state == EXEC)
|
|
begin
|
|
if(ins_fpadj) FP <= FP + operand;
|
|
else if(ins_storereg && ins_reg_fp) FP <= X;
|
|
end
|
|
end
|
|
|
|
// BP register
|
|
always @(posedge clk) if(seq_state == EXEC && ins_storereg && ins_reg_bp) BP <= X;
|
|
|
|
// IV register
|
|
always @(posedge clk)
|
|
begin
|
|
if(reset)
|
|
IV <= 0;
|
|
else if(seq_state == EXEC && ins_storereg && ins_reg_iv)
|
|
IV <= X;
|
|
end
|
|
|
|
// IR register
|
|
always @(posedge clk)
|
|
begin
|
|
if(seq_state == MEM && irq_pending) IR <= nPC; // use nPC as interrupt return addr
|
|
end
|
|
|
|
// process irq
|
|
always @(posedge clk)
|
|
begin
|
|
if(seq_state == MEM && irq_pending && !(ins_xfer & xfer_r2p)) // in FETCH state, clear irq_pending.
|
|
irq_pending <= 0;
|
|
else
|
|
irq_pending <= irq_pending || irq; // else set irq_pending when irq is high
|
|
end
|
|
|
|
// advance CPU state
|
|
always @ (posedge clk)
|
|
begin
|
|
if(reset)
|
|
{ PC, X, ESP, RP } <= { {WIDTH{1'b0}}, {WIDTH{1'b0}}, {WIDTH{1'b0}}, {WIDTH{1'b0}} };
|
|
else if(seq_state == FETCH)
|
|
{ PC, X, ESP, RP } <= { nPC, nX, nESP, nRP};
|
|
end
|
|
endmodule
|