The Big-Tech Silicon Interview Master Vault
Forty curated practice problems modeling the core digital design and verification principles evaluated across leading semiconductor engineering teams - twenty-five on design, fifteen on verification - followed by three real silicon failure case studies. Attempt each one before opening the answer. Every solution links back to the volume that derives it.
11.1 Twenty-five digital design problems
Gate-level & combinational
1 Build a 4-input AND gate using only 2:1 multiplexers.
A 2:1 MUX computes Y = S ? B : A. Tie A = 0 and it becomes
Y = S & B - an AND gate. So each AND costs one MUX with its low input
grounded.
For four inputs build a tree: two MUXes produce a&b and
c&d, and a third ANDs those. Three MUXes, two levels deep.
Volunteer the follow-up: "A chain also works with three MUXes but is three levels deep. The tree is the same area and one level faster." → Volume 2.1
2 Implement XOR using only 2-input NAND gates. Minimum count?
Four NANDs.
n2 = NAND(a, n1)
n3 = NAND(b, n1)
y = NAND(n2, n3) = a ⊕ b
Expanding, n2 = (a·b')' and n3 = (a'·b)', so the final NAND
gives a·b' + a'·b - XOR.
Follow-up: "Why does anyone care about NAND-only?" NAND is functionally complete and, in static CMOS, cheaper than AND or OR - an AND is physically a NAND followed by an inverter. Standard cell libraries are NAND/NOR heavy for that reason. → Volume 2.1
3 Build a 2:1 MUX from NAND gates only.
Start from Y = (A·~S) + (B·S) and apply De Morgan:
With ~S = NAND(S, S) that is four NAND gates: one inverter,
two product terms, one combiner.
→ Volume 2.1
4 Detect the leading '1' in an 8-bit value, MSB to LSB.
A priority encoder. casez expresses it literally:
always @(*) begin
valid = |din;
casez (din)
8'b1???????: pos = 3'd7;
8'b01??????: pos = 3'd6;
8'b001?????: pos = 3'd5;
8'b0001????: pos = 3'd4;
8'b00001???: pos = 3'd3;
8'b000001??: pos = 3'd2;
8'b0000001?: pos = 3'd1;
8'b00000001: pos = 3'd0;
default: pos = 3'd0;
endcase
end
Say the trade-off: "Priority is real logic - it costs depth, because each
bit must know no higher bit was set. For a wide input I would build it as a tree of
'any bit set in this half' terms rather than a linear chain." Also mention
valid: without it, position 0 is ambiguous with "no bits set".
→ Volume 2.1
5 Count the 1s in a 32-bit vector with minimum delay.
A balanced adder tree, not serial accumulation. Pair the 32 bits into 16 two-bit sums, then 8 three-bit, 4 four-bit, 2 five-bit, 1 six-bit - 5 levels, not 32. Widths grow only as needed, so early levels are nearly free.
Pre-empt the follow-up: "At 2 GHz I would pipeline it - five levels makes a natural two- or three-stage pipeline." → Volume 2.5
6 Detect whether a number is a power of two.
Subtracting one from a power of two clears the single set bit and sets everything below
it, so the AND is zero. The n != 0 term is required - zero would otherwise
pass.
The same expression checks that a one-hot FSM state has not been corrupted. → Volume 4.5
7 Detect signed overflow in an adder.
Equivalently: adding two numbers of the same sign must produce that sign. If it does not, the result wrapped. Opposite-signed operands can never overflow.
The distinction being tested: "C_out alone signals
unsigned overflow. The adder hardware is identical for signed and unsigned -
signedness exists only in how you interpret the result, which is why an ALU produces both
flags and lets the instruction choose."
→ Volume 2.3
8 Turn an adder into a subtractor without a second adder.
wire [W-1:0] b_in = b ^ {W{sub}}; // XOR row: inverts when sub = 1
assign {cout, result} = a + b_in + sub; // the spare carry-in supplies the +1
Cost: W XOR gates. Volunteer this: "For subtraction cout is
a not-borrow flag - it is 1 when A ≥ B unsigned, which is how unsigned less-than
comes out for free."
→ Volume 2.2
9 Why is a carry lookahead adder faster, and what does it cost?
A ripple carry adder's carry is a serial dependency - bit i
waits for bit i-1, so delay is O(N). CLA computes G = A·B and
P = A ⊕ B for every bit simultaneously (they depend only on
the inputs), then derives each carry as a flat sum-of-products. Delay becomes O(log N).
The cost, raised unprompted: "A flat 64-bit CLA would need a 64-input AND
gate and p[0] driving 64 loads - both physically impossible. Real adders
build it in 4-bit blocks with group generate and propagate, then a second level of
lookahead across the groups."
→ Volume 2.2
10 Design a parameterized barrel shifter.
Decompose the shift amount into its binary digits and build one stage per
digit: stage i shifts by 2i or passes through, controlled
by shamt[i].
log₂(N) stages of N 2:1 MUXes, and the delay does not depend on how far you shift. Replace the fill bits with the bits falling off the bottom and the same structure becomes a rotator. → Volume 2.3
Sequential & clocking
11 Design an edge detector - rising, falling, and both.
Register the signal once and compare it with itself:
reg sig_d;
always @(posedge clk or negedge rst_n)
if (!rst_n) sig_d <= 1'b0;
else sig_d <= sig_in;
assign rise = sig_in & ~sig_d; // 0 -> 1
assign fall = ~sig_in & sig_d; // 1 -> 0
assign both = sig_in ^ sig_d; // any change
Two follow-ups to pre-empt: "If sig_in is asynchronous it
must pass through a 2-FF synchronizer first, or the flop can go metastable. And these
outputs are combinational, so they glitch - if the consumer is in another domain, register
them."
→ Volume 5.2
12 Generate a 25 MHz clock with 50% duty from 100 MHz.
Divide by 4 - an even divisor, so two cascaded toggle flip-flops give an exact 50% duty cycle by construction. No counter decode needed.
Open with this instead and you will stand out: "Before dividing I would ask whether a real 25 MHz clock is needed or just slower logic. A derived clock creates a second domain with its own skew and constraints. If the logic just needs to run four times slower, a clock enable on the original 100 MHz clock is strictly better - one domain, no CDC, trivial timing." → Volume 3.5
13 Now divide by 3 with an exact 50% duty cycle.
Odd division needs the output high for 1.5 input periods, and you cannot get half a period from rising edges alone - so use the falling edge too.
Run two modulo-3 counters, one on posedge and one on negedge,
decode each to a wave high for 2 of 3 states, and AND them. The negedge
copy is offset by half a period, so the AND is high for 2T - 0.5T = 1.5T out of 3T -
exactly 50%.
Say this unprompted and you have shown you have built one: "That final AND is a combinational gate on a clock, so it can glitch. In an ASIC flow it must be a proper clock-gating cell from the library, not an ordinary AND2." → Volume 3.5
14 Design an asynchronous reset synchronizer. Why does recovery time matter?
reg [1:0] sync;
always @(posedge clk or negedge arst_n)
if (!arst_n) sync <= 2'b00; // ASSERT: async, immediate
else sync <= {sync[0], 1'b1}; // DE-ASSERT: shifts in on clock
assign rst_n = sync[1];
Recovery is the minimum time reset must be de-asserted before the next clock edge - the setup-time equivalent for the reset pin. Violate it and the flop can go metastable on reset release.
The systemic reason it matters: "Reset reaches tens of thousands of flops through different wire delays. If it releases near a clock edge, some flops leave reset a cycle before others - half the design starts on cycle N and half on N+1. Asserting asynchronously is safe; the release is the dangerous half. And you need one synchronizer per clock domain." → Volume 3.2
15 How does an LFSR guarantee maximal length? What if the state is all zeros?
Maximal length comes from tap positions corresponding to a primitive
polynomial over GF(2) for that width. The sequence then visits
2ⁿ - 1 states before repeating.
All zeros is a dead state. The XOR of any number of zeros is zero, so the
register shifts in a zero forever. That is precisely why the period is
2ⁿ - 1 and not 2ⁿ.
The variant they probe with: "With XNOR feedback the dead state is all ones instead, for the same reason. FPGAs often prefer XNOR because registers power up to zero, so the LFSR self-starts without an explicit non-zero seed." → Volume 3.4
16 Design a switch debouncer.
Three stages, in this order:
- Synchronize - the switch is asynchronous, so two flops first.
- Filter - require the synchronized level to be stable for N counts (typically 10-20 ms worth) before accepting it.
- Edge-detect - produce a one-cycle pulse from the accepted level.
The point of the question is whether you remember step 1. A debouncer that filters an unsynchronized input still has a metastable flop at its front door. → Volume 5.2
17 Why use a Gray counter instead of a binary one?
A binary counter can change several bits at once - 0111 → 1000 flips four.
Another clock domain sampling mid-transition can read a value that never existed. A Gray
counter changes exactly one bit per step, so a mistimed sample returns
either the old value or the new one.
Two caveats worth volunteering: "Keep the binary counter alongside - you need binary for arithmetic and Gray only for crossing. And single-bit stepping only holds when the count wraps at a power of two." → Volume 3.3
18 Convert Gray code back to binary.
An XOR prefix chain - each binary bit is the XOR of all Gray bits from the MSB down:
bin[i] = gray[i] ⊕ bin[i+1]
The asymmetry is the interesting part: "Binary→Gray is one XOR per bit in parallel. Gray→binary is a serial XOR chain, O(N) deep unless built as a prefix tree. That is why an async FIFO Gray-codes on the way out and never converts back on the far side." → Volume 6.3
19 Design a round-robin arbiter.
A fixed-priority arbiter always grants the lowest-numbered requester, so a busy low channel starves the others. Round-robin rotates the priority base: after granting channel k, priority starts at k+1 next time.
The standard implementation is two priority encoders - one masked to requests above the pointer, one unmasked - taking the masked result if any, otherwise the unmasked one (which wraps around).
Volunteer the property being tested: "The guarantee is bounded wait - no requester waits more than N-1 grants. That is what makes it fair, and why round-robin appears in every shared-bus and NoC design." → Volume 4.4
20 Design an overlapping 1011 sequence detector.
The method matters more than the answer. Name each state after the
longest prefix of 1011 the stream currently ends with: - , 1, 10,
101, 1011. Then for each state and input bit, append it and ask what the longest prefix is
now. Transitions fall out mechanically and overlap handling is automatic.
From 1011 on a 0 you go to state 10, because the
stream now ends …10110 whose longest useful suffix is 10.
Moore needs 5 states and asserts a cycle later; Mealy needs 4 and asserts in the same cycle, at the cost of a combinational input-to-output path. → Volume 4.1
Timing, CDC & memory
21 Compute setup and hold slack for a datapath with skew.
Worked: T = 5 ns, skew = 0.10, tsu = 0.15, tunc = 0.05, tcq = 0.25, tcomb = 3.80 → setup slack = +0.85 ns, Tmin = 4.15 ns → fmax ≈ 241 MHz.
Point at what is missing: "T does not appear in the hold
equation. A hold violation therefore fails at every frequency including DC, and positive
skew helps setup while hurting hold."
→ Volume 7.2
22 Crossing 200 MHz → 100 MHz - can data be lost?
Yes. Two separate problems, and candidates usually name only the first.
- Metastability - solved by a two-flop synchronizer, which gives the first flop a full destination period to resolve.
- Missed data - not solved by a synchronizer. The destination samples half as often, so a one-cycle pulse in the fast domain (5 ns) can vanish entirely between two 10 ns destination edges. A level must be stable for at least 1.5 destination clock periods to be caught reliably.
The fix depends on the traffic: toggle synchronizer for single pulses, handshake for occasional multi-bit transfers, asynchronous FIFO for sustained throughput. → Volume 5.2
23 Why can't you put a 2-FF synchronizer on each bit of a bus?
Because each bit resolves its metastability independently. Bits that left the source on the same edge can arrive on different edges, so the destination latches a mixture of old and new - a value that was never on the bus.
Concretely: a counter crossing 0111 → 1000 where bit 3 resolves a cycle early
is sampled as 1111. Every individual synchronizer worked
perfectly; the system is still broken.
The unifying answer: "Gray code, a held-stable bus with a synchronized flag, a handshake, or an async FIFO - they all work by reducing the crossing to a single bit." → Volume 5.3
24 Design a synchronous FIFO and derive its full and empty conditions.
Both full and empty put the pointers at the same address, so the address bits alone cannot distinguish them. Make each pointer one bit wider than the address - that bit counts laps.
full = (wptr[AW] != rptr[AW]) && (wptr[AW-1:0] == rptr[AW-1:0])
count = wptr - rptr
count works because two's complement subtraction wraps correctly. Also
qualify the enables - do_wr = wr_en && !full - so a write when full is
a harmless no-op rather than silent corruption.
→ Volume 6.2
25 Size a FIFO for a 120-item burst: writer 80 MHz, reader 50 MHz.
Burst duration = 120 / 80 MHz = 1.5 µs. Items read in that time = 50 MHz × 1.5 µs = 75. Depth = 120 - 75 = 45 → round up to 64 for a Gray-pointer FIFO.
Use effective rates: if the writer only writes on one clock in two, its effective rate is 40 MHz, not 80. Round the read count down - assuming the reader kept up better than it did would under-size the FIFO.
The trap version: "What if the writer never stops?" Then no finite depth works - occupancy grows without bound. FIFO depth only solves bursts. Sustained overload needs back-pressure, or a defined drop policy if the source cannot be stalled. → Volume 6.4
11.2 Fifteen SystemVerilog verification problems
1 Difference between logic and bit?
logic is 4-state (0, 1, X, Z); bit is
2-state (0, 1). bit simulates roughly twice as fast and uses
half the memory.
The answer that matters: "Use logic in RTL and on the DUT
boundary, because X propagation is a free bug detector - an unreset register shows XXXX
and fails the test. A 2-state variable silently reads 0, which often matches what the test
expected, so a genuine missing-reset bug passes simulation and shows up in the lab. Use
2-state for testbench internals like loop counters where X is meaningless."
→ Volume 8.1
2 Difference between new() and new[]?
new() is a class constructor - it allocates an object and
returns a handle. new[n] sizes a dynamic array.
Transaction t = new(); // construct one object
int q[]; q = new[8]; // allocate 8 array elements
q = new[16](q); // resize to 16, PRESERVING the old contents
The third form is the one candidates miss: new[16](q) copies the existing
elements across. Without the argument, resizing discards everything.
3 join_any versus join_none?
join_any blocks the parent until the first child finishes.
join_none does not block at all - the parent continues immediately and the
children run when it next blocks.
Two things to add. After join_any you almost always want
disable fork to kill the losers, or a stale watchdog fires on a later
transaction. And with join_none inside a loop, declare
automatic int idx = i; in the loop body - otherwise every spawned thread sees
i's final value.
→ Volume 9.1
4 What happens if a base-class method is not declared virtual?
The call resolves from the type of the handle, not the object. A
Base handle pointing at a Derived object calls
Base's implementation and the override is ignored.
Lead with the failure mode: "The dangerous part is that nothing errors. Your derived driver is constructed, connected, and never called - and the test reports a pass while exercising base behaviour. That is why UVM marks nearly everything virtual." → Volume 9.2
5 Shallow copy versus deep copy of a class handle?
t2 = t1 copies the handle - both point at the same object,
and modifying one changes "both". A shallow copy duplicates the object's fields but any
nested handles still point at the originals. A deep copy clones the nested objects
too.
The bug this causes: "Pushing the same handle into a mailbox twice and
then randomising it gives you two references to one randomised object. Every UVM sequence
item has copy() and clone() for exactly this reason."
→ Volume 9.2
6 How does randc work, and what happens when permutations are exhausted?
randc is random-cyclic: it visits every value in its range in
a random permutation before repeating any. A 4-bit randc produces all 16
values in random order, then reshuffles and starts a new permutation.
The practical limits: "It applies to integral types and, in practice,
modest widths - the solver tracks the whole permutation, so a 32-bit randc is
not viable. And the cyclic property applies to that variable alone; it says nothing about
combinations with other rand variables."
→ Volume 9.4
7 Write a constraint for 10 unique random numbers between 1 and 100.
class Gen;
rand int unsigned vals[10];
constraint c_range { foreach (vals[i]) vals[i] inside {[1:100]}; }
constraint c_unique { unique { vals }; } // SystemVerilog-2012
endclass
Without unique support, express it pairwise - but say why you would avoid it:
constraint c_uniq_pairs {
foreach (vals[i])
foreach (vals[j])
if (i < j) vals[i] != vals[j];
}
It is O(n²) constraints and the solver slows sharply as the array grows. → Volume 9.4
8 What does solve … before actually do?
It changes the probability distribution, never which solutions are legal. The solver picks the named variable first, then the rest consistently with it.
Classic case: rand bit a; rand bit [7:0] b; constraint { a -> b == 0; }.
There are 257 legal (a,b) pairs and only one has a=1, so a
uniform solver gives P(a=1) = 1/257 ≈ 0.4%. Add
solve a before b and it becomes 50%.
Why it matters: "A constraint that reads like a coin flip can leave a scenario essentially untested. Cross coverage is what exposes it." → Volume 9.4
9 How do you know randomize() failed?
It returns 0. It does not throw, and an unchecked call leaves the object holding its previous values while the test carries on regardless.
if (!pkt.randomize()) $fatal(1, "packet randomization failed");
// Inline constraints are ANDed with the class constraints:
if (!pkt.randomize() with { length > 1000; inject_err == 1; })
$fatal(1, "over-constrained");
Failure almost always means over-constrained - two constraints with no
overlapping solution. Debug with constraint_mode(0) to disable them one at a
time.
10 |-> versus |=> in SVA?
Overlapping (|->): if the antecedent matches at cycle N,
the consequent is checked at cycle N.
Non-overlapping (|=>): checked at cycle N+1.
Why getting it backwards is worse than no assertion: it produces a check that passes for the wrong reason, so you believe you have coverage you do not have. → Volume 9.5
11 Can you tape out with 100% code coverage and 50% functional coverage?
You can, and you would probably ship a bug.
Code coverage is collected automatically and measures which lines, branches, conditions and states were executed. Functional coverage is written by hand from the verification plan and measures which scenarios occurred.
The example that lands: "Executing every line of a FIFO proves nothing about whether you ever hit full-and-write-simultaneously. Code coverage tells you what you failed to execute; only functional coverage tells you what you failed to try." Then point at the Pentium FDIV case below - that is exactly this failure, and it cost half a billion dollars. → Volume 9.5
12 Mailbox, semaphore or event - which and when?
- Mailbox - pass objects between threads with buffering. Generator→driver, monitor→scoreboard.
- Semaphore - guard a shared resource so only N users touch it. Two drivers sharing one bus.
- Event - signal that something happened, no data attached. "Reset done."
Add: "Always parameterise a mailbox - mailbox #(Transaction).
A bare mailbox accepts any type and turns a type error into a runtime
surprise."
→ Volume 9.3
13 @(event) versus wait(event.triggered)?
@ is edge-sensitive: if the trigger fires before you reach
the @ - even in the same timestep - you wait forever.
wait(e.triggered) stays true for the rest of the timestep, so it cannot miss.
Same class of race as the stratified event queue ordering;
wait(...triggered) is the safe default.
→ Volume 9.3
14 What is a virtual interface and why is it needed?
An interface is a static construct, elaborated as part of the design hierarchy. Classes are dynamic, created at runtime, and cannot contain a static interface instance. A virtual interface is a handle to an interface, which a class can hold.
It is the bridge between the class-based testbench and the signal-level DUT: the environment receives it through configuration, and the driver and monitor use it to drive and sample real wires.
Pair it with clocking blocks: the driver and monitor both touch the same
signals on the same edge, and the clocking block's input #1step is what stops
them racing the DUT.
→ Volume 8.4
15 Name the UVM phases. Which one consumes simulation time?
Nine: build, connect, end_of_elaboration,
start_of_simulation, run, extract,
check, report, final.
Only run_phase consumes time. Every other phase completes in zero simulation time.
The detail that shows you have used it: "build_phase runs
top-down because a parent constructs its children; connect_phase runs
bottom-up because the ports it wires must already exist. And
run_phase has twelve sub-phases - reset, configure, main, shutdown and their
pre/post variants - for coordinating stimulus across components."
→ Volume 9.5
11.3 Real-world silicon bug case studies
Three failures worth knowing by name. The first two are documented public incidents; the third is a bug class rather than a single event, included because it is the one you are most likely to cause yourself.
Case 1 - The Intel Pentium FDIV bug (1994)
| Detail | |
|---|---|
| What broke | The floating-point divider used an SRT algorithm driven by a lookup table of quotient-digit predictions. Five entries were missing. |
| Symptom | A small set of divisions returned results wrong from around the 4th significant digit. |
| Cost | A recall and a roughly $475 million charge - plus lasting reputational damage from the initial "most users will never notice" response. |
| Root cause | The logic was correct. The data it consulted was not. |
This is the concrete answer to problem 11 above. Intel taped out with the logic verified and the data unverified, and it cost them half a billion dollars.
Case 2 - Intel "Cougar Point" chipset (2011)
| Detail | |
|---|---|
| What broke | A transistor in the PLL clocking circuit for the 3 Gbps SATA ports had a higher-than-intended voltage applied. |
| Symptom | Nothing at first. The circuit degraded over time, so SATA ports 2-5 could fail after months or years in the field. |
| Cost | Disclosed January 2011; roughly $700 million to repair and replace, plus a further revenue impact. |
| Root cause | A circuit-level design issue, not a logic error. |
It is a useful counterweight to this whole course: Volumes 01-09 are about getting the logic right, and that is necessary but not sufficient. Reliability, electromigration, IR drop and aging analysis are a separate discipline - a chip can be logically perfect and still fail in the field.
Case 3 - The blocking-assignment pipeline race
Unlike the first two this is a bug class rather than a single documented incident - included because it is the one you are most likely to write yourself, and it has shipped in real designs many times.
// Two stages of a DSP pipeline,
// written by two engineers.
always @(posedge clk)
stage1 = mult_out;
always @(posedge clk)
stage2 = stage1;
// Non-blocking: both RHS values are
// sampled before either LHS updates.
always @(posedge clk)
stage1 <= mult_out;
always @(posedge clk)
stage2 <= stage1;
always block runs first. Depending on the
order, stage2 receives either the old stage1 (correct - two
pipeline stages) or the new one (wrong - the stage collapses).
The reason it escapes: one simulator picks an order and sticks to it. The design passes regression for months. Then a tool version changes, or the design is re-synthesized, or a different simulator is used for sign-off - and the answer changes. The RTL never changed, so nobody suspects it.
This is Cummings' Golden Rule 1, and it is why "use
<= in every clocked
block" is not style advice.
→ Volume 1.4
The Academy continues with FPGA Mastery and the ASIC physical design flow - both syllabi are published, and volumes ship in order.