Arrays, memories and generate constructs

Learning objectives
  • Distinguish packed vectors from unpacked arrays
  • Model synchronous memories
  • Define read-during-write behavior
  • Use procedural loops
  • Create repeated or conditional hardware with generate constructs
1 Objective→ 2 Learn→ 3 Example→ 4 Practice→ 5 Verify→ 6 Continue
How to study this lessonUnderstand the idea first. Use the example to see the behaviour, practise where provided, then make sure you can explain the result before continuing.
addrwdatawrite enable
→
mem[0]mem[1]…mem[N-1]
→
rdata
Concept diagram: Arrays, memories and generate constructs

Explanation

Vectors and memories

reg [7:0] data_byte;       // one packed 8-bit vector
reg [7:0] mem [0:255];     // 256 unpacked entries, each 8 bits

In Verilog, the range before the name sets the packed word width. The range after the name indexes separate elements. Review address width and legal bounds whenever an array is accessed.

Synchronous single-port memory

module sync_ram #(
    parameter DATA_WIDTH = 8,
    parameter ADDR_WIDTH = 4
) (
    input  wire                  clk,
    input  wire                  we,
    input  wire [ADDR_WIDTH-1:0] addr,
    input  wire [DATA_WIDTH-1:0] wdata,
    output reg  [DATA_WIDTH-1:0] rdata
);
    localparam DEPTH = (1 << ADDR_WIDTH);
    reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];

    always @(posedge clk) begin
        if (we)
            mem[addr] <= wdata;
        rdata <= mem[addr];
    end
endmodule

This template registers the read data. During a write and read to the same address, the visible result depends on the specified behavior and tool mapping. Define whether the interface returns old data, new data, or retains the previous output. Match the approved memory template for the target technology.

Resetting every memory bit can prevent inference of a dense RAM. Often the interface resets valid flags, pointers, or output registers instead of the whole storage array.

Initializing memory from a file

For simulation, $readmemh and $readmemb load hexadecimal or binary words into an unpacked memory. For example, after declaring mem in a testbench or simulation model:

initial $readmemh("program.hex", mem);

Keep the file in the documented run directory and verify its depth, word width, and address order. A simulator accepting this construct does not prove that a target synthesis flow will implement the same initialization. Check the FPGA/ASIC tool's memory-initialization rules and the actual inferred memory; do not use a file load as a substitute for a required reset sequence.

Procedural loops describe replicated logic

integer i;
always @* begin
    parity = 1'b0;
    for (i = 0; i < WIDTH; i = i + 1)
        parity = parity ^ data[i];
end

For synthesis, loop bounds must be statically determinable. The loop describes unrolled combinational hardware; it does not reuse one XOR gate across simulated iterations.

Generate creates structure during elaboration

genvar g;
generate
    for (g = 0; g < WIDTH; g = g + 1) begin : gen_bit
        assign y[g] = a[g] & b[g];
    end
endgenerate

The named block gives each generated instance a stable hierarchical path. An if generate can select structure using a parameter:

generate
    if (REGISTER_OUTPUT) begin : gen_registered
        always @(posedge clk) y <= next_y;
    end else begin : gen_comb
        always @* y = next_y;
    end
endgenerate

Both branches must declare and drive signals consistently for the selected configuration.

Worked example

A RAM with ADDR_WIDTH=4 has 16 entries, not four. Each additional address bit doubles depth. With DATA_WIDTH=8, its logical capacity is 128 bits. Implementation overhead and physical macro size are separate concerns.

Check yourself

Which declaration dimension selects a memory entry? Why might resetting every entry prevent RAM inference? How does a generate loop differ from a time-consuming simulation loop?