diff --git a/rtl/data_mem.v b/rtl/data_mem.v deleted file mode 100644 index e8b3d4b..0000000 --- a/rtl/data_mem.v +++ /dev/null @@ -1,86 +0,0 @@ -`default_nettype none -`include "memory_map.vh" - -// data_mem.v - byte-addressable data memory for RISC-V CPU - -module data_mem #(parameter DATA_WIDTH = 32, ADDR_WIDTH = 32, MEM_SIZE = 1048576) ( // 1MB = 1024*1024 bytes - input wire clk, - input wire wr_en, - input wire rd_en, - input wire [3:0] write_byte_enable, // Write byte enables - input wire [2:0] load_type, // Load type encoding - input wire [ADDR_WIDTH-1:0] addr, - input wire [DATA_WIDTH-1:0] wr_data, - output wire [DATA_WIDTH-1:0] rd_data_out -); - - // Array of 1MB bytes - reg [7:0] data_ram [0:MEM_SIZE-1]; - - // Initialize memory to zeros - initial begin - integer i; - for (i = 0; i < MEM_SIZE; i = i + 1) begin - data_ram[i] = 8'h00; - end - end - `ifdef COCOTB_SIM - //dump 80 registers to a wire using generate statement so that I can scope them - // This is useful for debugging in cocotb - generate - genvar j; - for (j = 0; j < 80; j = j + 1) begin : dump_regs - wire [7:0] reg_data = data_ram[j]; - end - endgenerate - `endif - - - // Direct byte access for reads (much simpler and more accurate) - wire [7:0] byte_data; - wire [15:0] halfword_data; - wire [31:0] word_data; - - // Read individual bytes directly - assign byte_data = (addr < MEM_SIZE) ? data_ram[addr] : 8'h00; - - // Read halfwords (little-endian: low byte first) - assign halfword_data = (addr < MEM_SIZE-1) ? - {data_ram[addr+1], data_ram[addr]} : 16'h0000; - - // Read words (little-endian: low byte first) - assign word_data = (addr < MEM_SIZE-3) ? - {data_ram[addr+3], data_ram[addr+2], data_ram[addr+1], data_ram[addr]} : 32'h00000000; - - // Format read data based on load type - much simpler logic - assign rd_data_out = rd_en ? ( - (load_type == 3'b000) ? {{24{byte_data[7]}}, byte_data} : // LB - Load Byte (sign-extend) - (load_type == 3'b100) ? {24'h0, byte_data} : // LBU - Load Byte Unsigned - (load_type == 3'b001) ? {{16{halfword_data[15]}}, halfword_data} : // LH - Load Halfword (sign-extend) - (load_type == 3'b101) ? {16'h0, halfword_data} : // LHU - Load Halfword Unsigned - (load_type == 3'b010) ? word_data : // LW - Load Word - 32'h0 // Invalid load type - ) : 32'h0; - - // Synchronous write logic for byte-addressable memory - always @(posedge clk) begin - if (wr_en && (addr < MEM_SIZE)) begin - // For byte writes: always write wr_data[7:0] to data_ram[addr] - if (write_byte_enable[0]) begin - data_ram[addr] <= wr_data[7:0]; - end - // For halfword writes: write 2 consecutive bytes - if (write_byte_enable[1]) begin - data_ram[addr+1] <= wr_data[15:8]; - end - // For word writes: write 4 consecutive bytes - if (write_byte_enable[2]) begin - data_ram[addr+2] <= wr_data[23:16]; - end - if (write_byte_enable[3]) begin - data_ram[addr+3] <= wr_data[31:24]; - end - end - end - -endmodule \ No newline at end of file diff --git a/rtl/include/memory_map.vh b/rtl/include/memory_map.vh index 70b80cc..368d527 100644 --- a/rtl/include/memory_map.vh +++ b/rtl/include/memory_map.vh @@ -49,7 +49,7 @@ `define UART_BAUD 32'h2000000C // Baud rate divisor // Memory access helper macros -`define IS_INSTR_MEM(addr) ((addr) <= `INSTR_MEM_SIZE) +`define IS_INSTR_MEM(addr) ((addr) <= `INSTR_MEM_END) `define IS_TIMER_MEM(addr) ((addr) >= `TIMER_BASE && (addr) <= `TIMER_END) `define IS_DATA_MEM(addr) ((addr) >= `DATA_MEM_BASE && (addr) <= `DATA_MEM_END) `define IS_PERIPH_MEM(addr) ((addr) >= `PERIPH_BASE && (addr) <= `PERIPH_END) diff --git a/rtl/instr_mem.v b/rtl/instr_mem.v deleted file mode 100644 index 005ee09..0000000 --- a/rtl/instr_mem.v +++ /dev/null @@ -1,126 +0,0 @@ -`default_nettype none -// instr_mem.v - instruction memory for single-cycle RISC-V CPU with byte/halfword/word access - -module instr_mem #( - parameter DATA_WIDTH = 32, - parameter ADDR_WIDTH = 32, - parameter MEM_SIZE = 512 -) ( - input wire [ADDR_WIDTH-1:0] instr_addr, // Instruction fetch address (word-aligned) - input wire [ADDR_WIDTH-1:0] instr_addr_p2, // Data access address (byte-aligned) - input wire [2:0] load_type, // Load type for data access - output reg [DATA_WIDTH-1:0] instr, // Instruction output (always word) - output reg [DATA_WIDTH-1:0] instr_p2 // Data read output (byte/halfword/word) -); - -// Array of 32-bit words (keeps $readmemh compatibility) -reg [DATA_WIDTH-1:0] instr_ram [0:MEM_SIZE-1]; - -`ifdef COCOTB_SIM -initial begin - `ifdef INSTR_HEX_FILE - $display("Loading instruction memory from file: %s", `INSTR_HEX_FILE); - $readmemh(`INSTR_HEX_FILE, instr_ram); - `else - $display("No instruction file specified, initializing memory with NOPs."); - `endif - // Debug: Print first few instructions after loading - // $display("Instruction memory loaded - first few entries:"); - // $display(" [0x00]: 0x%08h", instr_ram[0]); - // $display(" [0x04]: 0x%08h", instr_ram[1]); - // $display(" [0x08]: 0x%08h", instr_ram[2]); -end -`else -initial begin - // Initialize instruction memory with NOPs - integer i; - for (i = 0; i < MEM_SIZE; i = i + 1) begin - instr_ram[i] = 32'h00000013; // Default to NOP instruction - end -end -`endif - -// Port 1: Instruction fetch (always word-aligned) -always @(*) begin - if (instr_addr[ADDR_WIDTH-1:2] < MEM_SIZE) begin - instr = instr_ram[{2'b0, instr_addr[ADDR_WIDTH-1:2]}]; // Word-aligned access - end else begin - instr = 32'h00000013; // NOP for out-of-bounds - end -end - -// Port 2: Data access with byte/halfword/word support -always @(*) begin - // Extract word address and byte offset - reg [ADDR_WIDTH-3:0] word_addr; - reg [1:0] byte_offset; - reg [31:0] word_data; - reg [7:0] byte_data; - reg [15:0] halfword_data; - - word_addr = instr_addr_p2[ADDR_WIDTH-1:2]; - byte_offset = instr_addr_p2[1:0]; - - // Read the 32-bit word containing our target data - if (word_addr < MEM_SIZE) begin - /* verilator lint_off WIDTHTRUNC */ - word_data = instr_ram[word_addr]; // Read word-aligned data - end else begin - word_data = 32'h00000000; - end - - // Extract byte based on offset (little-endian) - case (byte_offset) - 2'b00: byte_data = word_data[7:0]; - 2'b01: byte_data = word_data[15:8]; - 2'b10: byte_data = word_data[23:16]; - 2'b11: byte_data = word_data[31:24]; - endcase - - // Extract halfword based on offset (little-endian) - case (byte_offset[1]) - 1'b0: halfword_data = word_data[15:0]; // bytes 0-1 - 1'b1: halfword_data = word_data[31:16]; // bytes 2-3 - endcase - - // Handle cross-word boundary access for halfwords and words - if (byte_offset == 2'b11 && (load_type == 3'b001 || load_type == 3'b101)) begin - // Halfword access that crosses word boundary - reg [31:0] next_word_data; - if (word_addr + 1 < MEM_SIZE) begin - next_word_data = instr_ram[word_addr + 1]; - end else begin - next_word_data = 32'h00000000; - end - halfword_data = {next_word_data[7:0], word_data[31:24]}; - end - - if (byte_offset != 2'b00 && load_type == 3'b010) begin - // Word access that crosses word boundary - need to combine two words - reg [31:0] next_word_data; - if (word_addr + 1 < MEM_SIZE) begin - next_word_data = instr_ram[word_addr + 1]; - end else begin - next_word_data = 32'h00000000; - end - - case (byte_offset) - 2'b01: word_data = {next_word_data[7:0], word_data[31:8]}; - 2'b10: word_data = {next_word_data[15:0], word_data[31:16]}; - 2'b11: word_data = {next_word_data[23:0], word_data[31:24]}; - default: word_data = word_data; // No change for 2'b00 - endcase - end - - // Format output based on load type - case (load_type) - 3'b000: instr_p2 = {{24{byte_data[7]}}, byte_data}; // LB - Load Byte (sign-extend) - 3'b100: instr_p2 = {24'h0, byte_data}; // LBU - Load Byte Unsigned - 3'b001: instr_p2 = {{16{halfword_data[15]}}, halfword_data}; // LH - Load Halfword (sign-extend) - 3'b101: instr_p2 = {16'h0, halfword_data}; // LHU - Load Halfword Unsigned - 3'b010: instr_p2 = word_data; // LW - Load Word - default: instr_p2 = 32'h0; // Invalid load type - endcase -end - -endmodule diff --git a/rtl/riscv_cpu.v b/rtl/riscv_cpu.v index 8f834f6..e80cfe9 100644 --- a/rtl/riscv_cpu.v +++ b/rtl/riscv_cpu.v @@ -23,7 +23,7 @@ module riscv_cpu ( wire [31:0] pc_inst0_out; wire pc_inst0_j_signal; wire [31:0] pc_inst0_jump; - wire stall_pipeline; // For load-use hazards + wire pipeline_stall; // For load-use hazards // Branch handling: use EX stage jump signal/address assign pc_inst0_j_signal = ex_inst0_jump_signal_out; assign pc_inst0_jump = ex_inst0_jump_addr_out; @@ -32,7 +32,7 @@ module riscv_cpu ( .rst(rst), .j_signal(pc_inst0_j_signal), .jump(pc_inst0_jump), - .stall(stall_pipeline), // Stall on load-use hazard + .stall(pipeline_stall), // Stall on load-use hazard .out(pc_inst0_out) ); @@ -46,12 +46,16 @@ module riscv_cpu ( wire branch_flush; assign branch_flush = ex_inst0_jump_signal_out; // Flush IF/ID if branch taken // If branch taken, flush IF/ID by setting instruction to 0 (NOP) + wire [31:0] instruction_to_if_id; + assign instruction_to_if_id = branch_flush ? 32'h00000013 : module_instr_in; + wire if_id_stall; + assign if_id_stall = pipeline_stall && !branch_flush; IF_ID if_id_inst0 ( .clk(clk), .rst(rst), .pc_in(pc_inst0_out), - .instruction_in(branch_flush ? 32'h13 : module_instr_in), - .stall(stall_pipeline), // Stall on load-use hazard + .instruction_in(instruction_to_if_id), + .stall(if_id_stall), .pc_out(if_id_pc_out), .instruction_out(if_id_instr_out) ); @@ -89,7 +93,7 @@ module riscv_cpu ( .instr_id_ex(id_ex_inst0_instr_id_out), .rd_ex(id_ex_inst0_rd_addr_out), .rd_valid_ex(id_ex_inst0_rd_valid_out), - .stall_pipeline(stall_pipeline) + .stall_pipeline(pipeline_stall) ); // Instantiate Register File @@ -151,7 +155,7 @@ module riscv_cpu ( .pc_in(if_id_pc_out), .rs1_value_in(rf_inst0_rs1_value_out), .rs2_value_in(rf_inst0_rs2_value_out), - .stall(pipeline_flush || stall_pipeline), // Use combined flush + .stall(pipeline_flush || pipeline_stall), // Use combined flush .rs1_valid_out(id_ex_inst0_rs1_valid_out), .rs2_valid_out(id_ex_inst0_rs2_valid_out), .rd_valid_out(id_ex_inst0_rd_valid_out), @@ -455,6 +459,4 @@ module riscv_cpu ( .wr_en_out(wb_inst0_wr_en_out) ); - // Write Back Stage - endmodule diff --git a/rtl/top.v b/rtl/top.v index c856620..40db4c2 100644 --- a/rtl/top.v +++ b/rtl/top.v @@ -29,7 +29,6 @@ module top ( wire [31:0] data_mem_addr; wire [3:0] cpu_write_byte_enable; // Write byte enables wire [2:0] cpu_load_type; // Load type - wire [31:0] instr_read_data; // Timer module wires wire [31:0] timer_read_data; @@ -57,16 +56,16 @@ module top ( // Multiplex read data based on address assign mem_read_data = timer_access ? timer_read_data : - data_mem_access ? data_mem_read_data : + data_mem_access ? unified_mem_read_data : uart_access ? uart_read_data : - instr_mem_access ? instr_read_data : 32'h00000000; + instr_mem_access ? unified_mem_read_data : 32'h00000000; // Debug outputs assign pc_debug = cpu_pc_out; assign instr_debug = instr_to_cpu; // Data memory read data (separate wire for clarity) - wire [31:0] data_mem_read_data; + wire [31:0] unified_mem_read_data; // Instantiate the RISC-V CPU core riscv_cpu cpu_inst ( @@ -87,33 +86,37 @@ module top ( .module_load_type(cpu_load_type) ); - // Instantiate instruction memory - instr_mem #( - .DATA_WIDTH(32), - .ADDR_WIDTH(32), - .MEM_SIZE(131072) // 512KB / 4 bytes = 128K words - ) instr_mem_inst ( - .instr_addr(cpu_pc_out), - .instr_addr_p2(data_mem_addr), - .load_type(cpu_load_type), - .instr(instr_to_cpu), - .instr_p2(instr_read_data) - ); + // Address translation for unified memory + wire [31:0] instr_addr; + wire [31:0] data_addr; + wire data_we; + wire data_re; - // Instantiate data memory - data_mem #( - .DATA_WIDTH(32), + assign instr_addr = cpu_pc_out; + assign data_addr = instr_mem_access ? (data_mem_addr - `INSTR_MEM_BASE) : + (data_mem_addr - `DATA_MEM_BASE + `INSTR_MEM_SIZE); + assign data_we = cpu_mem_write_en && (data_mem_access || instr_mem_access); + assign data_re = cpu_mem_read_en && (data_mem_access || instr_mem_access); + + // Unified memory size covers both instruction and data regions + localparam UNIFIED_MEM_SIZE = `INSTR_MEM_SIZE + `DATA_MEM_SIZE; + + // Instantiate unified memory (parameterized like data_mem / instr_mem on main) + unified_memory #( .ADDR_WIDTH(32), - .MEM_SIZE(1048576) // 1MB in bytes - ) data_mem_inst ( + .DATA_WIDTH(32), + .MEM_SIZE(UNIFIED_MEM_SIZE) + ) unified_mem_inst ( .clk(clk), - .wr_en(cpu_mem_write_en && data_mem_access), - .rd_en(cpu_mem_read_en && data_mem_access), - .write_byte_enable(cpu_write_byte_enable), - .load_type(cpu_load_type), - .addr(data_mem_addr - `DATA_MEM_BASE), - .wr_data(cpu_mem_write_data), - .rd_data_out(data_mem_read_data) + .addr_instr(instr_addr), + .instr_out(instr_to_cpu), + .addr_data(data_addr), + .write_data(cpu_mem_write_data), + .read_data(unified_mem_read_data), + .write_enable(data_we), + .byte_enable(cpu_write_byte_enable), + .read_enable(data_re), + .load_type(cpu_load_type) ); // Instantiate timer module diff --git a/rtl/unified_memory.v b/rtl/unified_memory.v new file mode 100644 index 0000000..70ee363 --- /dev/null +++ b/rtl/unified_memory.v @@ -0,0 +1,123 @@ +`default_nettype none +`include "memory_map.vh" + +// unified_memory.v - Unified dual-port memory for instructions and data + +module unified_memory #( + parameter ADDR_WIDTH = 32, + parameter DATA_WIDTH = 32, + parameter MEM_SIZE = 2097152 // 2MB total (covers both instruction and data regions) +) ( + input wire clk, + + // Instruction port (read-only) + input wire [ADDR_WIDTH-1:0] addr_instr, + output reg [DATA_WIDTH-1:0] instr_out, + + // Data port (read/write) + input wire [ADDR_WIDTH-1:0] addr_data, + input wire [DATA_WIDTH-1:0] write_data, + output reg [DATA_WIDTH-1:0] read_data, + input wire write_enable, + input wire [3:0] byte_enable, // Byte enables for partial writes + input wire read_enable, + input wire [2:0] load_type // Load type for sign/zero extension +); + + // Byte-addressed memory array + reg [7:0] ram [0:MEM_SIZE-1]; +`ifdef COCOTB_SIM + reg [31:0] word_mem [0:`INSTR_MEM_SIZE/4-1]; +`endif + + // Initialize memory with NOPs + initial begin + integer i; + + // Initialize all memory with NOPs + for (i = 0; i < MEM_SIZE; i = i + 4) begin + ram[i + 0] = 8'h13; // NOP = 0x00000013 + ram[i + 1] = 8'h00; + ram[i + 2] = 8'h00; + ram[i + 3] = 8'h00; + end + + // Load program from hex file if defined (simulation only) + `ifdef INSTR_HEX_FILE + $display("Loading unified memory from file: %s", `INSTR_HEX_FILE); + + // Read program as words + for (i = 0; i < `INSTR_MEM_SIZE/4; i = i + 1) begin + word_mem[i] = 32'h00000013; // NOP + end + $readmemh(`INSTR_HEX_FILE, word_mem); + + // Unpack words into byte array (little-endian) + for (i = 0; i < `INSTR_MEM_SIZE/4; i = i + 1) begin + ram[i*4 + 0] = word_mem[i][7:0]; + ram[i*4 + 1] = word_mem[i][15:8]; + ram[i*4 + 2] = word_mem[i][23:16]; + ram[i*4 + 3] = word_mem[i][31:24]; + end + `endif + end + + // Instruction port (word-aligned, read-only) + // Combinational read for zero-latency instruction fetch + wire [ADDR_WIDTH-1:0] aligned_instr_addr; + assign aligned_instr_addr = {addr_instr[ADDR_WIDTH-1:2], 2'b00}; + + always @(*) begin + if (aligned_instr_addr < `INSTR_MEM_SIZE - 3) begin + // Little-endian: LSB first + instr_out = {ram[aligned_instr_addr + 3], ram[aligned_instr_addr + 2], ram[aligned_instr_addr + 1], ram[aligned_instr_addr + 0]}; + end else begin + instr_out = 32'h00000013; // NOP for out-of-bounds + end + end + + // Data port (byte-aligned, read/write) + // Combinational read logic + wire [7:0] byte_data; + wire [15:0] halfword_data; + wire [31:0] word_data; + + // Read data directly from memory (byte-addressed) + assign byte_data = (addr_data < MEM_SIZE) ? ram[addr_data] : 8'h00; + + // Read halfword (little-endian) + assign halfword_data = (addr_data < MEM_SIZE - 1) ? + {ram[addr_data + 1], ram[addr_data + 0]} : 16'h0000; + + // Read word (little-endian) + assign word_data = (addr_data < MEM_SIZE - 3) ? + {ram[addr_data + 3], ram[addr_data + 2], ram[addr_data + 1], ram[addr_data + 0]} : 32'h00000000; + + // Format output based on load type + always @(*) begin + if (read_enable) begin + case (load_type) + 3'b000: read_data = {{24{byte_data[7]}}, byte_data}; // LB (sign-extend) + 3'b100: read_data = {24'h0, byte_data}; // LBU (zero-extend) + 3'b001: read_data = {{16{halfword_data[15]}}, halfword_data}; // LH (sign-extend) + 3'b101: read_data = {16'h0, halfword_data}; // LHU (zero-extend) + 3'b010: read_data = word_data; // LW + default: read_data = 32'h0; + endcase + end else begin + read_data = 32'h0; + end + end + + // Synchronous write logic with byte enables + always @(posedge clk) begin + if (write_enable && (addr_data < MEM_SIZE)) begin + if (byte_enable[0]) ram[addr_data + 0] <= write_data[7:0]; + if (byte_enable[1] && (addr_data < MEM_SIZE - 1)) ram[addr_data + 1] <= write_data[15:8]; + if (byte_enable[2] && (addr_data < MEM_SIZE - 2)) ram[addr_data + 2] <= write_data[23:16]; + if (byte_enable[3] && (addr_data < MEM_SIZE - 3)) ram[addr_data + 3] <= write_data[31:24]; + end + end + +endmodule +`default_nettype wire