rv32g-core

Pipelined, synthesizable, and fabricable RISC-V core in SystemVerilog+Yosys+OpenROAD
Log | Files | Refs | LICENSE

commit 57387869f290867fc0691098e3d1633c18c8bd17
parent 0ebcccf55db0716499c5c2f259694a3544d773b7
Author: Vineet Kumar <git@vineetk.net>
Date:   Tue,  9 Jun 2026 00:31:42 -0400

implement a sort of modified harvard arch. and icache flushing

needed to pass the rv32ui-p-fence_i test. it seems to require supporting
self-modifying programs, which the previous way did not allow.

Diffstat:
Mrtl/rv32g_hart.sv | 16++++++++++------
Mrtl/rv32g_pkg.sv | 4++++
Mrtl/stage_decode.sv | 33+++++++++++++++++----------------
Mrtl/stage_fetch.sv | 31+++++++++++++++++++++++++++++--
Mrtl/stage_memory.sv | 16++++++++--------
Mrtl/tb_rv32g.sv | 33+++++++++++++++++++++------------
Artl/unified_memory.sv | 40++++++++++++++++++++++++++++++++++++++++
7 files changed, 129 insertions(+), 44 deletions(-)

diff --git a/rtl/rv32g_hart.sv b/rtl/rv32g_hart.sv @@ -8,11 +8,13 @@ module rv32g_hart input logic imem_valid, output logic [31:0] dmem_addr, - input logic [31:0] dmem_data, input logic dmem_valid, + input logic [31:0] dmem_rdata, output logic [31:0] dmem_wdata, output logic dmem_we, - output logic [3:0] dmem_be); + output logic [3:0] dmem_be, + + output exception_t exception); // x0 to x31 reg_t regfile [0:31]; @@ -36,10 +38,10 @@ module rv32g_hart // end // end - stage_fetch u_fetch(.pc, .imem_addr, .imem_data, .inst); + stage_fetch u_fetch(.clk, .rstN, .flush_i(ctrl.fence_flush), .pc, .imem_addr, .imem_data, .inst); 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 <= '0; + if (!rstN) pc <= BASE_ADDR; else if (imem_valid) pc <= next_pc; else begin // TODO (pipeline) IF/ID stall. if no instruction, just wait? @@ -80,10 +82,10 @@ module rv32g_hart // TODO stall when dmem_valid is 0 and ctrl.memop != 0. find out what else needs to be done in between stages stage_memory u_mem(.memop(ctrl.memop), .alu_out, .rs2_val, - .dmem_addr, .dmem_data, .dmem_wdata, .dmem_we, .dmem_be, .mem_out); + .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 - $display("MEM: memop: %h, dmem_addr: %h, dmem_data: %h, dmem_wdata: %h, dmem_we: %d, dmem_be: %b, mem_out: %h", ctrl.memop, dmem_addr, dmem_data, dmem_wdata, dmem_we, dmem_be, mem_out); + $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 else ; // TODO I need to think of what else should happen end @@ -110,4 +112,6 @@ module rv32g_hart endcase end + assign exception = ctrl.exception; + endmodule: rv32g_hart diff --git a/rtl/rv32g_pkg.sv b/rtl/rv32g_pkg.sv @@ -1,4 +1,7 @@ package rv32g_pkg; + parameter BASE_ADDR = 32'h8000_0000; + parameter CACHE_DEPTH = 4096; + typedef logic [31:0] reg_t; typedef enum logic [3:0] {INVALIDTYPE, @@ -171,5 +174,6 @@ package rv32g_pkg; branchop_t branchop; wbsrc_t wbsrc; exception_t exception; + logic fence_flush; } ctrl_t; endpackage: rv32g_pkg diff --git a/rtl/stage_decode.sv b/rtl/stage_decode.sv @@ -8,13 +8,13 @@ module stage_decode output logic [31:0] rs2_val, output logic [4:0] rd_num); - logic [6:0] funct7; - logic [4:0] rs2_num; - logic [4:0] rs1_num; - logic [2:0] funct3; - opcode_t opcode; - inst_type_t inst_type; - inst_name_t name; + logic [6:0] funct7; + logic [4:0] rs2_num; + logic [4:0] rs1_num; + logic [2:0] funct3; + opcode_t opcode; + inst_type_t inst_type; + inst_name_t name; always_comb begin funct7 = inst[31:25]; @@ -123,14 +123,15 @@ module stage_decode // set control signals based on instruction name always_comb begin - ctrl = '{aluop: ALU_NONE, - aluop1src: ALUOP1_NONE, - aluop2src: ALUOP2_NONE, - memop: MEM_NONE, - pcsrc: PC_SEQ, - branchop: BRANCH_NONE, - wbsrc: WB_NONE, - exception: EXC_NONE}; + ctrl = '{aluop: ALU_NONE, + aluop1src: ALUOP1_NONE, + aluop2src: ALUOP2_NONE, + memop: MEM_NONE, + pcsrc: PC_SEQ, + branchop: BRANCH_NONE, + wbsrc: WB_NONE, + fence_flush: 1'b0, + exception: EXC_NONE}; // the major opcodes share the same signals, just operation varies based on funct unique case (opcode) @@ -257,7 +258,7 @@ module stage_decode ECALL: ctrl.exception = EXC_CALL; EBREAK: ctrl.exception = EXC_BKPT; // Zifencei - FENCE_I: ; + FENCE_I: ctrl.fence_flush = 1'b1; default: ctrl.exception = EXC_ILL_INST; endcase end diff --git a/rtl/stage_fetch.sv b/rtl/stage_fetch.sv @@ -1,10 +1,37 @@ module stage_fetch - (input logic [31:0] pc, + (input logic clk, + input logic rstN, + input logic flush_i, // from fence.i + input logic [31:0] pc, input logic [31:0] imem_data, output logic [31:0] imem_addr, output logic [31:0] inst); + localparam CACHE_SIZE = 256; // words + localparam IDX_WIDTH = $clog2(CACHE_SIZE); + + logic [31:0] cache [0:CACHE_SIZE-1]; + logic valid [0:CACHE_SIZE-1]; + logic [IDX_WIDTH-1:0] idx; + logic [31:0] cached_inst; + assign imem_addr = pc; - assign inst = imem_data; + assign idx = pc[IDX_WIDTH+1:2]; + + always_ff @(posedge clk or negedge rstN) begin + if (!rstN) begin // async flush + for (int i = 0; i < CACHE_SIZE; i++) valid[i] <= 0; + end else if (flush_i) begin // sync flush + for (int i = 0; i < CACHE_SIZE; i++) valid[i] <= 0; + end else begin // cache miss :( + if (!valid[idx] || cache[idx] !== imem_data) begin + cache[idx] <= imem_data; + valid[idx] <= 1; + end + end + end + + assign cached_inst = valid[idx] ? cache[idx] : imem_data; + assign inst = cached_inst; endmodule: stage_fetch diff --git a/rtl/stage_memory.sv b/rtl/stage_memory.sv @@ -5,7 +5,7 @@ module stage_memory input reg_t rs2_val, output logic [31:0] dmem_addr, - input logic [31:0] dmem_data, + input logic [31:0] dmem_rdata, output logic [31:0] dmem_wdata, output logic dmem_we, // byte-enable signal is used so that memory controller doesn't overwrite adjacent bytes in word @@ -24,22 +24,22 @@ module stage_memory mem_out = 0; unique case (alu_out[1:0]) - 2'b00: bdata = dmem_data[7:0]; - 2'b01: bdata = dmem_data[15:8]; - 2'b10: bdata = dmem_data[23:16]; - 2'b11: bdata = dmem_data[31:24]; + 2'b00: bdata = dmem_rdata[7:0]; + 2'b01: bdata = dmem_rdata[15:8]; + 2'b10: bdata = dmem_rdata[23:16]; + 2'b11: bdata = dmem_rdata[31:24]; endcase unique case (alu_out[1]) - 1'b0: hdata = dmem_data[15:0]; - 1'b1: hdata = dmem_data[31:16]; + 1'b0: hdata = dmem_rdata[15:0]; + 1'b1: hdata = dmem_rdata[31:16]; endcase unique case (memop) MEM_NONE: mem_out = '0; MEM_LB: mem_out = {{24{bdata[7]}}, bdata}; MEM_LH: mem_out = {{16{hdata[15]}}, hdata}; - MEM_LW: mem_out = dmem_data; + MEM_LW: mem_out = dmem_rdata; MEM_LBU: mem_out = {24'b0, bdata}; MEM_LHU: mem_out = {16'b0, hdata}; MEM_SB: begin diff --git a/rtl/tb_rv32g.sv b/rtl/tb_rv32g.sv @@ -1,6 +1,7 @@ timeunit 1ns; timeprecision 1ps; module tb_rv32g + import rv32g_pkg::*; (); // DUT (design-under-test) ports @@ -14,22 +15,27 @@ module tb_rv32g logic [31:0] dmem [0:4095]; logic [31:0] dmem_addr; - logic [31:0] dmem_data; - logic dmem_valid; - logic [31:0] dmem_wdata; logic dmem_we; logic [3:0] dmem_be; + logic [31:0] dmem_rdata; + logic [31:0] dmem_wdata; + logic dmem_valid; + + exception_t exception; // debug stuff int cycle_cnt = 0; // 100 MHz clock generation // T = 1/f/2 = 1/(100*10^6)/2 = 1e-8/2 = 5ns - initial forever #5ns clk = ~clk; + initial begin + clk = 0; + forever #5ns clk = ~clk; + end initial begin $display("Reading rom.txt"); - $readmemh("rom.txt", imem); + $readmemh("rom.txt", u_mem.mem); rstN = 0; imem_addr = 0; @@ -37,7 +43,9 @@ module tb_rv32g imem_valid = 1; dmem_valid = 1; - #5 rstN = 1; + #5; + @(posedge clk); + rstN = 1; $display("Reset now deactivated"); end @@ -49,14 +57,15 @@ module tb_rv32g $display("Cycle: %d", cycle_cnt); end - assign imem_data = imem[imem_addr >> 2]; - assign dmem_data = dmem[dmem_addr >> 2]; - assign imem_valid = (imem_data !== 32'h0000_0000 && imem_data !== 32'hffff_ffff) ? 1 : 0; + unified_memory u_mem (.clk, .imem_addr, .imem_data, .imem_valid, + .dmem_addr, .dmem_we, .dmem_be, + .dmem_wdata, .dmem_rdata); - rv32g_hart dut(.clk, .rstN, + rv32g_hart dut(.clk, .rstN, .exception, .imem_addr, .imem_data, .imem_valid, - .dmem_addr, .dmem_data, .dmem_valid, - .dmem_wdata, .dmem_we, .dmem_be); + .dmem_addr, .dmem_valid, + .dmem_rdata, .dmem_wdata, + .dmem_we, .dmem_be); always if (!imem_valid) $finish(); diff --git a/rtl/unified_memory.sv b/rtl/unified_memory.sv @@ -0,0 +1,40 @@ +module unified_memory + import rv32g_pkg::*; + (input logic clk, + + // instruction port + input logic [31:0] imem_addr, + output logic [31:0] imem_data, + output logic imem_valid, + + // data port + input logic [31:0] dmem_addr, + input logic dmem_we, + input logic [3:0] dmem_be, + input logic [31:0] dmem_wdata, + output logic [31:0] dmem_rdata); + + logic [31:0] mem [0:CACHE_DEPTH-1]; + logic [31:0] imem_idx, dmem_idx; + + // initial $readmemh("rom.txt", mem); + + always_comb begin + imem_idx = (imem_addr - BASE_ADDR) >> 2; + dmem_idx = (dmem_addr - BASE_ADDR) >> 2; + + imem_data = (imem_idx < CACHE_DEPTH) ? mem[imem_idx] : '0; + imem_valid = (imem_idx < CACHE_DEPTH) ? 1'b1 : 1'b0; + end + + always_ff @(posedge clk) begin + // for self-modifying memory + if (dmem_we && dmem_idx < CACHE_DEPTH) begin + for (int b = 0; b < 4; b++) begin + if (dmem_be[b]) mem[dmem_idx][b << 3 +: 8] <= dmem_wdata[b << 3 +: 8]; + end + end + dmem_rdata <= (dmem_idx < CACHE_DEPTH) ? mem[dmem_idx] : '0; + end + +endmodule