A gate has limited drive strength. With many loads its output edge slows, so following logic switches late and buffer trees add further delay.
Loads N | Fan-out per stage F = 4 | Buffer levels | Extra delay at ~0.1 ns per level |
|---|---|---|---|
16 | 4 | 2 | ≈ 0.2 ns |
64 | 4 | 3 | ≈ 0.3 ns |
256 | 4 | 4 | ≈ 0.4 ns |
1024 | 4 | 5 | ≈ 0.5 ns |
A registered signal can avoid the tree by register replication. Several identical flops each drive a fraction of the loads, trading a few extra flops for a shorter path.
Read each panel from the enable flop on the left to the load flops on the right. The drawing uses 16 loads, the first row of the table; the code below uses N = 64.
In A, every load waits for two buffer stages: about 0.2 ns from the table before the logic even starts.
In B, each copy sees only 4 loads, so it needs no buffer tree and its path is a plain wire.
B costs three extra flops, and each copy still has to be kept apart from the others by the tool (next section).
Version B slices the data as q[c*PER +: PER] with PER = N / COPIES, so it requires N to be a multiple of COPIES. If it is not, integer division truncates PER and the top bits of q are never driven (they stay x); the module therefore opens with an elaboration-time $error guard that stops the build instead.
yosys result for sel_regs_b with (* keep *) on the enable net: 1 enable flop (copies merged)
yosys result with a (* keep_hierarchy *) one-flop module per copy: 4 enable flops (copies kept)The listing below uses the second approach: the en_reg module with (* keep_hierarchy *) on each instance. Synthesis keeps four instances, each with its own flop, and the testbench confirms output bit-identical to the single-enable version over 5,000 random cycles.
No. Equal values do not establish physical release alignment or recovery/removal timing. Use the approved shared release synchronizer and reset distribution tree for each destination domain; independent regional synchronizers may release on different cycles. Any registered branches need an explicit release/latency contract and implementation approval.
Reset fanout closure requires reset-tree synthesis/placement review, branch skew and min-pulse checks, recovery/removal analysis at all sinks and corners, and CDC/RDC review of interacting regions. The enable replication experiment below supplies no reset signoff evidence. See Reset tree planning for shared-synchronizer and branch methodology.
Both enable styles plus a testbench that proves they match over 5,000 random cycles.
// Version A: one enable register drives all N loads.
module sel_regs_a #(parameter int N = 64) (
input logic clk, rst_n, en_in,
input logic [N-1:0] d,
output logic [N-1:0] q
);
logic en;
always_ff @(posedge clk or negedge rst_n) if (!rst_n) en <= 1'b0; else en <= en_in;
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) q <= '0;
else if (en) q <= d; // 'en' fans out to N flip-flops
endmodule
// A one-flop module: instantiating it several times with keep_hierarchy stops synthesis from merging the copies.
module en_reg (input logic clk, rst_n, d, output logic q);
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) q <= 1'b0;
else q <= d;
endmodule
// Version B: the enable register is replicated, each copy drives N/COPIES loads.
module sel_regs_b #(parameter int N = 64, parameter int COPIES = 4) (
input logic clk, rst_n, en_in,
input logic [N-1:0] d,
output logic [N-1:0] q
);
localparam int PER = N / COPIES;
if (N % COPIES != 0) $error("N must be a multiple of COPIES"); // elaboration-time guard: otherwise the top bits stay undriven
logic [COPIES-1:0] en;
for (genvar c = 0; c < COPIES; c++) begin : g_copy
(* keep_hierarchy *) en_reg u_en (.clk, .rst_n, .d(en_in), .q(en[c])); // one real flop per copy
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) q[c*PER +: PER] <= '0;
else if (en[c]) q[c*PER +: PER] <= d[c*PER +: PER]; // each copy drives only PER flops
end
endmodule
module tb_fan;
timeunit 1ns; timeprecision 1ps;
localparam int N = 64;
logic clk = 0, rst_n = 1, en_in = 0;
logic [N-1:0] d = '0, qa, qb;
sel_regs_a #(N) a (.clk, .rst_n, .en_in, .d, .q(qa));
sel_regs_b #(N, 4) b (.clk, .rst_n, .en_in, .d, .q(qb));
always #5 clk = ~clk;
int errors = 0;
initial begin
#1 rst_n = 0; @(posedge clk); #1 rst_n = 1;
for (int i = 0; i < 5000; i++) begin
@(negedge clk);
en_in = $urandom_range(0, 1);
d = {$urandom, $urandom};
@(posedge clk); #1;
if (qa !== qb) errors++;
end
if (errors == 0) $display("PASS: replicating the enable register changes nothing functionally (5000 cycles, 64-bit data)");
else $fatal(1, "FAIL: %0d mismatches", errors);
$finish;
end
endmoduleYosys results: sel_regs_b with (* keep ) on the enable net gives 1 enable flop (copies merged). With a ( keep_hierarchy *) one-flop module per copy it gives 4 enable flops. The two versions match over 5,000 random cycles.
The enable replication experiment supplies no reset signoff evidence. Reset fanout needs reset-tree review, recovery and removal analysis at all sinks and corners, and CDC and RDC review.