← 04 Low Power, Area and Synthesis | Index | 06 Verification and Testbench →
Round 05 — Combined answers (Gemini · GPT · Claude)
Contents
- 0. Resume probe
- Part A — Rapid-fire snippets (0:05–0:20, ~60 s each)
- Part B — Live code review (0:20–0:50)
- Part C — Failing-test debug scenario (0:50–0:55)
- Part D — Your questions (0:55–1:00)
0. Resume probe
M5Q1 · "What is the worst bug you have ever let through review? What did you change afterwards?"
Gemini Answer
- Situation: When absorbing legacy TSF/beacon blocks and reviewing a timer-comparison update, a subtle signed vs. unsigned width-extension bug slipped through review where a 16-bit microsecond offset was subtracted from a 64-bit counter without guarding against underflow when
next_tbtt < pre_tbtt_usright after reset. - Symptom: On cold boot with a small initial TSF,
next_tbtt - pre_tbtt_uswrapped around to64'hFFFF_FFFF_FFFF_xxxx, suppressing the first pre-TBTT interrupt. - What I changed afterwards:
- Added a mandatory mixed-width arithmetic and unsigned-subtraction underflow check to my code review checklist and implemented an AST-level width/signedness diagnostic rule in
lucisv. - Made it standard practice to write a boundary SVA (
next_tbtt >= pre_tbtt_usor rewrite subtractions as additionstsf_q + pre_tbtt_us >= next_tbtt_us) during review.
- Added a mandatory mixed-width arithmetic and unsigned-subtraction underflow check to my code review checklist and implemented an AST-level width/signedness diagnostic rule in
GPT Answer
"My resume combines TSF/beacon ownership, block verification, and lucisv tooling. I separate mechanically detectable RTL defects from protocol/CDC defects: lint is valuable, but a clean lint report cannot prove timestamp coherence or correct beacon scheduling." Use a real project bug if asked for an incident; the resume does not describe one.
Claude Answer
Template: pick a real one, and structure it as symptom → root cause → why review missed
it → what changed. Example shape that fits this profile:
"An equality compare on a timestamp in a block I reviewed. It was correct for a TSF that
steps by one, and the review happened before the smooth-drift correction existed; once the
TSF could double-step the event was skipped forever, and it showed up as a missed TWT
service period weeks later in FPGA. Review missed it because the diff was clean and the
assumption lived outside the diff. Afterwards I added a lint rule in lucisv flagging ==
against any signal tagged as a timer, and a review-checklist line: 'what invariant does
this compare assume, and who owns it'."
Part A — Rapid-fire snippets (0:05–0:20, ~60 s each)
For each snippet: what is wrong, what is the silicon symptom, would a linter catch it?
M5Q2 · A1. Snippet 1
always_comb begin
if (sel) y = a;
end
Gemini Answer
| # | What is wrong | Silicon / Sim Symptom | Caught by Linter? |
|---|---|---|---|
| A1 | Missing else / default assignment in always_comb. | Infers a transparent level-sensitive latch on y when sel == 0 (glitch/timing hazard). | Yes (hard error in always_comb). |
GPT Answer
Missing assignment when sel=0: unintended latch/stale output. Add an else or default. Lint/synthesis should flag.
Claude Answer
y not assigned when !sel → latch. Symptom: timing loop on a transparent latch,
holds stale data, DFT headaches. Lint: yes.
M5Q3 · A2. Snippet 2
logic [7:0] a, b, sum;
assign sum = a + b;
Gemini Answer
| # | What is wrong | Silicon / Sim Symptom | Caught by Linter? |
|---|---|---|---|
| A2 | Adding two 8-bit numbers into an 8-bit target sum. | Carry-out bit (bit 8) is silently truncated, overflowing for any sum | Yes (width-truncation warning, though often waived unless strict). |
GPT Answer
Eight-bit addition discards carry. It is a bug only if modulo-256 behavior is unintended. For full sum, use a 9-bit result and explicit extensions. Width lint may flag; intent needs review.
Claude Answer
8-bit sum of two 8-bit operands drops the carry. Symptom: silent wrap at 256.
Lint: yes (width/overflow warning), if the rule is enabled.
M5Q4 · A3. Snippet 3
always_ff @(posedge clk) begin
cnt = cnt + 1;
if (cnt == MAX) done <= 1'b1;
end
Gemini Answer
| # | What is wrong | Silicon / Sim Symptom | Caught by Linter? |
|---|---|---|---|
| A3 | Blocking assignment cnt = cnt + 1 mixed with <= inside always_ff. | if (cnt == MAX) compares the already-incremented cnt (fires 1 cycle early at MAX-1), and any other always_ff reading cnt has a simulation race! | Yes (blocking in always_ff + mixed =/<= error). |
GPT Answer
Blocking assignment makes the comparison see the incremented value; other processes can race with cnt. Use a sized next-count expression and nonblocking state assignments. Also decide whether done is sticky or needs a default clear. Lint catches blocking-in-sequential; specification review resolves off-by-one behavior.
Claude Answer
Blocking cnt in always_ff, then done compares the already-incremented value:
fires one count early, and other blocks reading cnt race in sim. Lint: yes.
M5Q5 · A4. Snippet 4
case (op)
2'b00: y = a & b;
2'b01: y = a | b;
2'b10: y = a ^ b;
endcase
Gemini Answer
| # | What is wrong | Silicon / Sim Symptom | Caught by Linter? |
|---|---|---|---|
| A4 | Incomplete case (op) missing 2'b11 / default. | In always_comb, infers a latch on y holding the stale value when op == 2'b11. | Yes (incomplete case / latch error). |
GPT Answer
Missing op=11/default assignment can infer a latch in a combinational process. Add a safe default and an illegal-op assertion. Lint catches incomplete assignment/case.
Claude Answer
No 2'b11 item and no default. In always_comb that's a latch on y; if
unique/full_case is applied, 2'b11 is a don't-care and synthesis picks whatever is
cheapest, so sim and silicon can differ. Lint: yes.
M5Q6 · A5. Snippet 5
logic [3:0] a;
if (a == 4'b1xxx) hit = 1'b1; else hit = 1'b0;
Gemini Answer
| # | What is wrong | Silicon / Sim Symptom | Caught by Linter? |
|---|---|---|---|
| A5 | Logical equality a == 4'b1xxx with X literals. | In simulation, a == 4'b1xxx evaluates to 1'bx, so if always takes the else branch (hit = 0)! Synthesis treats x as don't-care (hit = a[3]) → fatal sim-vs-synth mismatch. | Yes (X-literal in == error; use casez or inside). |
GPT Answer
== 4'b1xxx does not mean wildcard matching: it can return X, and if treats X as not true. If only the top bit matters, compare a[3] and check unknowns separately. Lint can flag X comparisons.
Claude Answer
== against a literal with x is never true in sim, so hit is stuck at 0, while
synthesis may read the x as don't-care and build hit = a[3]. Classic sim/synth
mismatch. Lint: yes in most tools. Fix: casez/inside with ?, or a[3].
M5Q7 · A6. Snippet 6
// valid/ready sink
assign out_valid = in_valid & out_ready;
assign in_ready = out_ready;
Gemini Answer
| # | What is wrong | Silicon / Sim Symptom | Caught by Linter? |
|---|---|---|---|
| A6 | out_valid depends combinationally on out_ready (in_valid & out_ready). | Violates AXI/valid-ready protocol: if downstream waits for out_valid == 1 before asserting out_ready == 1, the bus deadlocks permanently. | No (syntactically valid; caught by SVA protocol checker or human review). |
GPT Answer
Valid depends on ready, violating the source contract and risking deadlock with a receiver waiting for valid. For a transparent path use out_valid=in_valid, with the appropriate data path and in_ready=out_ready; add storage when cutting the ready path. Protocol checks/review, sometimes specialized lint.
Claude Answer
out_valid depends on out_ready: violates the valid/ready rule. A sink that waits
for valid before raising ready deadlocks. Lint: no; protocol checker or human.
M5Q8 · A7. Snippet 7
localparam int DEPTH = 16;
logic [$clog2(DEPTH)-1:0] wptr, rptr;
assign full = (wptr == rptr) && wrapped; // wrapped maintained elsewhere
assign empty = (wptr == rptr);
Gemini Answer
| # | What is wrong | Silicon / Sim Symptom | Caught by Linter? |
|---|---|---|---|
| A7 | empty = (wptr == rptr) does not check !wrapped (and 4-bit ptrs lack an explicit wrap MSB). | When the FIFO becomes full (wptr == rptr && wrapped), empty also asserts 1'b1 simultaneously! | No (semantic bug; requires human review or FIFO SVA !(full && empty)). |
GPT Answer
Equal pointers can mean full or empty; the current expressions assert both at full. If wrapped means different wrap phases, empty must also require !wrapped; alternatively use extended pointers or an occupancy count. Formal/reference FIFO checks are stronger than generic lint.
Claude Answer
4-bit pointers for depth 16 can't tell full from empty; the hand-rolled wrapped
flag has to be updated on every wrap of either pointer and is wrong the first time both
wrap in the same cycle. Use $clog2(DEPTH)+1-bit pointers. Lint: no.
M5Q9 · A8. Snippet 8
logic signed [15:0] x;
assign y = x >> 1;
Gemini Answer
| # | What is wrong | Silicon / Sim Symptom | Caught by Linter? |
|---|---|---|---|
| A8 | Logical right shift >> on a signed variable x. | Zero-fills MSB instead of sign-extending (-4 >> 1 becomes +32766 instead of -2). Should use arithmetic shift >>>. | Yes (in strict linters like SpyGlass / lucisv signed-shift rule). |
GPT Answer
>> zero-fills even for signed x. Use >>> with a signed left operand and explicit output width. Negative samples otherwise change value incorrectly. Signedness lint may help.
Claude Answer
Logical shift on a signed value zero-fills the sign bit: negative numbers become
large positives. Need >>> (and y declared signed). Lint: some tools.
M5Q10 · A9. Snippet 9
always_ff @(posedge clk_b) begin
data_b <= data_a; // data_a is a 32-bit bus from clk_a domain
valid_b <= valid_a_sync; // valid_a_sync is 2-flop synchronized
end
Gemini Answer
| # | What is wrong | Silicon / Sim Symptom | Caught by Linter? |
|---|---|---|---|
| A9 | Multi-bit bus data_a[31:0] sampled across clock domains on the same cycle as valid_a_sync. | No guarantee data_a was stable when valid_a launched or that routing skew didn't delay data_a past valid_a_sync; needs an enable pulse valid_rise + MCP stability constraint. | CDC Tool: Yes (DMUX / unsynchronized multi-bit crossing). Syntax Linter: No. |
GPT Answer
Synchronizing valid does not by itself make the 32-bit bus coherent; unconditional sampling can capture torn data. Use a held payload and destination capture after synchronized control, with acknowledgment and physical timing bounds, or an async FIFO. CDC analysis plus protocol review.
Claude Answer
32-bit bus captured on a separately synchronized valid: nothing guarantees data_a
is stable when valid_b arrives, and the bus itself is sampled with no sync. Needs data held
≥ 2–3 clk_b cycles around valid with set_max_delay -datapath_only, or a handshake. CDC
tool: yes; lint: no.
M5Q11 · A10. Snippet 10
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) q <= '0;
else if (en) q <= d;
always_ff @(posedge clk)
if (clear) q <= '0;
Gemini Answer
| # | What is wrong | Silicon / Sim Symptom | Caught by Linter? |
|---|---|---|---|
| A10 | Signal q is driven in two separate always_ff blocks. | Multi-driven net / synthesis compilation error (cannot connect two flip-flop outputs together). | Yes (hard error in synthesis & lint). |
GPT Answer
Two processes drive q. Merge into one sequential block with explicit reset/clear/enable priority. Lint and synthesis should reject/flag.
Claude Answer
q driven from two always_ff blocks, one async-reset and one not. Multi-driven
net: compile/synthesis error, or last-wins in a lenient flow. Lint: yes, hard error.
M5Q12 · A11. Snippet 11
genvar i;
integer k;
generate for (i = 0; i < N; i++) begin
always_ff @(posedge clk) for (k = 0; k < 4; k++) acc[i] <= acc[i] + in[i][k];
end endgenerate
Gemini Answer
| # | What is wrong | Silicon / Sim Symptom | Caught by Linter? |
|---|---|---|---|
| A11 | (1) Shared static integer k across all generated always_ff blocks; (2) non-blocking <= accumulation inside a for loop. | (1) Sim race on k across parallel instances; (2) acc[i] <= acc[i] + in[i][k] overwrites the RHS schedule 4 times—only k=3 (in[i][3]) is actually added! | Yes (k shared var + NBA in loop warning). |
GPT Answer
All nonblocking RHS values use old acc[i]; the last assignment wins rather than summing four terms. Compute a properly widened temporary sum, then one NBA per accumulator. A shared module-scope loop variable is also poor practice; use local loop variables and named generate scopes. Lint can flag repeated assignments; arithmetic intent requires review.
Claude Answer
integer k is shared by all N generated always_ff blocks → sim race on k. And
the non-blocking accumulate in a loop only keeps the last iteration (acc[i] + in[i][3]),
not the sum. Lint: partial (shared loop variable yes, the semantic error no).
M5Q13 · A12. Snippet 12
assign tbtt_fire = (tsf_us == next_tbtt_us);
Gemini Answer
(tsf_us is a 64-bit timestamp that normally increments by 1 but can be corrected by ±N by
a sync engine.)
| # | What is wrong | Silicon / Sim Symptom | Caught by Linter? |
|---|---|---|---|
| A12 | Exact equality tsf_us == next_tbtt_us on a timestamp that can jump or double-step by | If a next_tbtt_us, tbtt_fire never asserts and TBTT hangs forever! Must use >=. | No (requires domain/protocol human review). |
GPT Answer
(tsf_us is a 64-bit timestamp that normally increments by 1 but can be corrected by ±N by
a sync engine.)
Equality misses targets when TSF skips over them and can remain true across a pause. Use a wrap-aware crossing/greater-or-equal check with one-shot target advancement, coherent snapshots, and a defined jump policy. Needs architectural checks.
Claude Answer
(tsf_us is a 64-bit timestamp that normally increments by 1 but can be corrected by ±N by
a sync engine.)
Equality on a timestamp that can skip values under slew correction: the event is
missed forever. Use >= with a one-shot. Lint: no; human.
Part B — Live code review (0:20–0:50)
Read the following module as if it had just landed in your review queue. Narrate what you
look at first. Produce a prioritised findings list (silicon-killer / functional /
style). Then answer: which findings would a linter catch, which need a CDC tool, which need a
human?// tbtt_gen.sv -- Target Beacon Transmission Time generator // // Generates a one-cycle tbtt_pulse when the 64-bit TSF reaches the next TBTT and a // pre_tbtt_pulse `pre_tbtt_us` microseconds before it so firmware can prepare the // beacon. TSF is in microseconds. Beacon interval is in TUs (1 TU = 1024 us). // // Clock domains: // clk : MAC core clock (160 MHz) // tsf_tick : 1 MHz tick from the always-on timer block (separate domain) // tsf_us : 64-bit TSF value, updated by the timer block on tsf_tick module tbtt_gen #( parameter int BI_W = 16 )( input logic clk, input logic rst_n, // config from register block (clk domain) input logic en, input logic [BI_W-1:0] beacon_interval_tu, input logic [15:0] pre_tbtt_us, input logic cfg_wr, // one-cycle pulse on any config write // TSF from timer block input logic tsf_tick, input logic [63:0] tsf_us, // outputs output logic tbtt_pulse, output logic pre_tbtt_pulse, output logic [63:0] next_tbtt_us, output logic tbtt_missed // sticky, cleared by cfg_wr ); typedef enum logic [1:0] {IDLE, ARMED, FIRED} state_e; state_e state, state_d; logic [BI_W-1:0] bi_us; logic tick_q, tick_rise; logic [63:0] tsf_q; logic armed; logic pre_hit, tbtt_hit; // --------------------------------------------------------------- // Beacon interval in microseconds assign bi_us = beacon_interval_tu << 10; // --------------------------------------------------------------- // Tick edge detect and TSF sample always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) begin tick_q <= 1'b0; tsf_q <= '0; end else begin tick_q <= tsf_tick; if (tick_rise) tsf_q <= tsf_us; end end assign tick_rise = tsf_tick & ~tick_q; // --------------------------------------------------------------- // Arm / state machine always_comb begin case (state) IDLE: if (en && cfg_wr) state_d = ARMED; ARMED: if (tbtt_hit) state_d = FIRED; FIRED: state_d = ARMED; endcase end always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) begin state <= IDLE; armed <= 1'b0; end else begin state <= state_d; armed = (state_d == ARMED) || (state_d == FIRED); end end // --------------------------------------------------------------- // Next TBTT tracking assign tbtt_hit = armed && (tsf_q >= next_tbtt_us); assign pre_hit = armed && (tsf_q == next_tbtt_us - pre_tbtt_us); always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) begin next_tbtt_us <= '0; end else if (cfg_wr) begin next_tbtt_us <= tsf_q + bi_us; end else if (tbtt_hit) begin next_tbtt_us <= next_tbtt_us + bi_us; end end always_ff @(posedge clk) begin if (!en) next_tbtt_us <= '0; end // --------------------------------------------------------------- // Outputs always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) begin tbtt_pulse <= 1'b0; pre_tbtt_pulse <= 1'b0; end else begin tbtt_pulse <= tbtt_hit & tick_rise; pre_tbtt_pulse <= pre_hit & tick_rise; end end always_ff @(posedge clk) begin if (tbtt_hit && (tsf_q > next_tbtt_us + 64'd1)) tbtt_missed <= 1'b1; else if (cfg_wr) tbtt_missed <= 1'b0; end endmodule
M5Q14 · B-overview. Overall approach
Gemini Answer
How I Read a Module First (30-Second Scan)
- Header & Clock/Reset Boundary: Check every input port's clock domain (
clkat 160 MHz vs.tsf_tick/tsf_usat 1 MHz) and reset polarity. - Sequential Blocks & Multi-Drivers: Scan all
always_ffblocks for reset consistency, blocking assignments, and multiply-driven signals. - Combinational Blocks & Widths: Scan
assignandalways_combfor latches, bit-shift truncations, and underflow/overflow. - Protocol & Corner-Case Behavior: Trace TSF jumps, slew pauses/skips, and wrap-around.
Prioritized Findings List
Priority 1 — Silicon-Killers (CDC, Synthesis Failures, Latches, Truncation)
- [CDC Silicon-Killer — Lines 160–169] Unsynchronized async input
tsf_tick& combinational edge detecttick_rise:- Symptom:
tsf_tickcomes from an asynchronous 1 MHz domain and is used directly inassign tick_rise = tsf_tick & ~tick_q;and captured into a single floptick_q.tick_risewill glitch and go metastable, corrupting the 64-bittsf_qload enable and output pulses (tbtt_pulse). - Fix: Pass
tsf_tickthrough a 2-flop synchronizer (tick_sync1_q,tick_sync2_q) and a 3rd delay flop (tick_sync3_q), then formtick_rise = tick_sync2_q & ~tick_sync3_q. Ensuretsf_usis held stable in the timer domain forclkcycles aftertsf_tickrises. - Caught by: CDC tool (and
lucisvclock-domain rule).
- Symptom:
- [Multi-Driven Register — Lines 196–208]
next_tbtt_usdriven in twoalways_ffblocks:- Symptom:
next_tbtt_usis assigned in both the async-reset block (line 196) and the reset-lessif (!en)block (line 206). This will fail synthesis with a multi-driven net error. - Fix: Merge
if (!en) next_tbtt_us <= '0;into the singlealways_ffblock at line 196. - Caught by: Linter / Compiler.
- Symptom:
- [Severe Width Truncation — Line 148 & 156]
bi_usis[BI_W-1:0](16 bits) but assignedbeacon_interval_tu << 10:- Symptom: Shifting a 16-bit TU value left by 10 bits inside a 16-bit wire truncates the upper 10 bits! Standard beacon interval
100 TU() truncates to 16'h9000(), and any multiple of truncates to 0, causing an infinite TBTT loop! - Fix: Declare
logic [BI_W+9:0] bi_us; assign bi_us = {beacon_interval_tu, 10'b0};. - Caught by: Linter (shift width truncation warning).
- Symptom: Shifting a 16-bit TU value left by 10 bits inside a 16-bit wire truncates the upper 10 bits! Standard beacon interval
- [Inferred Latch in FSM — Lines 173–179]
state_dhas no default assignment inalways_comb:- Symptom: When
state == IDLE && !(en && cfg_wr), orstate == ARMED && !tbtt_hit, orstate == 2'b11,state_dis unassigned → infers a latch onstate_d. - Fix: Add
state_d = state;at the top ofalways_combanddefault: state_d = IDLE;incase (state). - Caught by: Linter.
- Symptom: When
- [Missing Reset on Sticky Status Output — Lines 222–227]
tbtt_missedhas no reset:- Symptom: Powers up as
Xin simulation and random0/1in silicon, firing false missed-TBTT status to firmware beforecfg_wrever occurs. - Fix: Add
negedge rst_nreset (if (!rst_n) tbtt_missed <= 1'b0;). - Caught by: Linter.
- Symptom: Powers up as
Priority 2 — Functional & Protocol Bugs
- [Back-to-Back Beacon Burst on Forward TSF Jump — Lines 201–203] Naive
next_tbtt_us <= next_tbtt_us + bi_us:- Symptom: If cross-chip sync jumps
tsf_qforward by(or , spanning beacon intervals), tsf_q >= next_tbtt_usstays1forconsecutive cycles/ticks, emitting back-to-back tbtt_pulseevents! - Caught by: Human reviewer.
- Symptom: If cross-chip sync jumps
- [Pre-TBTT Equality & Unsigned Underflow — Line 194]
assign pre_hit = armed && (tsf_q == next_tbtt_us - pre_tbtt_us);:- Symptom: (a) Exact equality
==missespre_tbtt_pulseif the smooth-drift slew engine steps byacross the target value; (b) if next_tbtt_us < pre_tbtt_usnear, unsigned subtraction underflows to 64'hFFFF...; (c) no one-shot flag preventspre_hitfrom firing again if TSF pauses (+0slew) on that exact microsecond. - Fix: Write
armed && !pre_fired_q && (tsf_q + 64'(pre_tbtt_us) >= next_tbtt_us)and setpre_fired_q <= 1'b1until the nexttbtt_hit. - Caught by: Human reviewer.
- Symptom: (a) Exact equality
- [Pulse Gating Drop — Line 217]
tbtt_pulse <= tbtt_hit & tick_rise;whilenext_tbtt_usupdates on everyclkcycle (Line 201):- Symptom:
tbtt_hitis evaluated onclk(160 MHz). As soon astsf_qupdates on the cycle aftertick_rise,tbtt_hitasserts one cycle aftertick_risehas already fallen to0! Then on that same cycle, line 201 incrementsnext_tbtt_us <= next_tbtt_us + bi_us, clearingtbtt_hitbefore the nexttick_rise—sotbtt_pulseis permanently 0! - Caught by: Human reviewer / Simulation.
- Symptom:
- [Broken
tbtt_missedCondition — Line 223]if (tbtt_hit && (tsf_q > next_tbtt_us + 64'd1)):- Symptom: Flags
tbtt_missedon a harmlessslew step instead of checking whether a full beacon interval was skipped ( tsf_q >= next_tbtt_us + bi_us).
- Symptom: Flags
Priority 3 — Coding Style & Race Hazards
- [Blocking Assignment in
always_ff— Line 187]armed = (state_d == ARMED) || (state_d == FIRED);:- Symptom: Uses
=insidealways_ffinstead of<=. - Caught by: Linter.
- Symptom: Uses
GPT Answer
Start with clocks/resets and ownership of each state variable, then widths, state completeness, and event semantics.
Prioritized findings
Silicon/signoff blockers:
tsf_tickis used directly in core-domain combinational edge detection; one sampled bit is not a synchronizer.tsf_usis a separate unsafely sampled 64-bit bus. Transfer an atomic snapshot with an event/sequence indication, not a synchronized tick plus an unqualified bus.next_tbtt_ushas two procedural drivers. Merge disable/config/update priority into one owner.bi_us[BI_W-1:0]loses the ten shifted bits. UseBI_W+10bits and extend before shifting, or{beacon_interval_tu,10'b0}. Reject BI=0.state_dlacks defaults on multiple paths, inferring storage. Default to current state, cover invalid states, and define disable/config transitions.
Functional defects:
armed = ...in sequential logic should be nonblocking or derived combinationally; its behavior races with consumers. State andarmedduplicate information unnecessarily.tsf_qis updated by an NBA on the same edge the outputs evaluate oldtsf_q. Thetick_risequalification can therefore emit stale-event decisions. Drive a single coherent snapshot-processing transaction and account for registered latency.pre_hitrequires exact equality, repeats on a paused timestamp unless latched, and underflows when lead time exceeds the target. Use a per-interval pre-event flag and a guarded threshold/crossing comparison.next = current + BIon configuration is not TSF-aligned. Repeatednext += BIwhile behind causes catch-up activity for every old deadline, and the FIRED state does not suppress thearmedlevel. Do not replay a burst of expired beacon events after a jump.- Disable does not consistently stop/reset the FSM, armed state, and pulses. Specify config-update semantics and pulse priority explicitly.
tbtt_missedhas no reset and unclear set-versus-clear priority. Its thresholdnext+1can overflow, and a hard-coded one-us tolerance is not a complete missed-event definition.- Specify backward-jump, timestamp-wrap, reset, interval-change, and zero/oversized pre-lead policies. A comparison alone does not define scheduling behavior.
Tool split: lint catches widths, latches, drivers, reset omissions, and assignment style. CDC/RDC tools catch unsafe domain/reset structures. Humans/spec models are needed for alignment, jump policy, missed semantics, and exactly-once events; assertions/formal test those choices.
Claude Answer
What I look at first: the clock-domain comment, every async input and where it lands,
every signal with more than one driver, widths on anything with a shift or add, and then
the state machine. Findings in priority order:
Silicon-killers
- CDC on
tsf_tick/tsf_us.tsf_tickis async and goes raw intotick_rise,
which enables a 64-bit capture. Metastabletick_q→ corrupttsf_q. Worse, the timer
block updatestsf_uson that same tick, so the capture samples the bus while it is
changing. Fix: 2-flop sync ontsf_tick, edge-detect on the synced signals, capture
tsf_usa fewclklater when the timer guarantees stability, with a documented
stability window andset_max_delay -datapath_only; or a proper handshake. (CDC tool.) next_tbtt_usis multi-driven by twoalways_ffblocks (async reset and none).
Synthesis error, or last-wins and a reset-less path into a 64-bit register. (Lint.)bi_ustruncation.beacon_interval_tu << 10intoBI_Wbits keeps the low 16
bits: any BI > 63 TU wraps, and 100 TU is the default. NeedsBI_W+10bits. (Lint.)- The pulses never fire.
tsf_qis captured at the edge aftertick_rise;tbtt_hit
becomes true one cycle later, whentick_riseis already 0;next_tbtt_usadvances the
cycle after that.tbtt_pulse <= tbtt_hit & tick_riseis therefore always 0. Register
the hit as a one-shot and drop thetick_risequalifier. (Human; a simple test catches it.)
Functional
5. Catch-up after a TSF jump. After any forward jump the design adds one BI per hit, so
tbtt_hit stays true for consecutive evaluations. And next_tbtt_us <= tsf_q + bi_us on
cfg_wr is not aligned to a multiple of BI, which 802.11 requires (TBTT ≡ TSF mod BI = 0).
6. pre_hit equality compare on a timestamp that double-steps under slew → missed;
next_tbtt_us − pre_tbtt_us underflows while next_tbtt_us is 0 after reset. Use a
window compare with a one-shot.
7. tbtt_missed has no reset (X / random at power-up in a sticky status bit FW reads),
a miss beats a coincident clear so a FW clear can be lost, and the > next + 1
threshold is off at µs granularity (document the tolerance). (Lint for the reset.)
8. No disable path. !en zeroes next_tbtt_us but the FSM stays ARMED, so tbtt_hit
is true every cycle; and any cfg_wr (e.g. a pre_tbtt_us change) re-bases the TBTT.
9. FSM latch. state_d unassigned in IDLE-without-write and ARMED-without-hit, no
default. Add state_d = state; at the top and a default. (Lint.)
Style / standard violations
10. Blocking assignment to armed in always_ff — works, but it is a race with any
reader and a standard violation. (Lint.)
11. Mixed reset styles across blocks; case should be unique case; the FIRED state does
nothing and can go.
Who catches what: lint → 2, 3, 7 (reset), 9, 10. CDC tool → 1. Human (or a protocol/
scenario test) → 4, 5, 6, 7 (priority), 8.
M5Q15 · B1. The firmware team reports that after a cross-chip TSF correction of +3 ms, the AP transmits a burst of several beacons back to back.
Follow-ups once you have your list:
- The firmware team reports that after a cross-chip TSF correction of +3 ms, the AP
transmits a burst of several beacons back to back. Which finding explains it, and what is
the correct behaviour per 802.11 (hint: TBTT is defined asTSF mod BI == 0)?
Gemini Answer
- Why firmware saw a burst of beacons after a +3 ms TSF jump & 802.11 spec behavior:
- Finding #6 (
next_tbtt_us <= next_tbtt_us + bi_usfiring a pulse on every step while catching up). Per IEEE 802.11, TBTTs are strictly periodic at multiples ofBI(); if TSF jumps forward past one or more TBTTs, the missed intermediate TBTTs must be skipped silently (setting tbtt_missed <= 1) andnext_tbtt_usmust advance to the first multiple ofBIstrictly greater thantsf_qwithout pulsingtbtt_pulse.
- Finding #6 (
GPT Answer
- Burst after +3 ms: incrementing an expired target by one interval repeatedly replays old deadlines. Keep TBTTs on the TSF interval grid. After a jump, mark skipped deadlines as missed, emit at most the event permitted by the agreed recovery policy, and re-arm a strictly future aligned deadline. Scheduled TBTT and actual transmitted-beacon time are different: medium access can delay transmission. The number of skipped deadlines depends on the configured interval; +3 ms alone does not imply multiple default-length beacon intervals.
Claude Answer
- Beacon burst after +3 ms correction: finding 5. Once the TSF jumps past one or more
TBTTs,next_tbtt_usonly catches up one BI per hit, so each evaluation is another hit
and (with the pulse logic fixed) another beacon. Correct behaviour: skip the missed TBTTs,
flagtbtt_missedonce, and fire at the next TSF value withtsf mod BI == 0.
M5Q16 · B2. Rewrite the next-TBTT computation so that after an arbitrary TSF jump it lands on the correct next multiple of the beacon interval without a divider.
- Rewrite the next-TBTT computation so that after an arbitrary TSF jump it lands on the
correct next multiple of the beacon interval without a divider.
Gemini Answer
- Divider-less Next-TBTT Catch-Up FSM:
- Note that
, so next_tbtt_us[9:0]is always10'b0. For arbitrary jumps without a 64-bit divider, use a binary-scaled shift-subtract / iterative catch-up FSM inclk(160 MHz): whilenext_tbtt_us <= tsf_q, suppresstbtt_pulse, settbtt_missed <= 1'b1, and add the largest power-of-two multiple(bi_us << k)that keepsnext_tbtt_us <= tsf_q(or addbi_usonce per 6.25 nsclkcycle—catching up a 1-second jump of 10 beacons takes only 10 cycles = 62.5 ns!).
- Note that
GPT Answer
- No-divider realignment: for positive BI, obtain remainder
r=TSF mod BIusing a bounded restoring-modulo FSM, then computenext=TSF+(BI-r); r=0 deliberately advances to the next interval. A 65-bit accumulator prevents intermediate overflow. Pseudocode for each of 64 timestamp bits, high to low:r=(r<<1)|bit; if(r>=BI) r-=BI. This uses a comparator/subtractor, not a combinational divider. With BI in TUs, instead reduceTSF[63:10] mod beacon_interval_tu; reconstruct the next TU-aligned multiple. A full-width overflow/epoch policy is still required. Capture a coherent timestamp, handle newer snapshots while busy, and verify the result remains future before arming; rerun/advance if time has overtaken it. A bit mask works only for power-of-two BI.
Claude Answer
- Catch-up without a divider.
bi_usis a multiple of 1024, so align with a
shift-and-add long division in a small FSM: startnext = 0, then fork = 53down to
0, ifnext + (bi_us << k) <= tsf_qadd it (54 cycles worst case, once per jump or
config write); finish withnext += bi_ussonext > tsf_q. Holdtbtt_hitoff while
the FSM is busy. For small jumps the same FSM degenerates to a few single-BI adds.
M5Q17 · B3. Which of your fixes change the number of flops? Does that matter for anything downstream?
- Which of your fixes change the number of flops? Does that matter for anything downstream?
Gemini Answer
- Do these fixes change the flop count & downstream impact?
- Yes: adding the 3-flop
tsf_ticksynchronizer,pre_fired_qone-shot flop, and wideningbi_usif registered adds ~5 flops. - Downstream impact: Pre-freeze RTL: negligible area delta, automatic in synthesis/LEC/scan. Post-freeze ECO: adding flops requires spare
SDFFcells, scan-chain stitching, and manual key-point mapping in Conformal LEC.
- Yes: adding the 3-flop
GPT Answer
- Flop changes: synchronizers, snapshot/handshake state, widened BI, event flags, and modulo-FSM state add flops; removing redundant state may subtract some. Rerun synthesis, sequential equivalence as appropriate, STA/CDC/RDC, scan insertion/ATPG and release checks. A post-scan or metal-only context changes what is feasible.
Claude Answer
- Flop changes: +2 synchronizer flops, a 64-bit delayed capture if the stability
window needs it, the catch-up FSM (a 6-bit shift index and a state bit), a one-shot for
the pulse; the width fix, the reset ontbtt_missedand the multi-driver removal don't
add flops. Pre-freeze it only shifts the area number. As a post-freeze ECO, every new flop
has to be stitched into a scan chain and mapped in LEC, so I'd prefer to fix it in RTL.
Part C — Failing-test debug scenario (0:50–0:55)
A nightly regression of 300 tests has one test that fails ~1 in 8 seeds with a scoreboard
mismatch: the expected PPDU length is off by exactly one OFDM symbol, only for LDPC + MU
cases. The RTL has not changed in two weeks; the testbench had a "refactor only" commit
yesterday.
M5Q18 · C1. Describe, in order, the first five things you do.
Describe, in order, the first five things you do. For each: what question it answers, and
what you do if the answer is yes/no.
Gemini Answer
Scenario: 1 in 8 seeds fails with PPDU length off by 1 OFDM symbol only for LDPC + MU. RTL unchanged for 2 weeks; TB had a "refactor only" commit yesterday.
- Step 1: Run the exact failing seed on the pre-refactor TB commit (
git checkout HEAD~1).- Question answered: Did the yesterday TB commit introduce the failure, or just shift randomization order?
- If it passes on
HEAD~1with identical stimulus: The TB refactor broke the reference predictor. If stimulus changed: Force the failing(MCS, RU, PSDU_LEN)vector as a directed test on both commits.
- Step 2:
git diffthe TB refactor commit specifically around the LDPC encoding-parameter predictor and DPI/class variables.- Question answered: Did the refactor leave a local variable uninitialized across transactions (explaining the 1-in-8 seed history dependence!) or alter integer division/rounding in
N_avbits/N_pld? - If yes: Fix the uninitialized variable / rounding in the predictor. If no: Inspect how MU per-user loop ordering changed.
- Question answered: Did the refactor leave a local variable uninitialized across transactions (explaining the 1-in-8 seed history dependence!) or alter integer division/rounding in
- Step 3: Print all intermediate IEEE 802.11 LDPC variables (
N_SYM_init,N_avbits,N_pld,L_LDPC,N_cw,N_shrt,N_punc) from both the RTL and the TB scoreboard for the failing transaction.- Question answered: Which exact step diverges first, and which one matches the IEEE 802.11 hand-calculated equation?
- If RTL matches IEEE spec: Bug is 100% in the TB reference model. If TB matches IEEE spec: Latent RTL corner-case bug exposed by new random trajectory.
- Step 4: Check for state leakage from the previous PPDU in the failing seed.
- Question answered: Why does it fail on only 1 in 8 seeds for a deterministic math block?
- Action: Run the failing transaction as the very first PPDU after reset vs. immediately after the preceding PPDU of the failing seed to catch stale per-user running-max or LDPC registers in RTL or TB.
- Step 5: Add a permanent directed regression test + SVA for that exact LDPC boundary condition and re-run 100 seeds.
- Question answered: Is the root cause completely eliminated and locked down against regression?
GPT Answer
- Reproduce and freeze artifacts: rerun the failing seed with exact RTL/TB/tool/config revisions and logs. Reproducible? Reduce the trace; otherwise preserve all randomness and investigate scheduling/uninitialized state before changing logic.
- A/B the TB refactor: run old/new TB against the same RTL and stimulus. Only new fails? Diff copying, transaction lifetime, rounding, and sampling. Both fail? Investigate an old bug or stimulus/environment change too. A refactor correlation is evidence, not proof.
- Trace transaction identity/order: compare accepted PPDU/user IDs, input values, reset/flush epochs, and output sampling. Misaligned? Fix that first. Aligned? Compare arithmetic intermediates.
- Check LDPC/MU boundaries independently: calculate initial symbols, STBC rounding, LDPC extra-symbol condition, and common maximum using the specification. Model wrong? Repair model and regression-check it. RTL wrong? Produce a minimal boundary case and RTL fix.
- Minimize and close: reduce users/payload/rate combinations while preserving failure; inspect shared object handles, missing clone/copy, stale per-user arrays, DPI widths, and fork races. Add a targeted boundary test/checker, then rerun relevant matrix and seed regressions.
Claude Answer
- Re-run the failing seed on the pre-refactor testbench commit, same RTL. Question:
testbench regression or latent RTL bug? Pass → the refactor changed expected values or
stimulus; fail → latent RTL bug exposed by a new seed (also check whether that seed ever
ran before the refactor). - Diff the refactor for anything near LDPC/MU expected-value computation, DPI-C calls,
randomisation order or$urandomconsumption ("refactor only" that reorders randomize
calls changes stimulus). Hit → bisect within it; nothing → step 3. - Dump the intermediates for the failing PPDU on both sides:
N_pld,N_avbits,
N_cw,L_LDPC,N_shrt,N_puncand the extra-symbol decision. Hand-compute the spec
result for that vector. RTL wrong → file a real bug; model wrong → fix the model. - Characterise the flakiness. Same seed, same result every time? Yes → a specific
parameter corner (anN_avbitsthreshold, a particular user RU); no → non-determinism:
uninitialised memory in the C model, a static variable across DPI calls, a race between
the scoreboard and the monitor on MU user ordering. 1-in-8 with no RTL change smells
like the latter. - Pin it with an assertion on the exact intermediate, run 50+ seeds on both commits to
confirm cause and fix, then add the vector as a directed test and a coverage bin at the
LDPC threshold so it can't come back silently.
Part D — Your questions (0:55–1:00)
M5Q19 · D1. Questions for the interviewer
Gemini Answer
- "What does your merge-request code review checklist look like—are lint and CDC reports automatically attached to the MR before human review starts?"
- "Of the silicon or late-emulation bugs you've seen in the MAC, what class of bug slipped past both lint and block-level DV most often?"
- "How big is a typical RTL merge request here, and how many reviewers sign off?"
GPT Answer
Which defects block review immediately? Who defines timer jump/missed-event semantics? How are TB-only refactors validated against existing regressions?
Claude Answer
- What is the review SLA, the typical MR size, and is there a required checklist?
- What fraction of escapes to FPGA/silicon were caught by lint vs. review vs. DV, and
which class dominates? - Does the team want tooling contributions (lint rules, checkers) from RTL engineers, or
is that a separate methodology group?