A combinational grant with no assignment for the idle case must hold its old value. Synthesis builds a latch, and the wave shows a stale grant while no request is active.
// yosys: latch
`timescale 1ns/1ps
module arb_grant_bug (
input logic req0, req1,
output logic [1:0] grant
);
always_comb begin
if (req0) grant = 2'd0;
else if (req1) grant = 2'd1;
// no default: grant holds its old value when both requests are 0
end
endmoduleThe fix assigns the output first. The idle code 3 parks the grant where no request can be mistaken for one.
// yosys: ok
`timescale 1ns/1ps
module arb_grant_fixed (
input logic req0, req1,
output logic [1:0] grant
);
always_comb begin
grant = 2'd3; // idle parks where no request can be mistaken
if (req0) grant = 2'd0;
else if (req1) grant = 2'd1;
end
endmoduleWatch grant against req0 and req1. The idle slots keep the previous grant instead of parking.
Slot 0: req0 is 1, so grant is 0.
Slot 1: req1 is 1, so grant is 1.
Slot 2: both requests are 0, but grant holds 1 instead of parking.
Slot 3: req0 is 1, so grant is 0.
Slot 4: both requests are 0, but grant holds 0.
Slot 5: req1 is 1, so grant is 1.
Within one process, statement order is deterministic: a = din; b = a makes both registers sample din at the rising edge. The second stage adds no delay. Input-to-output behavior still waits for that edge; this is not a zero-latency combinational path or an interprocess race, and synthesis can agree with this collapsed behavior.
`timescale 1ns/1ps
module pipe_bug (
input logic clk, din,
output logic a, b
);
always @(posedge clk) begin
a = din; // BUG: blocking in clocked logic updates at once
b = a; // reads the just-written a
end
endmoduleNonblocking assignments sample together at the edge. Each stage takes the previous value, so the pipeline delays by one stage per flop.
`timescale 1ns/1ps
module pipe_fixed (
input logic clk, din,
output logic a, b
);
always_ff @(posedge clk) begin
a <= din;
b <= a;
end
endmoduleWatch b against a and din. Every slot shows all three equal: the second stage adds no delay.
Slot 1: din is 1, so a is 1 and b is already 1.
Slot 2: din is 0, so a is 0 and b is already 0.
Slot 3: din is 1, so a is 1 and b is already 1.
Slot 4: din is 0, so a is 0 and b is already 0.
Slot 5: din is 1, so a is 1 and b is already 1.
Slot 6: din is 0, so a is 0 and b is already 0.
With nonblocking assignments b takes the previous a. Varied input history distinguishes that extra stage; repeated input values can make correct stages equal too. The finite bench below checks both histories after register updates.
// snippet: deliberately racy; do not require either simulator ordering
`timescale 1ns/1ps
always @(posedge clk) a = din;
always @(posedge clk) b = a;
// If the first process runs first, b sees new din; if the second runs
// first, b sees old a. Use nonblocking assignments in both processes
// to sample old state independently of active-region execution order.Before edge: a=0, din=1 | After edge |
|---|---|
a process executes first | a=1, b=1 |
b process executes first | a=1, b=0 |
Both use nonblocking assignments | a=1, b=0 after NBA, independent of ordering |
Inspect combinational assignment coverage: the shown block omits the idle assignment and retains a stale grant. A waveform alone does not exclude other causes, including scheduling races; confirm the source and input history.
Equality alone proves no bug: repeated inputs also produce equal stages. Compare a varied input history with the intended latency, then inspect assignments. For the single-process collapse shown here, nonblocking assignments restore the extra stage.
Both bug halves beside their fixes, with a reference that counts the symptoms.
`timescale 1ns/1ps
module arb_grant_bug2 (
input logic req0, req1,
output logic [1:0] grant
);
always_comb begin
if (req0) grant = 2'd0;
else if (req1) grant = 2'd1;
end
endmodule
module arb_grant_fixed2 (
input logic req0, req1,
output logic [1:0] grant
);
always_comb begin
grant = 2'd3;
if (req0) grant = 2'd0;
else if (req1) grant = 2'd1;
end
endmodule
module pipe_bug2 (
input logic clk, rst_n, din,
output logic a, b
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
a = 1'b0;
b = 1'b0;
end else begin
a = din;
b = a;
end
end
endmodule
module pipe_fixed2 (
input logic clk, rst_n, din,
output logic a, b
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
a <= 1'b0;
b <= 1'b0;
end else begin
a <= din;
b <= a;
end
end
endmodule