Arrays, memories and generate constructs
- 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
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?