Key dimensions flow from the top level to the leaves using a single source of truth. Hard-coded leaf constants diverge from integration intent.
Each level passes values by name. The top owns DATA_W and FIFO_D, the fabric maps them onto DATA_W and DEPTH, and the fifo receives WIDTH and DEPTH.
Read the boxes from the outside in and follow one value, such as 8, through every level.
8 changes name once. It is FIFO_D in the top and DEPTH from the fabric down, because the fabric's override is .DEPTH(FIFO_D).
16 changes name once. It is DATA_W down to the fabric and WIDTH in the fifo, through .WIDTH(DATA_W).
The value never changes. The bench reads dut.u_fabric.u_cmd_fifo.WIDTH and DEPTH and requires 16 and 8.
Level | Parameter | Arrives as |
|---|---|---|
|
| Owned here |
|
| Named passing from the top |
|
| Named passing from the fabric |
// snippet
// the legacy named-passing shape, kept with real ports
fabric #(
.DATA_W (DATA_W),
.DEPTH (FIFO_D)
) u_fabric (
.clk_i (clk_i),
.rst_ni (rst_ni),
...
);Exact connection sizing is enforced by lint. A $bits(wdata_i) != DATA_W check inside the module is tautological: the declaration already uses DATA_W. A 16-bit unsigned wire driving a 24-bit input is zero-extended; a wider output driving a narrower receiver is truncated. Signed input extension depends on the connected expression. Treat port-width lint diagnostics as errors. The bench separately checks actual descendant parameter values, depth behavior, and preservation of selected words; data round trips alone can miss a 32-bit leaf behind 16-bit wires.
The fifo here is a power-of-two template: DEPTH must be at least 2 and a power of two, and g_depth_check rejects anything else at elaboration. For other depths, use the modulo-wrap fifo in the avoid-defparam-overrides lesson of the parameterization and generate chapter, whose pointers wrap at DEPTH - 1 for any depth.
Depth is checked functionally: with FIFO_D=8, the bench requires space before each write and full only after the eighth accepted write. Exact descendant parameter checks provide separate configuration evidence.
DATA_W to 16 but the leaf was also edited to 24. What catches it?Lint, not elaboration: connecting a 16-bit signal to a 24-bit port only warns and pads the port in simulators, so run lint with width warnings as errors. A padded port can still round-trip the narrower word, so the simulation passes and only lint flags it.
Non-default requests expose missing forwarding in the descendant-parameter checks. Behavior alone need not fail: a default 32-bit leaf can preserve all tested 16-bit words. That counterexample is source reasoning, not an executed mutation.
Two hierarchy levels with non-default dimensions. The finite bench checks exact descendant configuration separately from selected data preservation and full/empty behavior. Connection lint remains a separate obligation.
module param_fifo #(
parameter int WIDTH = 32,
parameter int DEPTH = 16
) (
input logic clk_i,
input logic rst_ni,
input logic wr_i,
input logic [WIDTH-1:0] wdata_i,
input logic rd_i,
output logic [WIDTH-1:0] rdata_o,
output logic full_o,
output logic empty_o
);
localparam int AW = $clog2(DEPTH);
logic [WIDTH-1:0] mem [DEPTH];
logic [AW-1:0] wptr, rptr;
logic [AW:0] count;
// elaboration-time contract: power of two, DEPTH >= 2 (this template wraps pointers by overflow)
if (DEPTH < 2 || DEPTH != (1 << AW)) begin : g_depth_check
$error("param_fifo: DEPTH must be a power of two and at least 2");
end
assign full_o = (count == DEPTH);
assign empty_o = (count == '0);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
wptr <= '0;
rptr <= '0;
count <= '0;
rdata_o <= '0;
end else begin
case ({wr_i && !full_o, rd_i && !empty_o})
2'b10: begin
mem[wptr] <= wdata_i;
wptr <= wptr + 1'b1;
count <= count + 1'b1;
end
2'b01: begin
rdata_o <= mem[rptr];
rptr <= rptr + 1'b1;
count <= count - 1'b1;
end
2'b11: begin
mem[wptr] <= wdata_i;
rdata_o <= mem[rptr];
wptr <= wptr + 1'b1;
rptr <= rptr + 1'b1;
end
default: begin end
endcase
end
end
endmodule
module fabric #(
parameter int DATA_W = 32,
parameter int DEPTH = 16
) (
input logic clk_i,
input logic rst_ni,
input logic wr_i,
input logic [DATA_W-1:0] wdata_i,
input logic rd_i,
output logic [DATA_W-1:0] rdata_o,
output logic full_o,
output logic empty_o
);
param_fifo #(
.WIDTH (DATA_W),
.DEPTH (DEPTH)
) u_cmd_fifo (
.clk_i (clk_i),
.rst_ni (rst_ni),
.wr_i (wr_i),
.wdata_i (wdata_i),
.rd_i (rd_i),
.rdata_o (rdata_o),
.full_o (full_o),
.empty_o (empty_o)
);
endmodule
module soc_top #(
parameter int DATA_W = 32,
parameter int FIFO_D = 16
) (
input logic clk_i,
input logic rst_ni,
input logic wr_i,
input logic [DATA_W-1:0] wdata_i,
input logic rd_i,
output logic [DATA_W-1:0] rdata_o,
output logic full_o,
output logic empty_o
);
fabric #(
.DATA_W (DATA_W),
.DEPTH (FIFO_D)
) u_fabric (
.clk_i (clk_i),
.rst_ni (rst_ni),
.wr_i (wr_i),
.wdata_i (wdata_i),
.rd_i (rd_i),
.rdata_o (rdata_o),
.full_o (full_o),
.empty_o (empty_o)
);
endmodule
Testbench run with Icarus Verilog 13.0 (2026-10-16): prints PASS. It checks exact descendant configuration separately from selected data preservation and full and empty behaviour, with FIFO_D equal to 8. It is a finite digital check. The default-width counterexample in the check-yourself answer is reasoning from the source, not a run mutation.