Data Memory

Imagine a warehouse where your CPU stores and retrieves data—that’s what data memory is in our single-cycle core. While instruction memory is a read-only stencil, data memory behaves like a library with both check-out (read) and return (write) operations. Let’s unpack src/data_mem.v and see how it works.


Core Parameters

parameter DATA_WIDTH = 32;   // bits per word (data size)
parameter ADDR_WIDTH = 11;   // bits for addressing (how many words?)
  • DATA_WIDTH = 32: Each memory slot holds a 32-bit value—like a 32-page data packet.

  • ADDR_WIDTH = 11: With 11 bits, you can address 2¹¹ = 2048 words (warehouse shelves).

Trade-off: 8 KB of storage (2 048 × 4 bytes) gives enough room for variables, arrays, and the stack without exhausting FPGA RAM.


Declaring the Warehouse

reg [DATA_WIDTH-1:0] mem [(1<<ADDR_WIDTH)-1:0];
  • This line sets up a 2D array: 2 048 entries, each 32 bits wide.

  • Analogy: A warehouse with 2 048 bins (addresses 0 to 2047), each bin holding a data crate of 32 bits.


Synchronous Read and Write

always @(posedge clk) begin
    if (we) begin
        mem[addr] <= write_data;   // store new data
    end
    read_data <= mem[addr];        // retrieve data
end
  • Write Enable (we): When we is high at the clock’s rising edge, new data is returned to the warehouse at slot addr.

  • Read Data: Also at the same clock edge, the content at addr is checked out and presented on read_data.

  • Same-cycle read: Both operations happen on the same rising edge—read returns the old data if we is high, or the new data, depending on synthesis rules.

Reflect: How might asynchronous reads differ? What are the pros and cons of reading immediately vs. on the clock?


Connecting to the Datapath

  1. Load (read)

    data_mem dmem(
      .clk(clk),
      .we(1'b0),         // disable write on load
      .addr(alu_result[ADDR_WIDTH-1:0]),
      .write_data(32'b0),
      .read_data(load_data)
    );
    • The ALU computes an address (alu_result). We slice it down to ADDR_WIDTH bits to index into data memory.

    • On a load instruction, we is low, so read_data returns the stored value.

  2. Store (write)

    data_mem dmem(
      .clk(clk),
      .we(mem_write),    // control logic asserts this on store ops
      .addr(alu_result[ADDR_WIDTH-1:0]),
      .write_data(reg_data),
      .read_data()       // unused
    );
    • For a store instruction, we is high. The data from the register file goes into memory at the computed address.
  3. Control Logic

    • Decodes the instruction (opcode, funct3) to generate mem_read and mem_write signals.

    • Ensures we don’t accidentally write when we only meant to read, and vice versa.


Why It Matters

Data memory is the backbone for all load/store operations. Whether you’re loading an array element or storing a computed result, this module must be reliable and fast. In advanced designs, you’ll layer caches or even dual-ported memories, but here we start with a clean, single-ported design.