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:
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