Index | 02 Timing, Pipelining and Microarchitecture →
Round 01 — Combined answers (Gemini · GPT · Claude)
Contents
- 0. Resume probe
- Part A — Rapid-fire Q and A (0:05–0:20, ~1 min each)
- M1Q2 · A1. Blocking vs. non-blocking: give one concrete example where…
- M1Q3 · A2. Name three differences between always_comb and always @().
- M1Q4 · A3. What does logic give you over reg/wire? Name one situation…
- M1Q5 · A4. Write (speak) the smallest always_comb block that infers a…
- M1Q6 · A5. unique case vs. priority case vs. plain case: what does…
- M1Q7 · A6. logic signed 7:0 a; logic 7:0 b; logic 8:0 y; assign y = a +…
- M1Q8 · A7. logic 7:0 a, b; logic 7:0 p; assign p = a b; — what width is…
- M1Q9 · A8. == vs. ===: which is synthesizable and why does the other…
- M1Q10 · A9. casez vs. casex: why is casex banned in most coding…
- M1Q11 · A10. When do you use a generate loop vs. a parameterized module…
- M1Q12 · A11. Packed vs. unpacked arrays: which one does synthesis map…
- M1Q13 · A12. When is a reset-less flop acceptable in an ASIC? What do…
- M1Q14 · A13. function vs. task in synthesizable RTL. What does…
- M1Q15 · A14. Interfaces with modports for IP delivery: give one real…
- M1Q16 · A15. Name four classes of bugs a linter catches that a passing…
- Part B — Live coding (0:20–0:55)
- M1Q17 · B1. Valid/ready pipeline stage with a skid buffer
- M1Q18 · B1.1 How many data registers does your design have? Can it be…
- M1Q19 · B1.2 Where is the critical path? What breaks if out_ready fans…
- M1Q20 · B1.3 Sketch the three SVA properties you would put on this…
- M1Q21 · B2. N-way round-robin arbiter feeding the skid buffer
- M1Q22 · B2.1 Describe the classic "double-width priority encoder" trick…
- M1Q23 · B2.2 A new requirement arrives: requester 0 must get at least…
- M1Q24 · B2.3 What functional coverage bins would you want to see hit…
- Part C — Your questions (0:55–1:00)
- Additional notes
0. Resume probe
M1Q1 · "Pick the gnarliest SystemVerilog construct you had to support in your parser and tell me why it was hard."
Gemini Answer
- Answer: The hardest construct in a recursive-descent parser without a full elaboration/type-table pass is disambiguating parenthesized expressions, type casts, and assignment patterns—specifically constructs like
foo'(a + b)vs.'{default: '0}vs.(a)after a macro expansion. - In SystemVerilog (IEEE 1800-2017), an identifier
Tfollowed by'can be a user-defined type castT'(expr)or, ifTis a macro or width parameter, a sized/cast expression, while'{starts an assignment pattern and{starts a concatenation. Furthermore, insidecasezitems,?is a valid literal character (4'b10?z), whereas outside it is the ternary operator. - How I solved it in
lucisv: I implemented a contextual lexer with bounded token lookahead and a speculative backtracking checkpoint in the recursive-descent expression parser, paired with a token-preservation invariant check (verifying that the non-trivia token stream before and after formatting is bit-for-bit identical) so the formatter can never silently alter RTL semantics.
GPT Answer
"I built lucisv in Rust: a lexer, recursive-descent parser, comment-preserving AST, formatter, 60+ lint rules, and a language server. Formatting is guarded by token-preservation and idempotence checks. It achieved 99.3% file-level parse success on an 820-file corpus and is in daily use on production RTL. A useful technical discussion is distinguishing declarations from expressions and preserving comments through transformations. I would use an actual implementation example when asked which construct was hardest." The resume does not identify that hardest construct.
Claude Answer
Template answer (adapt to what actually bit you):
"The preprocessor. Macros with arguments and `ifdef blocks don't respect the grammar:
a macro can expand to half a port list, and an `ifdef can split a case item in two.
A formatter that must be token-preserving and idempotent can't just expand macros and
parse the result, because the output has to be written back with the macros still in it.
I ended up parsing the unexpanded token stream, attaching macro invocations and
conditional directives as trivia on the AST nodes they sit inside, and refusing to reformat
any region where a directive cuts across a syntax node. The idempotence check
(format twice, compare) and the token-preservation check (non-trivia tokens before/after
must match exactly) are what let me ship that safely on production RTL."
Runner-up if asked for a second: the ' ambiguity — T'(x) cast vs '{...} assignment
pattern vs '0 fill literal — needs lookahead because T could be a type or a parameter.
Part A — Rapid-fire Q and A (0:05–0:20, ~1 min each)
M1Q2 · A1. Blocking vs. non-blocking: give one concrete example where using = inside an always_ff produces a simulation result that differs from the synthesized netlist.
Gemini Answer
- Example:
always_ff @(posedge clk) begin a = d; end always_ff @(posedge clk) begin q <= a; end - Why sim != synth: In simulation, both
always_ffblocks trigger onposedge clkin the Active region in arbitrary simulator order. If the first block runs first,ais updated immediately via=, and the second block samples the newdin the same clock edge (acting like 1 flop stage instead of 2). In synthesis, both infer edge-triggered D-flops clocked byclk, soqalways gets the olda(2 flop stages).
GPT Answer
Blocking/nonblocking. always_ff @(posedge clk) a = b; plus a separate always_ff @(posedge clk) c <= a; races: simulation can give c old or new a, depending on process order. Hardware flops sample the old value if timing is met. Use nonblocking assignments for state. In contrast, a=b; c=a; within one process can synthesize the same collapsed logic that it simulates; that alone is not a guaranteed sim/synthesis mismatch.
Claude Answer
Two blocks, one uses blocking:
always_ff @(posedge clk) a = d; // blocking
always_ff @(posedge clk) q <= a; // reads a
Sim: the two processes run in arbitrary order in the same timestep. If the first runs first,
q sees the new d (one flop stage); otherwise the old a (two stages). The netlist is
always two flops. Blocking inside always_ff turns process ordering into functionality.
M1Q3 · A2. Name three differences between always_comb and always @(*).
Gemini Answer
- Function sensitivity:
always_combautomatically sensitizes to signals read inside functions called from the block;always @(*)only sensitizes to the function arguments. - Time-0 evaluation:
always_combis guaranteed to execute once at time 0 after allinitialandalwaysblocks have started, avoiding time-0 X-latches/races. - Single-driver enforcement:
always_combmakes it a compile/elaboration error for any other process to write to a variable assigned inside thealways_combblock, and disallows timing controls (#,@,wait) inside it.
GPT Answer
always_comb. Runs once at time zero, includes reads inside called functions in its implicit sensitivity, and restricts other processes from writing its assigned variables. It also disallows blocking timing controls. Read-before-write ordering still matters because written expressions are excluded from sensitivity.
Claude Answer
always_comb: (1) sensitivity includes signals read inside called functions,
@(*) only sees the arguments; (2) executes once at time 0 so outputs aren't X until the
first input change; (3) variables assigned in it may not be assigned anywhere else and no
timing controls are allowed — tools error, @(*) silently allows both.
M1Q4 · A3. What does logic give you over reg/wire? Name one situation where you cannot use logic and must use wire.
Gemini Answer
- What
logicgives you: A single 4-state data type usable for both procedural assignments (always_comb/always_ff) and continuous assignments (assign/ module output ports), eliminating the arbitrary Verilog-2001regvs.wiresplit while catching unintended multi-driver bugs at compile time. - Where you MUST use
wire: Multiply-driven nets with resolution functions, such as bidirectionalinoutpads or tri-state buses.
GPT Answer
logic. A four-state variable type usable for procedural state or a single continuous/module driver. Resolved multiple-driver or bidirectional connections need a net, e.g. wire logic, rather than a plain variable. logic itself does not mean flop.
Claude Answer
logic is one 4-state type for both procedural and continuous assignment, and it
is single-driver so a second driver is a compile error. You must use wire for anything
with multiple drivers or resolution: tri-state buses, inout pads, pull-ups.
M1Q5 · A4. Write (speak) the smallest always_comb block that infers a latch. Then fix it two different ways.
Gemini Answer
- Latch-inferring snippet:
always_comb begin if (en) y = a; end - Fix 1 (Default assignment at top of block):
always_comb begin y = '0; if (en) y = a; end - Fix 2 (Complete
if/elseor ternary):always_comb begin if (en) y = a; else y = '0; end
GPT Answer
Latch. always_comb if (sel) y = a; leaves y unchanged otherwise. Fix with else y=b;, or assign y=b; before the if. Tools should diagnose unintended storage.
Claude Answer
always_comb if (en) y = a; infers a latch. Fix 1: default at the top
(y = '0; before the if). Fix 2: complete the branch (else y = '0; or y = en ? a : '0;).
M1Q6 · A5. unique case vs. priority case vs. plain case: what does synthesis do differently, and what does simulation do differently, for each?
Gemini Answer
unique case:- Synthesis: Asserts both
parallel_case(items are mutually exclusive → flat OR/AND mux instead of priority chain) andfull_case(if nodefault, unreachable values are treated as don't-cares). - Simulation: Emits a run-time warning/violation if 0 items match (and no
default) or if >1 items match simultaneously.
- Synthesis: Asserts both
priority case:- Synthesis: Preserves priority encoding when items overlap, but asserts
full_caseif nodefaultis present. - Simulation: Emits a run-time warning if 0 items match (no
default).
- Synthesis: Preserves priority encoding when items overlap, but asserts
- Plain
case:- Synthesis: Builds a priority chain if items can overlap and infers a latch (in combinational logic) if not all input values are covered and no
defaultis given. - Simulation: Quietly holds previous value if no item matches.
- Synthesis: Builds a priority chain if items can overlap and infers a latch (in combinational logic) if not all input values are covered and no
GPT Answer
Case qualifiers. unique promises at most one match and, without default, a match; priority promises a match while retaining first-match priority. Simulation reports violations, normally warnings. Synthesis can exploit those promises. Plain case makes neither promise, but mutually exclusive items can still optimize to parallel logic. A default removes the no-match warning, not the overlap check for unique.
Claude Answer
- Plain
case: synth builds a priority chain if items could overlap; missing default +
missing values = latch in comb logic. Sim: no match → nothing happens. unique: synth assumes items are mutually exclusive (parallel mux, no priority) and full
(missing values are don't-care). Sim: runtime violation on no match or >1 match.priority: synth keeps the priority order but assumes full. Sim: violation on no match only.
The danger is the "full" assumption: it removes the latch and any safety default.
M1Q7 · A6. logic signed [7:0] a; logic [7:0] b; logic [8:0] y; assign y = a + b; Is the addition signed or unsigned? What gets extended, and to how many bits?
Gemini Answer
- Signed or unsigned? Unsigned. In SystemVerilog, if any operand in an expression is unsigned (
bandy), the entire operation is evaluated as unsigned. - Extension: Both
aandbare zero-extended to the context width of 9 bits ($bits(y)) before addition, soa's sign bit is treated asinstead of .
GPT Answer
Mixed addition. Unsigned, evaluated at the 9-bit assignment context. Both operands zero-extend to 9 bits; a loses signed interpretation. If signed addition was intended, explicitly sign/zero-extend each input and cast both to signed.
Claude Answer
Unsigned. One unsigned operand makes the whole expression unsigned; the LHS only
sets width. a and b are zero-extended to 9 bits (context width from y), so a's MSB
is +128 instead of −128. Fix: y = a + $signed({1'b0, b}) or declare b signed.
M1Q8 · A7. logic [7:0] a, b; logic [7:0] p; assign p = a * b; — what width is the multiplier actually evaluated at, and what does the netlist contain?
Gemini Answer
- Evaluated width: 8 bits (
max($bits(a), $bits(b), $bits(p)) = 8). - Netlist content: An
lower-half multiplier (the partial products that only contribute to bits [15:8]are optimized away by synthesis). If you wanted the upper bits or a full 16-bit product,p(or one operand) must be at least 16 bits wide.
GPT Answer
Multiply width. Eight-bit expression context; only the low eight product bits are observable. Synthesis can prune the upper product logic. Assign into a 16-bit destination to retain the full unsigned product.
Claude Answer
Context width is max(8,8,8) = 8, so an 8×8 multiplier of which only the low 8
product bits survive; synthesis prunes the partial products feeding bits [15:8]. Declare
p 16 bits if you want the full product.
M1Q9 · A8. == vs. ===: which is synthesizable and why does the other one matter for X-propagation bugs that escape to silicon?
Gemini Answer
- Synthesizable:
==(logical equality).===(case equality) comparesXandZliterally and is simulation-only. - Why it matters for X-prop: If
selis1'bx,if (sel == 1'b1)evaluates to1'bx, which standard Verilogiftreats as false, deterministically executing theelsebranch in simulation. In silicon,selis either0or1, so half the time the hardware takes thethenbranch—hiding reset/uninitialized-flop bugs behind X-optimism.
GPT Answer
Equality. == can return X; === always returns 0/1 and compares X/Z literally in simulation. === is not universally nonsynthesizable: tools can synthesize binary comparisons, but hardware cannot detect a simulator's X state. Use case inequality in scoreboards and explicit unknown checks in assertions. An X condition in an if can hide a real control ambiguity.
Claude Answer
== synthesizes; === is sim-only (compares X/Z literally). With x unknown,
if (x == 1'b1) evaluates to X, which if treats as false, so sim deterministically takes
else. Silicon has a real 0/1 and may take the then branch. Uninitialized flops hide
behind this X-optimism; === in checkers or an X-prop sim mode catches it.
M1Q10 · A9. casez vs. casex: why is casex banned in most coding standards?
Gemini Answer
casextreatsXandZas don't-cares in both the case items AND the case selector expression. If the selector signal isXin simulation (due to an uninitialized register or bus contention),casexmatches the first branch instead of going todefault: x, completely masking X-propagation bugs during RTL simulation.casezonly wildcardsZ/?.
GPT Answer
Wildcards. casex treats X/Z in either the selector or item as wildcards; casez treats Z/? in either as wildcards. Thus even casez can mask a floating selector bit. Prefer explicit masks or carefully controlled case inside patterns.
Claude Answer
casex treats X/Z in the selector as wildcards too, so an X on the design
signal matches the first item instead of propagating. casez only wildcards z/? in
the items. Banned because it hides X bugs and creates sim/synth mismatch.
M1Q11 · A10. When do you use a generate loop vs. a parameterized module array instance (sub u_sub[N-1:0] (...))? How does each affect hierarchical names for backend scripts?
Gemini Answer
- When to use each: Use an instance array (
u_sub[N-1:0]) when instantiatingidentical copies with vector slicing on ports (compact, fast elaboration). Use generate forwhen per-index parameters differ, when conditionalif (i == 0)wiring is needed, or when intermediate per-slice signals/logic are required. - Hierarchical names: Generate loops create scopes named
<gen_block_name>[i].u_sub(or tool-generatedgenblk1[i].u_subif unnamed—always label generate blocks so SDC/LEC/floorplan scripts don't break!). Instance arrays createu_sub[i].
GPT Answer
Replication. Generate supports conditional structure and per-instance parameters; instance arrays suit uniform replication. Name generated scopes explicitly and agree on elaborated hierarchy with backend; both constructs create indexed paths that scripts must handle.
Claude Answer
generate for when iterations differ (per-index parameters, if (i==0) edge
wiring, intermediate per-slice signals). Array of instances for pure replication with port
slicing. Generate adds a scope level: gen_lane[i].u_sub; leave it unnamed and you get
tool-specific genblk1[i], which breaks SDC/UPF/ECO scripts. Instance arrays give
u_sub[i] with no extra scope. Always name generate blocks.
M1Q12 · A11. Packed vs. unpacked arrays: which one does synthesis map to a memory macro, and what coding pattern makes a tool infer a RAM vs. flops?
Gemini Answer
- Which maps to SRAM/LUTRAM: An unpacked array of packed vectors:
logic [DATA_W-1:0] mem [DEPTH];. - RAM vs. Flops pattern: To infer a synchronous RAM macro/BRAM, the read address or read data must be registered synchronously (
always_ff @(posedge clk) rdata <= mem[raddr];) with no asynchronous reset on the memory array and at most 1–2 ports. Addingif (!rst_n) mem <= '0;, reading asynchronously, or writing multiple arbitrary addresses in one cycle forces synthesis to build discrete flip-flops and huge mux trees.
GPT Answer
Arrays/RAM. An unpacked array of packed words is a common memory declaration. Inference depends on supported read/write ports, synchronous/asynchronous read behavior, and reset style, not just the declaration. Resetting every word often prevents RAM inference; asynchronous-read memories exist where supported. ASIC SRAMs commonly require explicit macro wrappers.
Claude Answer
Unpacked array of packed words: logic [W-1:0] mem [DEPTH];. RAM inference wants
a single synchronous write, a synchronous (registered address or registered data) read,
and no reset on the array. Async read, a reset, or more write ports than the library
macro has turns it into flops plus a mux tree.
M1Q13 · A12. When is a reset-less flop acceptable in an ASIC? What do you need to prove to sign it off?
Gemini Answer
- When acceptable: Pure datapath pipeline registers whose contents are always qualified by a reset-controlled valid bit (
valid_q). - What you must prove for sign-off:
- X-propagation / Formal: No X from an un-reset datapath flop can ever reach a control flop, FSM, clock enable, memory write-enable, or primary output when
valid_q == 0(and oncevalid_q == 1, the datapath flop has been overwritten with clean data). - DFT / ATPG: The flop is on a scan chain (scan shift initializes it cleanly) and X-bounding covers any non-scan shadow path.
- X-propagation / Formal: No X from an un-reset datapath flop can ever reach a control flop, FSM, clock enable, memory write-enable, or primary output when
GPT Answer
Resetless data. Acceptable when reset valid/control state prevents observation until every used bit is initialized. Prove that invariant through reset, flush, clock gating, and power transitions; check X propagation, DFT, and security requirements.
Claude Answer
Datapath registers whose contents are always qualified by a reset-controlled valid
bit. Sign-off: show no X from them can reach a control flop, FSM, write enable, or output
while valid is low — X-prop simulation, formal, or GLS without X-init. Also confirm they are
on scan chains so ATPG initializes them. Payoff: smaller reset tree and no reset recovery
timing on the datapath.
M1Q14 · A13. function vs. task in synthesizable RTL. What does automatic change, and why does it matter inside a loop?
Gemini Answer
functionvs.task: Functions execute in zero simulation time, cannot contain@/#/wait, and return a value (orvoid), making them synthesizable into combinational logic. Tasks can consume time and are used in testbenches (though zero-time tasks with output arguments can synthesize, functions are preferred in RTL).automatic: Allocates local variables dynamically per invocation rather than statically shared across the module. Inside a loop or when called from multiplealways_combblocks, a static function shares local temporary variables across calls in simulation, causing race conditions.
GPT Answer
Function/task. Functions consume no simulation time; tasks may consume time, but synthesizable task bodies cannot contain arbitrary timing behavior. automatic gives each invocation separate locals. It helps concurrent/reentrant calls; a sequential loop alone does not require it. Static loop bounds, definite assignments, and supported constructs determine synthesis.
Claude Answer
function: zero time, returns a value, synthesizable. task: may consume time,
testbench territory (zero-time tasks synthesize in some tools but don't rely on it).
automatic gives each call its own locals. A static function's locals are shared, so two
calls active in the same timestep (unrolled loop iterations, two processes) can clobber
each other in sim; recursion also needs it.
M1Q15 · A14. Interfaces with modports for IP delivery: give one real pro and one real con you have seen (think SoC integration, LEC, and backend scripts).
Gemini Answer
- Real Pro: Eliminates 500-line port-connection boilerplate on wide buses (AXI, MAC-to-PHY channels); adding one signal to the interface updates every hop across 5 hierarchy levels automatically.
- Real Con: Synthesis, LEC (Conformal), and backend STA scripts flatten interface ports into
<if_inst_name>_<sig_name>or<if_inst_name>.<sig_name>depending on tool flags, which complicates top-level netlist port matching, UPF port lists, and hierarchical ECO scripts.
GPT Answer
Interfaces. Pro: one shared signal/type definition reduces wiring errors. Con: parameterization and flattening can complicate LEC, hierarchy-based scripts, and tool compatibility. I would validate the delivery flow before adopting them; the resume does not document a specific interface-related incident.
Claude Answer
Pro: one definition of the MAC-to-baseband bus, adding a signal touches one file
instead of every hierarchy hop. Con: at the IP boundary the SoC integrator, LEC and backend
see flattened names (u_bb_if_tx_valid vs u_bb_if.tx_valid depending on tool), so port
matching, UPF and ECO scripts break. We deliver flat ports at the IP top for that reason.
M1Q16 · A15. Name four classes of bugs a linter catches that a passing simulation would not.
Gemini Answer
- Implicit width truncation / extension (e.g., assigning a 10-bit sum to an 8-bit wire where tests only used small numbers).
- Inferred latches in
always_combon error branches never hit by existing tests. - Simulation-synthesis mismatch constructs (
casex, blocking=inalways_ff, incomplete sensitivity lists in legacyalways @(...)). - Structural/clocking hazards (asynchronous reset pin driven by combinational logic or used as synchronous data, undriven/multi-driven nets).
GPT Answer
Lint. Width/sign truncation, incomplete combinational assignments, multiple drivers, and suspicious/unreachable case items. A simulation can pass simply because its stimulus never exercises the affected behavior.
Claude Answer
(1) Width mismatch/truncation that tests never exercise. (2) Latch inference on
branches not covered. (3) Sim/synth mismatch constructs: casex, blocking in always_ff,
incomplete sensitivity lists. (4) Structural: multi-driven or undriven nets, combinational
loops, async reset used as data, clock used as data, missing default, signed/unsigned
mixing. All are "green in sim, wrong in the netlist" classes.
Part B — Live coding (0:20–0:55)
M1Q17 · B1. Valid/ready pipeline stage with a skid buffer
Write a synthesizable, parameterized module:
module skid_buf #(parameter int W = 32) ( input logic clk, rst_n, input logic in_valid, output logic in_ready, input logic [W-1:0] in_data, output logic out_valid, input logic out_ready, output logic [W-1:0] out_data );Requirements:
in_readymust be a registered output (no combinational path fromout_readyto
in_ready). This is the point of a skid buffer.- Full throughput: one transfer per cycle in steady state when the sink is always ready.
- No data loss, no duplication, under arbitrary
out_readybehaviour.- Standard valid/ready rules:
in_validmay not depend onin_ready; once asserted,
in_validandin_dataare held until accepted.
Gemini Answer
module skid_buf #(
parameter int W = 32
) (
input logic clk,
input logic rst_n,
input logic in_valid,
output logic in_ready,
input logic [W-1:0] in_data,
output logic out_valid,
input logic out_ready,
output logic [W-1:0] out_data
);
// Two storage slots: main output register (out_data/out_valid)
// and overflow skid register (skid_data_q/skid_valid_q).
logic [W-1:0] skid_data_q;
logic skid_valid_q;
wire push = in_valid && in_ready;
wire pop = out_valid && out_ready;
// in_ready is registered: we can accept whenever the skid slot is empty.
assign in_ready = ~skid_valid_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out_valid <= 1'b0;
skid_valid_q <= 1'b0;
end else begin
// Main output slot occupancy:
if (skid_valid_q) begin
// If skid has data, out_valid stays 1 even after a pop
out_valid <= 1'b1;
end else if (push) begin
out_valid <= 1'b1;
end else if (pop) begin
out_valid <= 1'b0;
end
// Skid slot occupancy: fills when we push into an already-full output that isn't popping
if (skid_valid_q && pop) begin
skid_valid_q <= 1'b0;
end else if (push && out_valid && !out_ready) begin
skid_valid_q <= 1'b1;
end
end
end
// Datapath registers (reset-less for area/power; gated by enables)
always_ff @(posedge clk) begin
// Load skid register only when stalling with an active push
if (push && out_valid && !out_ready) begin
skid_data_q <= in_data;
end
// Load main output register from skid on pop (if skid valid), else from input
if (skid_valid_q && pop) begin
out_data <= skid_data_q;
end else if (push && (!out_valid || out_ready)) begin
out_data <= in_data;
end
end
endmodule
GPT Answer
A two-entry FIFO is a simple skid-buffer implementation. in_ready is explicitly registered from next occupancy. It has one-cycle output latency and sustains one transfer per cycle with an always-ready sink. After completely filling, it deliberately waits one cycle after a dequeue before accepting again.
module skid_buf #(parameter int W = 32) (
input logic clk, rst_n,
input logic in_valid,
output logic in_ready,
input logic [W-1:0] in_data,
output logic out_valid,
input logic out_ready,
output logic [W-1:0] out_data
);
logic [W-1:0] data_q [2];
logic rd_q, wr_q;
logic [1:0] count_q, count_d;
logic push, pop;
assign out_valid = (count_q != 2'd0);
assign out_data = data_q[rd_q];
assign push = in_valid && in_ready;
assign pop = out_valid && out_ready;
always_comb begin
count_d = count_q;
case ({push, pop})
2'b10: count_d = count_q + 2'd1;
2'b01: count_d = count_q - 2'd1;
default: ;
endcase
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rd_q <= 1'b0;
wr_q <= 1'b0;
count_q <= 2'd0;
in_ready <= 1'b1;
end else begin
count_q <= count_d;
in_ready <= (count_d < 2'd2);
if (push) wr_q <= ~wr_q;
if (pop) rd_q <= ~rd_q;
end
end
// Data need not reset: count_q qualifies all observations.
always_ff @(posedge clk)
if (rst_n && push) data_q[wr_q] <= in_data;
endmodule
Claude Answer
module skid_buf #(parameter int W = 32) (
input logic clk, rst_n,
input logic in_valid,
output logic in_ready,
input logic [W-1:0] in_data,
output logic out_valid,
input logic out_ready,
output logic [W-1:0] out_data
);
logic skid_valid;
logic [W-1:0] skid_data;
logic out_load, in_fire;
assign in_ready = ~skid_valid; // registered: pure flop output
assign out_load = ~out_valid | out_ready; // output register can advance
assign in_fire = in_valid & in_ready;
// control (reset)
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out_valid <= 1'b0;
skid_valid <= 1'b0;
end else begin
if (out_load)
out_valid <= skid_valid | in_valid; // skid first, else input
if (in_fire && !out_load)
skid_valid <= 1'b1; // accepted but output stalled
else if (out_load)
skid_valid <= 1'b0; // drained into output register
end
end
// datapath (no reset)
always_ff @(posedge clk) begin
if (out_load)
out_data <= skid_valid ? skid_data : in_data;
if (in_fire && !out_load)
skid_data <= in_data;
end
endmodule
in_valid can only reach out_valid when skid_valid is 0, and in_ready is 1 exactly
then, so the "skid first, else input" line is correct without a mux on valid.
M1Q18 · B1.1 How many data registers does your design have? Can it be done with one? Why not?
- How many data registers does your design have? Can it be done with one? Why not?
Gemini Answer
- How many data registers? Can it be done with one?
- Two
W-bit registers (out_dataandskid_data_q). It cannot be done with one ifin_readyis registered and full 1-word/cycle throughput is required: whenout_valid == 1andin_ready == 1on cycle, and out_readysuddenly drops to0on cycle, out_datamust hold its unaccepted word whilein_data(accepted becausein_readywas already1) must be captured into a second register (skid_data_q).
- Two
GPT Answer
- Storage: two words. With one registered output word and registered backpressure, an occupied stage may still accept a second word when the sink unexpectedly stalls. That requires the second slot.
Claude Answer
- Registers: two data registers (
out_data,skid_data). One is impossible because
in_readyis a flop: in the cycle the sink stalls I have already promised to accept
this cycle's word, and the output register is still holding the stalled one.
M1Q19 · B1.2 Where is the critical path? What breaks if out_ready fans out to 200 of these?
- Where is the critical path? What breaks if
out_readyfans out to 200 of these?
Gemini Answer
- Where is the critical path? What breaks if
out_readyfans out to 200 of these?- Critical path inside the module is
out_ready → Data/Valid enable mux → out_data.D / skid_valid_q.D. Ifout_readyfans out to 200 parallel skid buffers (or 200 bits × 2 registers = 12,800 flip-flop load pins),out_readysuffers massive capacitive load and wire delay. Fix: tree-bufferout_readyor group the 200 channels into a wider word with replicated control flops (skid_valid_q/out_validduplicated per physical slice).
- Critical path inside the module is
GPT Answer
- Timing: output selection and occupancy/ready next-state logic; exact critical path requires synthesis. A sink-ready net driving 200 instances still has electrical fanout and local timing cost; buffer or partition it. The registered ready boundary only stops combinational propagation upstream.
Claude Answer
- Critical path:
out_ready→out_load→ enables/mux-select ofout_data,out_valid,
skid_valid. With 200 instances,out_readyis a 200×(W+2)-load net that needs a buffer
tree and eats the cycle. The skid protects the upstreamin_ready, not its own
internal enable; fix by registeringout_readyat the sink or adding a full
two-entry FIFO stage soout_readyonly lands on a small amount of logic.
M1Q20 · B1.3 Sketch the three SVA properties you would put on this module.
- Sketch the three SVA properties you would put on this module.
Gemini Answer
- Three SVA properties:
// 1. Output stability: once out_valid asserts without out_ready, out_valid & out_data hold ap_out_hold: assert property (@(posedge clk) disable iff (!rst_n) (out_valid && !out_ready) |=> (out_valid && $stable(out_data))); // 2. No overflow: never push when both slots are already full ap_no_overflow: assert property (@(posedge clk) disable iff (!rst_n) (out_valid && skid_valid_q) |-> !in_ready); // 3. Liveness / full throughput: if skid is empty, in_ready must be high ap_ready_when_space: assert property (@(posedge clk) disable iff (!rst_n) !skid_valid_q |-> in_ready);
GPT Answer
- Three properties: output holds under stall; occupancy stays in
[0,2]and equals accepted minus emitted transfers since reset; a reference queue proves order and data preservation. Also assume the source holds valid/data under stall.
assert property (@(posedge clk) disable iff (!rst_n)
out_valid && !out_ready |=> out_valid && $stable(out_data));
assert property (@(posedge clk) disable iff (!rst_n)
count_q <= 2'd2 && in_ready == (count_q < 2'd2));
// Third check: on push append in_data to a reference queue;
// on pop compare out_data to its oldest entry, then remove it.
Claude Answer
-
SVAs:
- Output hold:
out_valid && !out_ready |=> out_valid && $stable(out_data). - Skid invariant:
skid_valid |-> out_valid(never hold overflow data while the
output is empty — the no-drop/no-dup proof in one line, backed by a scoreboard). - Input hold (assume):
in_valid && !in_ready |=> in_valid && $stable(in_data).
Plus:
!in_ready |-> $past(in_valid && in_ready && out_valid && !out_ready)— ready
only drops for the one legitimate reason. - Output hold:
M1Q21 · B2. N-way round-robin arbiter feeding the skid buffer
module rr_arb #(parameter int N = 4, parameter int W = 32) ( input logic clk, rst_n, input logic [N-1:0] req_valid, output logic [N-1:0] req_ready, input logic [W-1:0] req_data [N], output logic out_valid, input logic out_ready, output logic [W-1:0] out_data, output logic [$clog2(N)-1:0] out_id );Requirements:
- Exactly one grant per cycle when any request is pending and the sink is ready.
- Strict round-robin fairness: after granting requester i, requester i+1 has the highest
priority next.- Fully parameterized in
N(including non-power-of-twoN); nofor-loop that synthesizes
into anN²priority chain if you can avoid it.- Instantiate your
skid_bufon the output.
Gemini Answer
module rr_arb #(
parameter int N = 4,
parameter int W = 32,
parameter int ID_W = (N > 1) ? $clog2(N) : 1
) (
input logic clk,
input logic rst_n,
input logic [N-1:0] req_valid,
output logic [N-1:0] req_ready,
input logic [W-1:0] req_data [N],
output logic out_valid,
input logic out_ready,
output logic [W-1:0] out_data,
output logic [ID_W-1:0] out_id
);
logic sb_in_valid;
logic sb_in_ready;
logic [W-1:0] win_data;
logic [ID_W-1:0] win_id;
// Mask holds 1s for indices >= current highest-priority pointer (thermometer mask)
// After granting i, mask_q has 1s for [N-1 : i+1] (or all 1s if i == N-1)
logic [N-1:0] mask_q, mask_d;
logic [N-1:0] req_masked;
logic [N-1:0] gnt_masked, gnt_unmasked, gnt;
assign req_masked = req_valid & mask_q;
// Isolate lowest set bit: x & (~x + 1)
assign gnt_masked = req_masked & (~req_masked + N'(1));
assign gnt_unmasked = req_valid & (~req_valid + N'(1));
// If any masked request exists, use gnt_masked; otherwise wrap around to gnt_unmasked
assign gnt = (|req_masked) ? gnt_masked : gnt_unmasked;
assign sb_in_valid = |req_valid;
assign req_ready = sb_in_ready ? gnt : '0;
// Next thermometer mask: all bits strictly above the granted bit are 1
// Note: ~( (gnt - 1) | gnt ) sets bits > granted index to 1, and wrap to all-1s if MSB granted
always_comb begin
mask_d = mask_q;
if (sb_in_valid && sb_in_ready) begin
if (gnt[N-1]) mask_d = {N{1'b1}};
else mask_d = ~((gnt - N'(1)) | gnt);
end
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) mask_q <= {N{1'b1}};
else mask_q <= mask_d;
end
// One-hot mux for data and binary ID
always_comb begin
win_data = '0;
win_id = '0;
for (int i = 0; i < N; i++) begin
if (gnt[i]) begin
win_data |= req_data[i];
win_id |= ID_W'(i);
end
end
end
// Instantiate skid buffer for {out_id, out_data}
skid_buf #(
.W(ID_W + W)
) u_skid_buf (
.clk (clk),
.rst_n (rst_n),
.in_valid (sb_in_valid),
.in_ready (sb_in_ready),
.in_data ({win_id, win_data}),
.out_valid (out_valid),
.out_ready (out_ready),
.out_data ({out_id, out_data})
);
endmodule
GPT Answer
Arbitrate admission into the buffer, advance priority only on acceptance, and buffer the ID with the payload. A masked encoder followed by an unmasked fallback avoids an N-by-N search. Lock selection when the buffer stalls so newly arriving requests cannot change a presented transfer.
The original out_id[$clog2(N)-1:0] declaration is unsuitable for N=1; the guarded width below supports all N>=1. W>=1 is assumed.
module rr_arb #(parameter int N = 4, parameter int W = 32,
parameter int IW = (N > 1 ? $clog2(N) : 1)) (
input logic clk, rst_n,
input logic [N-1:0] req_valid,
output logic [N-1:0] req_ready,
input logic [W-1:0] req_data [N],
output logic out_valid,
input logic out_ready,
output logic [W-1:0] out_data,
output logic [IW-1:0] out_id
);
logic [IW-1:0] ptr_q, chosen, lock_id_q;
logic lock_q, found, arb_valid, buf_ready;
logic [W+IW-1:0] buf_in, buf_out;
always_comb begin
chosen = '0;
found = 1'b0;
for (int i = 0; i < N; i++) begin
if (!found && req_valid[i] && (i >= int'(ptr_q))) begin
chosen = IW'(i);
found = 1'b1;
end
end
if (!found) begin
for (int i = 0; i < N; i++) begin
if (!found && req_valid[i]) begin
chosen = IW'(i);
found = 1'b1;
end
end
end
if (lock_q) chosen = lock_id_q;
arb_valid = lock_q ? req_valid[lock_id_q] : found;
req_ready = '0;
if (arb_valid) req_ready[chosen] = buf_ready;
buf_in = {chosen, req_data[chosen]};
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
ptr_q <= '0;
lock_q <= 1'b0;
lock_id_q <= '0;
end else if (arb_valid && buf_ready) begin
ptr_q <= (chosen == IW'(N-1)) ? '0 : chosen + IW'(1);
lock_q <= 1'b0;
end else if (arb_valid && !lock_q) begin
lock_q <= 1'b1;
lock_id_q <= chosen;
end
end
skid_buf #(.W(W+IW)) u_buf (
.clk, .rst_n, .in_valid(arb_valid), .in_ready(buf_ready),
.in_data(buf_in), .out_valid, .out_ready, .out_data(buf_out)
);
assign {out_id, out_data} = buf_out;
endmodule
Claude Answer
module rr_arb #(parameter int N = 4, parameter int W = 32) (
input logic clk, rst_n,
input logic [N-1:0] req_valid,
output logic [N-1:0] req_ready,
input logic [W-1:0] req_data [N],
output logic out_valid,
input logic out_ready,
output logic [W-1:0] out_data,
output logic [$clog2(N)-1:0] out_id
);
localparam int IW = $clog2(N);
logic [IW-1:0] ptr; // requester with top priority
logic [2*N-1:0] req_dbl, grant_dbl;
logic [N-1:0] req_rot, grant_rot, grant_c, grant_q, grant;
logic lock_q;
// rotate right by ptr (double-width shift avoids modulo), fixed-priority
// pick of the lowest set bit, rotate back left.
assign req_dbl = {req_valid, req_valid} >> ptr;
assign req_rot = req_dbl[N-1:0];
assign grant_rot = req_rot & ~(req_rot - 1'b1); // isolate LSB, O(N) chain
assign grant_dbl = {grant_rot, grant_rot} << ptr;
assign grant_c = grant_dbl[2*N-1:N];
// hold the grant while the stage below is stalled (valid/ready hold rule)
assign grant = lock_q ? grant_q : grant_c;
logic sk_in_valid, sk_in_ready, fire;
logic [W-1:0] sk_in_data;
logic [IW-1:0] sk_in_id;
always_comb begin // one-hot AND-OR mux + encode
sk_in_data = '0;
sk_in_id = '0;
for (int i = 0; i < N; i++) begin
if (grant[i]) begin
sk_in_data = sk_in_data | req_data[i];
sk_in_id = sk_in_id | IW'(i);
end
end
end
assign sk_in_valid = |grant;
assign req_ready = grant & {N{sk_in_ready}};
assign fire = sk_in_valid & sk_in_ready;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
ptr <= '0;
lock_q <= 1'b0;
grant_q <= '0;
end else begin
lock_q <= sk_in_valid & ~sk_in_ready;
grant_q <= grant;
if (fire)
ptr <= (sk_in_id == IW'(N-1)) ? '0 : sk_in_id + 1'b1; // non-pow2 wrap
end
end
logic [W+IW-1:0] sk_out;
skid_buf #(.W(W+IW)) u_skid (
.clk, .rst_n,
.in_valid (sk_in_valid), .in_ready (sk_in_ready),
.in_data ({sk_in_id, sk_in_data}),
.out_valid, .out_ready,
.out_data (sk_out)
);
assign {out_id, out_data} = sk_out;
endmodule
(For N == 1, $clog2(1) = 0 makes the out_id port [-1:0]; guard with
localparam int IW = (N > 1) ? $clog2(N) : 1; and a matching port if N = 1 is real.)
M1Q22 · B2.1 Describe the classic "double-width priority encoder" trick for round-robin and its gate count versus a rotate-encode-rotate implementation.
- Describe the classic "double-width priority encoder" trick for round-robin and its gate
count versus a rotate-encode-rotate implementation.
Gemini Answer
- Thermometer-mask / double-width priority encoder vs. rotate-encode-rotate:
- The two-bank masked priority encoder (
req_maskedin parallel withreq_unmasked, selected by|req_masked) runs two-bit priority encoders in parallel + one 2:1 mux, eliminating the two barrel shifters ( rotate right by ptr+rotate left by ptr) of rotate-encode-rotate. For, the masked two-bank approach has ~40% fewer gates and 2 fewer logic levels, and works for arbitrary non-power-of-two without modulo barrel shifting!
- The two-bank masked priority encoder (
GPT Answer
- Double-width encoder: mask positions below the pointer in
{req,req}, select the first set bit, and fold the result modulo N. Two masked/fallback encoders as above implement the same priority order. Both can use O(N) encoder logic; rotation commonly needs O(N log N) barrel-mux wiring. Procedural priority chains may still be slow; inspect the mapped implementation or use a tree encoder for large N.
Claude Answer
- Double-width trick vs rotate-encode-rotate. Double-width: form
mask = ~((1<<ptr)-1),
run one find-first-set over the 2N-bit vector{req, req & mask}, OR the two halves back.
Cost ≈ a 2N-wide priority chain + an N-bit thermometer decode + N OR gates, about 3–4N
gates, O(log N) depth with a parallel-prefix FFS. Rotate-encode-rotate needs two
N×log₂N barrel shifters (2·N·log₂N mux2) plus an N-bit FFS. Comparable at N = 4;
double-width wins clearly by N = 16–32. The N² version is the nestedfor i / for j
"is any higher-priority request present" loop; avoid it.
M1Q23 · B2.2 A new requirement arrives: requester 0 must get at least 50% of grants when it is always requesting. Smallest change to your design?
- A new requirement arrives: requester 0 must get at least 50% of grants when it is
always requesting. Smallest change to your design?
Gemini Answer
- Requester 0 must get at least 50% of grants when always requesting — smallest change?
- Add a 1-bit toggle flop
prio0_turn_qthat flips on every cycle wherereq_valid[0] && sb_in_ready. Whenprio0_turn_q && req_valid[0]is true, forcegnt = N'(1)(grant requester 0 without advancingmask_q); on the alternate cycles, run the normal round-robin arbiter across[N-1:1](or[N-1:0]).
- Add a 1-bit toggle flop
GPT Answer
- At least 50% for port 0: alternate an entitlement slot for port 0 with an ordinary RR slot; if 0 is idle, use work-conserving RR. Toggle slots only on accepted grants. Giving port 0 two slots among N+1 guarantees only
2/(N+1), not 50% for arbitrary N.
Claude Answer
- Requester 0 gets ≥ 50%. Add a 1-bit
turnflop toggled on everyfire. When
turnis set andreq_valid[0], forcegrant_c = 'b1and don't advanceptr;
otherwise normal RR. Requester 0 gets every other grant when always asking, and the
others stay strictly RR among themselves. Alternative: two slots in an (N+1)-way arbiter.
M1Q24 · B2.3 What functional coverage bins would you want to see hit before trusting this arbiter?
- What functional coverage bins would you want to see hit before trusting this arbiter?
Gemini Answer
- Functional coverage bins before sign-off:
- Every requester
granted alone and under full contention ( req_valid == '1). - Transition bins: requester
granted immediately after requester for all pairs , especially wrap-around for non-power-of-two . - Backpressure cross:
sb_in_ready == 0whilereq_validhas 1, 2, andactive requests (verifying mask_qdoes not advance when stalled).
- Every requester
GPT Answer
- Coverage: every ID, every predecessor/successor pair, pointer wrap, sparse/all-active masks, stalls with newly arriving higher-priority requests, full-buffer recovery, reset, and N=1/3/5. Fairness is bounded in accepted grants, conditional on downstream progress and persistent request validity.
- Handshake nuance: buffered output transfers and new source grants need not occur on the same cycle. The buffer may accept while the final sink stalls, or emit a previously accepted word with no new request.
Claude Answer
- Coverage before trusting it: every requester granted; cross of (previous id × id)
so every i→j transition is seen including the N−1 → 0 wrap; all N requesting under
back-pressure; single requester alone (no bubble); lock engaged then a higher-priority
request arrives and does not steal the grant;out_readylow with pending requests
for > N cycles; fairness assertion that a held request is served within N grants.
Part C — Your questions (0:55–1:00)
M1Q25 · Questions for the interviewer
Gemini Answer
- "Since you own the SystemVerilog coding guidelines, what are the top two or three constructs that cause the most friction in code reviews or lint waivers here?"
- "How much of the coding standard is enforced automatically in pre-merge CI (SpyGlass / Ascent / custom scripts) versus human review?"
- "What is the team's philosophy on SystemVerilog interfaces vs. flattened structs at subsystem boundaries when handing off to synthesis and LEC?"
GPT Answer
- Which lint rules are release gates, and how are waivers reviewed?
- What SystemVerilog constructs are supported consistently across synthesis, LEC, and integration?
- How are small handshake blocks verified: simulation, formal, or both?
Claude Answer
- What fraction of the coding standard is enforced by lint vs. caught in review, and is
the waiver process per-line or per-rule? - Is the group moving to interfaces for IP delivery, and how are the LEC/backend naming
issues handled if so? - Which sim/synth mismatch has escaped furthest here, and what rule came out of it?
Additional notes
Notes. Material not tied to a question
Gemini Answer
No answer in gemini_answer.md.
GPT Answer
These are practice answers. Resume statements below are supported by the supplied resume; proposed implementations and tradeoffs are not claims about the production design. Question numbers follow interview.md.
Technical reference and validation
The code is an interview implementation, not a claimed extract from lucisv or NXP RTL. No HDL simulator is installed in the current environment, so compilation and RTL simulation remain unverified.
Tool-specific equality support: AMD UG901 supported expressions. In particular, the local answer key's blanket claim that === is simulation-only is too strong.
Claude Answer
No answer in claude_answer.md.