commit 8b56d0a6a6b0256eff987ce8fa89a176d9fc6738
parent 78d9f04bd0c4aa9e4853d1fa336d4064b3ad2f91
Author: Vineet Kumar <git@vineetk.net>
Date: Sat, 11 Jul 2026 14:55:42 +0530
do initial pipelining
Diffstat:
8 files changed, 255 insertions(+), 106 deletions(-)
diff --git a/rtl/csr_regfile.sv b/rtl/csr_regfile.sv
@@ -95,18 +95,14 @@ module csr_regfile
default: ;
endcase
end
-
- // FIXME remove once hart is pipelined
- // perform combinational read when rden
- assign rdata = (rden && !illegal_rd) ? regfile[reg_ridx] : '0;
always_ff @(posedge clk or negedge rstN) begin
if (!rstN) begin
mepc_reg <= '0;
end else begin
- // // perform sequential read when rden
- // if (rden && !illegal_rd) rdata <= regfile[reg_ridx];
- // else rdata <= '0;
+ // perform sequential read when rden
+ if (rden && !illegal_rd) rdata <= regfile[reg_ridx];
+ else rdata <= '0;
// perform sequential write when wren and not read-only
if (wren && !illegal_wr) begin
diff --git a/rtl/rv32g_hart.sv b/rtl/rv32g_hart.sv
@@ -18,53 +18,101 @@ module rv32g_hart
// x0 to x31
reg_t regfile [0:31];
- reg_t pc;
reg_t next_pc;
- logic [31:0] inst;
- ctrl_t ctrl;
- logic [31:0] imm;
- reg_t rs1_val;
- reg_t rs2_val;
- logic [4:0] rd_num;
- reg_t alu_out;
- reg_t mem_out;
- logic branch_taken;
-
// Zicsr
- logic [11:0] csr_addr;
- reg_t csr_rdata;
- reg_t csr_out;
- logic csr_illegal;
+ reg_t csr_rdata;
+ logic csr_illegal;
// CSR has xEPC registers for use with xRET instructions
- reg_t csr_mepc;
+ reg_t csr_mepc;
+
+ // pipeline registers
+ ifid_reg_t ifid_reg, ifid_next;
+ idex_reg_t idex_reg;
+ exmem_reg_t exmem_reg;
+ memwb_reg_t memwb_reg;
+
+ // hazards
+ logic if_stall;
+ assign if_stall = !imem_valid;
+
+ // TODO move in between decode and execute for better organization?
csr_regfile u_csr(.clk, .rstN, .illegal(csr_illegal), .mepc_shadow(csr_mepc),
- .raddr(csr_addr), .rden(ctrl.csr_rden), .rdata(csr_rdata),
- .waddr(csr_addr), .wren(ctrl.csr_wren), .wdata(csr_out));
+ .raddr(idex_reg.csr_addr), .rden(idex_reg.ctrl.csr_rden), .rdata(csr_rdata),
+ .waddr(idex_reg.csr_addr), .wren(idex_reg.ctrl.csr_wren), .wdata(exmem_reg.csr_out));
+
+ // =============
+ // === FETCH ===
+ // =============
+
+ // temporary signals
+ logic [31:0] fetch_inst;
+ reg_t pc_reg;
- stage_fetch u_fetch(.clk, .rstN, .flush_i(ctrl.fence_flush), .pc, .imem_addr, .imem_data, .inst);
+ stage_fetch u_fetch(.clk, .rstN, .flush_i(idex_reg.ctrl.fence_flush), .pc(pc_reg), .imem_addr, .imem_data, .inst(fetch_inst));
+ // IF/ID pipeline register
+ always_comb begin
+ ifid_next = ifid_reg; // holds by default
+ if (imem_valid) begin
+ ifid_next.pc = pc_reg;
+ ifid_next.inst = fetch_inst;
+ end
+ end
always_ff @(posedge clk or negedge rstN) begin
- $display("IF: pc: %h, next_pc: %h, imem_valid: %d, imem[%h]: %h, inst: %h", pc, next_pc, imem_valid, imem_addr, imem_data, inst);
- if (!rstN) pc <= BASE_ADDR;
- else if (imem_valid) pc <= next_pc;
- // else begin
- // // TODO (pipeline) IF/ID stall. if no instruction, just wait?
- // end
+ $display("IF: pc: %h, next_pc: %h, imem_valid: %d, imem[%h]: %h, inst: %h",
+ ifid_reg.pc, next_pc, imem_valid, imem_addr, imem_data, ifid_reg.inst);
+ if (!rstN) begin
+ pc_reg <= BASE_ADDR;
+ ifid_reg <= '0;
+ end else if (!if_stall) begin
+ pc_reg <= next_pc;
+ ifid_reg <= ifid_next;
+ end
end
- stage_decode u_decode(.inst, .regfile, .ctrl, .imm, .rs1_val, .rs2_val, .rd_num, .csr_addr);
- // TODO ID/EX pipeline register
-// always_ff @(posedge clk or negedge rstN) begin
- always_ff @(posedge clk) begin
- $display("ID: inst: %h, ctrl: %h, imm: %h, rs1_val: %h, rs2_val: %h, rd_num: %d", inst, ctrl, imm, rs1_val, rs2_val, rd_num);
-// if (!rstN) ; // TODO reset pipeline register
- if (ctrl.exception != EXC_NONE) begin
- // TODO handle exceptions
- unique case (ctrl.exception)
+ // ==============
+ // === DECODE ===
+ // ==============
+
+ // temporary signals
+ ctrl_t dec_ctrl;
+ reg_t dec_pc, dec_imm, dec_rs1_val, dec_rs2_val;
+ logic [4:0] dec_rd_num;
+ logic [11:0] dec_csr_addr;
+
+ stage_decode u_decode(.clk, .rstN, .regfile, .inst(ifid_reg.inst),
+ .pc_in(ifid_reg.pc), .pc_out(dec_pc),
+ .ctrl(dec_ctrl), .imm(dec_imm),
+ .rs1_val(dec_rs1_val), .rs2_val(dec_rs2_val),
+ .rd_num(dec_rd_num), .csr_addr(dec_csr_addr));
+ // ID/EX pipeline register
+ always_ff @(posedge clk or negedge rstN) begin
+ $display("ID: ctrl: %h, imm: %h, rs1_val: %h, rs2_val: %h, rd_num: %d",
+ idex_reg.ctrl, idex_reg.imm, idex_reg.rs1_val, idex_reg.rs2_val, idex_reg.rd_num);
+ if (!rstN) begin
+ // NOP by default
+ idex_reg <= '0;
+ end else if (if_stall) begin
+ // insert NOP
+ idex_reg <= '0;
+ end else begin
+ // latch decode outputs in normal operation
+ idex_reg.ctrl <= dec_ctrl;
+ idex_reg.pc <= dec_pc;
+ idex_reg.imm <= dec_imm;
+ idex_reg.rs1_val <= dec_rs1_val;
+ idex_reg.rs2_val <= dec_rs2_val;
+ idex_reg.rd_num <= dec_rd_num;
+ idex_reg.csr_addr <= dec_csr_addr;
+ end
+
+ if (idex_reg.ctrl.exception != EXC_NONE) begin
+ // TODO handle exceptions. might need moving.
+ unique case (idex_reg.ctrl.exception)
EXC_NONE: ;
- EXC_ILL_INST: $display("Exception: illegal instruction, inst: %h", inst);
+ EXC_ILL_INST: $display("Exception: illegal instruction, inst: %h", ifid_reg.inst);
EXC_CALL: $display("Exception: ECALL");
EXC_BKPT: $display("Exception: BREAK");
endcase
@@ -72,66 +120,95 @@ module rv32g_hart
end
// TODO find out what else needs to be done in between stages like exceptions
- stage_execute u_execute(.op(ctrl.aluop),
- .op1src(ctrl.aluop1src),
- .op2src(ctrl.aluop2src),
- .branchop(ctrl.branchop),
- .out(alu_out),
- .imm, .pc, .rs1_val, .rs2_val, .branch_taken,
- .csrop(ctrl.csrop),
- .csr_rden(ctrl.csr_rden),
- .csr_wren(ctrl.csr_wren),
- .csr_immen(ctrl.csr_immen),
- .csr_imm(ctrl.csr_imm),
+ // ===============
+ // === EXECUTE ===
+ // ===============
+
+ stage_execute u_execute(.clk,
+ .imm(idex_reg.imm),
+ .pc(idex_reg.pc),
+ .rs1_val(idex_reg.rs1_val),
+ .rs2_val(idex_reg.rs2_val),
+ .op(idex_reg.ctrl.aluop),
+ .op1src(idex_reg.ctrl.aluop1src),
+ .op2src(idex_reg.ctrl.aluop2src),
+ .branchop(idex_reg.ctrl.branchop),
+ .csrop(idex_reg.ctrl.csrop),
+ .csr_rden(idex_reg.ctrl.csr_rden),
+ .csr_wren(idex_reg.ctrl.csr_wren),
+ .csr_immen(idex_reg.ctrl.csr_immen),
+ .csr_imm(idex_reg.ctrl.csr_imm),
.csr_rdata(csr_rdata),
- .csr_out(csr_out),
- .is_mret(ctrl.is_mret));
- // TODO EX/MEM pipeline register
-// always_ff @(posedge clk or negedge rstN) begin
- always_ff @(posedge clk) begin
- $display("EX: op: %h, op1src: %d, op2src: %d, branchop: %d, out: %h, branch_taken: %d", ctrl.aluop, ctrl.aluop1src, ctrl.aluop2src, ctrl.branchop, alu_out, branch_taken);
-// if (!rstN) ; // TODO reset pipeline register
+ .is_mret(idex_reg.ctrl.is_mret),
+ .out(exmem_reg.alu_out),
+ .branch_taken(exmem_reg.branch_taken),
+ .csr_out(exmem_reg.csr_out));
+ // EX/MEM pipeline register
+ always_ff @(posedge clk or negedge rstN) begin
+ $display("EX: op: %h, op1src: %d, op2src: %d, branchop: %d, out: %h, branch_taken: %d",
+ idex_reg.ctrl.aluop, idex_reg.ctrl.aluop1src, idex_reg.ctrl.aluop2src,
+ idex_reg.ctrl.branchop, exmem_reg.alu_out, exmem_reg.branch_taken);
+ if (!rstN) exmem_reg = '0;
// if (!dmem_valid && ctrl.memop != MEM_NONE) begin
// // TODO stall
// end
end
// TODO stall when dmem_valid is 0 and ctrl.memop != 0. find out what else needs to be done in between stages
+
+ // ==============
+ // === MEMORY ===
+ // ==============
- stage_memory u_mem(.memop(ctrl.memop), .alu_out, .rs2_val,
- .dmem_addr, .dmem_rdata, .dmem_wdata, .dmem_we, .dmem_be, .mem_out);
- // TODO MEM/WB pipeline register
-// always_ff @(posedge clk or negedge rstN) begin
- always_ff @(posedge clk) begin
- $display("MEM: memop: %h, dmem_addr: %h, dmem_rdata: %h, dmem_wdata: %h, dmem_we: %d, dmem_be: %b, mem_out: %h", ctrl.memop, dmem_addr, dmem_rdata, dmem_wdata, dmem_we, dmem_be, mem_out);
- // if (!rstN) ; // TODO reset pipeline register
+ stage_memory u_mem(.memop(exmem_reg.memop),
+ .alu_out(exmem_reg.alu_out),
+ .rs2_val(exmem_reg.rs2_val),
+ .mem_out(memwb_reg.mem_out),
+ .alu_out_passthrough(memwb_reg.alu_out),
+ .dmem_addr, .dmem_rdata, .dmem_wdata, .dmem_we, .dmem_be);
+ // MEM/WB pipeline register
+ always_ff @(posedge clk or negedge rstN) begin
+ $display("MEM: memop: %h, dmem_addr: %h, dmem_rdata: %h, dmem_wdata: %h, dmem_we: %d, dmem_be: %b, mem_out: %h",
+ exmem_reg.memop, dmem_addr, dmem_rdata, dmem_wdata, dmem_we, dmem_be, memwb_reg.mem_out);
+ if (!rstN) memwb_reg = '0;
// else ; // TODO I need to think of what else should happen
end
// TODO find out what else needs to be done in between stages
- stage_writeback u_wb(.clk, .rstN, .wbsrc(ctrl.wbsrc), .rd_num, .regfile,
- .pc, .imm, .alu_out, .mem_out);
+ // =================
+ // === WRITEBACK ===
+ // =================
+
+ stage_writeback u_wb(.clk, .rstN, .regfile,
+ .wbsrc(memwb_reg.wbsrc),
+ .rd_num(memwb_reg.rd_num),
+ .pc(memwb_reg.pc),
+ .imm(memwb_reg.imm),
+ .alu_out(memwb_reg.alu_out),
+ .mem_out(memwb_reg.mem_out));
always_ff @(posedge clk) begin
- $display("WB: wbsrc: %d, rd_num: %d, pc: %h, imm: %h, alu_out: %h, mem_out: %h", ctrl.wbsrc, rd_num, pc, imm, alu_out, mem_out);
+ $display("WB: wbsrc: %d, rd_num: %d, pc: %h, imm: %h, alu_out: %h, mem_out: %h",
+ memwb_reg.wbsrc, memwb_reg.rd_num, memwb_reg.pc, memwb_reg.imm,
+ memwb_reg.alu_out, memwb_reg.mem_out);
end
- // TODO find out what else needs to be done after this stage
+ // TODO move to writeback stage or as separate stage?
always_comb begin
- unique case (ctrl.pcsrc)
+ unique case (exmem_reg.pcsrc)
// normal case
- PC_SEQ: next_pc = pc + 4;
+ PC_SEQ: next_pc = pc_reg + 4;
// branch or jal
PC_IMM: begin
- if ((ctrl.branchop != BRANCH_NONE) ? branch_taken : 1'b1) next_pc = pc + imm;
- else next_pc = pc + 4;
+ if ((exmem_reg.branchop != BRANCH_NONE) ? exmem_reg.branch_taken : 1'b1) next_pc = pc_reg + memwb_reg.imm;
+ else next_pc = pc_reg + 4;
end
// jalr
- PC_ALU: next_pc = alu_out & ~32'b1; // spec says to clear LSB and /may/ raise an exception
+ PC_ALU: next_pc = memwb_reg.alu_out & ~32'b1; // spec says to clear LSB and /may/ raise an exception
// CSR xRET
PC_MRET: next_pc = csr_mepc & ~32'b11; // bit 0 always cleared for alignment, bit 1 when no C-extension
endcase
end
- assign exception = (ctrl.exception != EXC_NONE) ? ctrl.exception :
- (ctrl.csrop != CSR_NONE && csr_illegal) ? EXC_ILL_INST : EXC_NONE;
+ assign exception = (idex_reg.ctrl.exception != EXC_NONE) ? idex_reg.ctrl.exception :
+ (idex_reg.ctrl.csrop != CSR_NONE && csr_illegal) ? EXC_ILL_INST : EXC_NONE;
endmodule: rv32g_hart
diff --git a/rtl/rv32g_pkg.sv b/rtl/rv32g_pkg.sv
@@ -297,4 +297,44 @@ package rv32g_pkg;
logic fence_flush;
logic is_mret;
} ctrl_t;
+
+ // pipeline registers
+ typedef struct packed {
+ reg_t pc;
+ logic [31:0] inst;
+ } ifid_reg_t;
+
+ typedef struct packed {
+ ctrl_t ctrl;
+ reg_t pc;
+ logic [31:0] imm;
+ reg_t rs1_val;
+ reg_t rs2_val;
+ logic [4:0] rd_num;
+ logic [11:0] csr_addr;
+ } idex_reg_t;
+
+ typedef struct packed {
+ reg_t alu_out;
+ reg_t mem_out;
+ reg_t csr_out;
+ reg_t rs2_val;
+ reg_t pc;
+ logic [4:0] rd_num;
+ logic branch_taken;
+ branchop_t branchop;
+ memop_t memop;
+ wbsrc_t wbsrc;
+ pcsrc_t pcsrc;
+ } exmem_reg_t;
+
+ typedef struct packed {
+ reg_t alu_out;
+ reg_t mem_out;
+ reg_t csr_out;
+ reg_t imm;
+ reg_t pc;
+ logic [4:0] rd_num;
+ wbsrc_t wbsrc;
+ } memwb_reg_t;
endpackage: rv32g_pkg
diff --git a/rtl/stage_decode.sv b/rtl/stage_decode.sv
@@ -1,13 +1,18 @@
module stage_decode
import rv32g_pkg::*;
- (input logic [31:0] inst,
+ (input logic clk,
+ input logic rstN,
+ input logic [31:0] inst,
input logic [31:0] regfile [0:31],
output ctrl_t ctrl,
output logic [31:0] imm,
output logic [31:0] rs1_val,
output logic [31:0] rs2_val,
output logic [4:0] rd_num,
- output logic [11:0] csr_addr);
+ output logic [11:0] csr_addr,
+ // passthrough
+ input reg_t pc_in,
+ output reg_t pc_out);
logic [6:0] funct7;
logic [4:0] rs2_num;
@@ -24,6 +29,9 @@ module stage_decode
funct3 = inst[14:12];
rd_num = inst[11:7];
opcode = opcode_t'(inst[6:0]);
+
+ // passthrorugh
+ pc_out = pc_in;
end
// parse the type for each instruction
@@ -338,7 +346,14 @@ module stage_decode
endcase
end
- assign rs1_val = regfile[rs1_num];
- assign rs2_val = regfile[rs2_num];
+ always_ff @(posedge clk or negedge rstN) begin
+ if (!rstN) begin
+ rs1_val <= '0;
+ rs2_val <= '0;
+ end else begin
+ rs1_val <= regfile[rs1_num];
+ rs2_val <= regfile[rs2_num];
+ end
+ end
endmodule: stage_decode
diff --git a/rtl/stage_execute.sv b/rtl/stage_execute.sv
@@ -1,6 +1,7 @@
module stage_execute
import rv32g_pkg::*;
- (input aluop_t op,
+ (input logic clk,
+ input aluop_t op,
input aluop1src_t op1src,
input aluop2src_t op2src,
input branchop_t branchop,
@@ -26,7 +27,10 @@ module stage_execute
logic [31:0] csr_wdata;
// privileged machine-mode (subset)
- reg_t mstatus_old, mstatus_new;
+ reg_t mstatus_q, mstatus_d;
+ always_ff @(posedge clk) begin
+ if (csr_wren) mstatus_q <= mstatus_d;
+ end
always_comb begin
// assign inputs
@@ -83,15 +87,14 @@ module stage_execute
endcase
// privileged machine-mode (subset)
+ mstatus_d = mstatus_q;
if (is_mret) begin
- mstatus_old = csr_rdata;
- mstatus_new = mstatus_old;
// MIE bit 3, MPIE bit 7, MPP bits 12-11
- mstatus_new[3] = mstatus_old[7]; // set MIE to MPIE
- mstatus_new[7] = 1'b1; // enable MPIE 1
- mstatus_new[12:11] = 2'b11; // set MPP to M-mode
+ mstatus_d[3] = mstatus_q[7]; // set MIE to MPIE
+ mstatus_d[7] = 1'b1; // enable MPIE 1
+ mstatus_d[12:11] = 2'b11; // set MPP to M-mode
// with only M-mode, don't need to clear MPRV (bit 17)
- csr_out = mstatus_new;
+ csr_out = mstatus_d;
end
end
diff --git a/rtl/stage_fetch.sv b/rtl/stage_fetch.sv
@@ -10,16 +10,16 @@ module stage_fetch
`define USE_ICACHE // TODO ifdef can be removed once pipelined with BSRAM
`ifdef USE_ICACHE
- localparam CACHE_SIZE = 256; // words
- localparam IDX_WIDTH = $clog2(CACHE_SIZE);
- localparam TAG_WIDTH = 32 - IDX_WIDTH - 2; // only be byte offset
-
- logic [31:0] cache [0:CACHE_SIZE-1];
- logic valid [0:CACHE_SIZE-1];
- logic [IDX_WIDTH-1:0] idx;
- logic [TAG_WIDTH-1:0] tag_mem [0:CACHE_SIZE-1];
- logic [TAG_WIDTH-1:0] tag_pc;
- logic hit;
+ localparam CACHE_SIZE = 256; // words
+ localparam IDX_WIDTH = $clog2(CACHE_SIZE);
+ localparam TAG_WIDTH = 32 - IDX_WIDTH - 2; // only be byte offset
+
+ (* keep *) logic [31:0] cache [0:CACHE_SIZE-1];
+ (* keep *) logic valid [0:CACHE_SIZE-1];
+ logic [IDX_WIDTH-1:0] idx;
+ (* keep *) logic [TAG_WIDTH-1:0] tag_mem [0:CACHE_SIZE-1];
+ logic [TAG_WIDTH-1:0] tag_pc;
+ logic hit;
always_comb begin
imem_addr = pc;
diff --git a/rtl/stage_memory.sv b/rtl/stage_memory.sv
@@ -11,7 +11,8 @@ module stage_memory
// byte-enable signal is used so that memory controller doesn't overwrite adjacent bytes in word
output logic [3:0] dmem_be,
- output logic [31:0] mem_out);
+ output logic [31:0] mem_out,
+ output logic [31:0] alu_out_passthrough);
logic [7:0] bdata;
logic [15:0] hdata;
diff --git a/rtl/tb_rv32g.sv b/rtl/tb_rv32g.sv
@@ -70,12 +70,29 @@ module tb_rv32g
.dmem_rdata, .dmem_wdata,
.dmem_we, .dmem_be);
+ logic seen_first_real_inst;
+ always_ff @(posedge clk or negedge rstN) begin
+ if (!rstN)
+ seen_first_real_inst <= 0;
+ else if (dut.ifid_reg.inst != 32'b0)
+ seen_first_real_inst <= 1;
+ end
+
+ parameter int MAX_CYCLES = 10000;
always_ff @(posedge clk) begin
- if ((!imem_valid && rstN) ||
- (cycle_cnt > 0 && imem_data == 32'b0) ||
- (cycle_cnt > 0 && rstN && exception == EXC_ILL_INST)) begin
- // if ((!imem_valid && rstN) ||
- // (cycle_cnt > 0 && imem_data == 32'b0)) begin
+ if (exception == EXC_ILL_INST && seen_first_real_inst) begin
+ $display("!!! Program terminated (exception ILL_INST at PC %h) !!!", dut.idex_reg.pc);
+ $finish();
+ end
+
+ if (exception == EXC_CALL || exception == EXC_BKPT) begin
+ $display("*** Program finished (exception %s) at cycle %0d ***",
+ exception == EXC_CALL ? "ECALL" : "EBREAK", cycle_cnt);
+ $finish();
+ end
+
+ if (cycle_cnt > MAX_CYCLES) begin
+ $display("!!! Timeout after %0d cycles !!!", MAX_CYCLES);
$finish();
end
end