← 13 DFT, Scan, MBIST and RTL Signoff | Index
Round 14 — Combined answers (Gemini · GPT · Claude)
Contents
- 0. Resume probe
- Part A — Rapid-fire Q and A (0:05–0:20)
- M14Q2 · A1. A synchronous FIFO has a non-power-of-two depth DEPTH = 10.…
- M14Q3 · A2. What is a "First-Word Fall-Through" (FWFT / show-ahead)…
- M14Q4 · A3. When do you switch from valid/ready handshaking to…
- M14Q5 · A4. In a credit-based link, a single bit flip corrupts the…
- M14Q6 · A5. You are designing a 64-bit to 40-bit gearbox (valid/ready).…
- M14Q7 · A6. How does adding a packet boundary signal (in_last + in_keep…
- M14Q8 · A7. In your UCLA Hardware Neural Network Accelerator, you…
- M14Q9 · A8. What is time-multiplexed folding in a DSP/neural-network…
- M14Q10 · A9. Fixed-point word-length optimization: when accumulating K…
- M14Q11 · A10. Convergent (round-half-to-even) rounding vs.…
- M14Q12 · A11. Pipelining a feedback loop (e.g., an IIR filter or a…
- M14Q13 · A12. Write (speak) the combinational logic for a one-hot to…
- Part B — Live Coding (0:20–0:55)
- Part C — Your questions (0:55–1:00)
- Additional notes
0. Resume probe
M14Q1 · "Tell me about the UCLA ternary-weight MNIST accelerator: how did you represent {-1, 0, +1} weights in hardware, what vanished from the MAC unit, and how did folding affect throughput vs. area?"
Gemini Answer
- Answer:
- Ternary Weight Representation: Instead of 8-bit fixed-point weights, we retrained the network with quantization-aware training to restrict weights to
, encoded in 2 bits ( 2'b00 = 0,2'b01 = +1,2'b10 = -1). - What vanished from the MAC unit: The entire
-bit hardware multiplier (~70 gates + carry tree) vanished! Each "multiply" became a tiny mux + conditional 2's-complement adder/subtractor ( w == +1 ? acc + x : (w == -1 ? acc - x : acc)), implemented by XORingxwith the sign bitw_negand feedingw_neginto the adder'scarry_in, gated to0whenw == 0—shrinking each MAC unit's gate area and dynamic energy by. - Folding + Pipelining + Word-Length Optimization: Rather than instantiating one physical MAC per neuron synapse (fully parallel, huge area, idle between frames), we time-multiplexed (folded) the layer across a compact array of pipelined processing elements, and trimmed the accumulator bit-widths per layer based on worst-case
weight norms—improving the Energy-Area Product ( ) by orders of magnitude and ranking 1st in the class competition.
- Ternary Weight Representation: Instead of 8-bit fixed-point weights, we retrained the network with quantization-aware training to restrict weights to
GPT Answer
"At UCLA I designed, synthesized and timing-closed a Verilog MNIST classifier, retrained to ternary weights, and used pipelining, folding and word-length optimization. It improved the energy-area product by orders of magnitude and ranked first in the class competition. Ternary weights remove general multipliers in favor of select/add/subtract. The resume does not give the exact encoding, widths, clock, baseline or folding factor; I would use the real reports before quoting those numbers."
Claude Answer
Template (put your real numbers in):
"Weights were {−1, 0, +1}, stored packed five trits per byte (3⁵ = 243 fits 8 bits) and
unpacked to a sign bit and a non-zero bit at the MAC input. The multiplier vanished: a MAC
became an adder with a conditional two's-complement invert — XOR the activation with the
sign and feed the sign as carry-in — and the non-zero bit as operand isolation, so a zero
weight adds a quiet zero instead of toggling the adder. That took the MAC from an 8×8
multiplier plus adder to roughly a fifth of the gates and energy. Folding: I time-
multiplexed each layer's MACs by a factor chosen so every layer had the same throughput
(balanced folding), which gave F× fewer MACs at F× lower throughput with a small input
mux and accumulator per physical MAC, and cut weight-memory bandwidth by the same factor.
Word length: Q1.7 activations, exact accumulators with guard bits, saturate-then-round at
layer outputs. The energy-area product improved by [orders of magnitude] over the baseline
and it ranked first in the class."
Part A — Rapid-fire Q and A (0:05–0:20)
M14Q2 · A1. A synchronous FIFO has a non-power-of-two depth DEPTH = 10. Why does the standard $clog2(DEPTH)+1 MSB-wrap-bit trick (wptr[MSB] != rptr[MSB] && wptr[LSB] == rptr[LSB]) fail if you simply wrap wptr[3:0] from 9 → 0, and what are the two cleanest ways to code full/empty for non-power-of-two depths?
Gemini Answer
- Why MSB wrap fails: With
DEPTH = 10, a 4-bit pointer (wptr[3:0]) only visits0..9(10 states out of 16). If you toggle an extra MSBwptr[4]when wrapping9 -> 0, the pointer distance across the wrap boundary isphantom counts, so binary subtraction wptr - rptris wrong and standard MSB-inversion fails if you ever calculate occupancy. - Two cleanest fixes:
- Explicit Occupancy Counter (
count_q): Wrapwptrandrptrexplicitly atDEPTH - 1(ptr_next = (ptr == DEPTH-1) ? '0 : ptr + 1'b1) and maintain an explicitcount_q[$clog2(DEPTH+1)-1:0]that increments onpush && !popand decrements onpop && !push, withempty = (count_q == 0)andfull = (count_q == DEPTH). - Explicit Wrap-Phase Bit (
wrapped_q) or Offset-Pointer Wrap: Maintain a 1-bitwrapped_qtoggled when a pointer wrapsDEPTH-1 -> 0(or set onpushwhenwptr_next == rptrand cleared onpop), withempty = (wptr == rptr) && !wrapped_qandfull = (wptr == rptr) && wrapped_q.
- Explicit Occupancy Counter (
GPT Answer
A natural binary pointer overflows at 16, not 10. Explicitly wrapping its low field without toggling phase loses full/empty history. Use explicit 9-to-0 pointer wrap plus an occupancy count, or explicit wrap-phase toggles with pointer equality. The phase-bit method can work for depth 10 if coded deliberately; the power-of-two arithmetic shortcut is what fails. Guard widths for DEPTH=1.
Claude Answer
With wptr[3:0] wrapping at 9, only 10 of 16 states are used, so the MSB-toggle
trick and any wptr − rptr arithmetic break across the wrap. Cleanest: (a) pointers in
0..DEPTH−1 with (ptr == DEPTH−1) ? 0 : ptr+1 plus an explicit count register
(full = count == DEPTH, empty = count == 0); or (b) a 1-bit wrapped flag set when a
push makes wptr catch rptr and cleared on pop, with full = (wptr == rptr) && wrapped.
M14Q3 · A2. What is a "First-Word Fall-Through" (FWFT / show-ahead) FIFO vs. a standard read-latency-1 FIFO? How do you convert a 1-cycle-read-latency SRAM FIFO into an FWFT FIFO?
Gemini Answer
- Difference: A standard SRAM/sync FIFO outputs
rdataon the clock cycle afterrd_enasserts; an FWFT (show-ahead) FIFO presents the head item ondoutas soon asempty == 0(out_valid = !empty), andrd_en = out_valid && out_readyadvances to the next item. - Converting a 1-cycle-read-latency SRAM FIFO to FWFT: Attach a 2-entry prefetch / skid buffer (from Round 12) to the SRAM output that automatically issues speculative reads (
mem_rd_en = !fifo_empty && prefetch_has_space) as soon as data enters the FIFO, hiding the 1-cycle SRAM read latency completely.
GPT Answer
FWFT presents the oldest available word with valid without a separate read request. A latency-1 FIFO returns a requested word later. Add prefetch/output holding registers, pending-read accounting and spill space to expose an SRAM FIFO as FWFT without losing in-flight data under stall.
Claude Answer
Standard FIFO: data appears the cycle after rd_en. FWFT: the head word is on dout
whenever !empty, so valid = !empty, rd_en = valid && ready matches valid/ready
directly. Convert a 1-cycle SRAM FIFO with the 3-slot read skid from round 12 on the output.
M14Q4 · A3. When do you switch from valid/ready handshaking to credit-based flow control? Give the exact formula for the minimum receiver FIFO depth (CREDITS) needed for 100% throughput when the forward data path has pipeline stages and the credit return path has pipeline stages.
Gemini Answer
- When: Whenever a link crosses a long physical distance (multi-cycle pipeline across chip partitions, NoC hops, or inter-FPGA/die-to-die links) where inserting
Npipeline stages requiresback-to-back 2-register skid buffers ( flip-flops!). Credit-based flow control uses simple 1-flop/stage feed-forward pipelines with no readywire on the link, concentrating all buffering into a single SRAM/FIFO at the receiver. - Exact Minimum Credit / RX Buffer Depth Formula for 100% Throughput:
If and (plus 1 cycle through the RX FIFO read / credit register), cycles MAX_CREDITS = 8.
GPT Answer
Use credits when the backpressure round trip is long and registered ready would need distributed spill storage. Define total credit reuse latency L from send to usable return; then C >= ceil(rate*L) at the target rate. L=Dfwd+Lrx+Dret+Llocal, where receiver dequeue and local return-register/bypass conventions matter. Dfwd and Dret alone do not determine one exact answer.
Claude Answer
When the ready round trip exceeds one cycle across the link (every pipeline stage
would otherwise need its own skid buffer). Sustained 1 word/cycle needs
CREDITS ≥ D_fwd + D_ret + (registers in TX send, RX count and credit pulse). With 4 + 3 and
one register each at TX output, RX occupancy and credit return: 10–11.
M14Q5 · A4. In a credit-based link, a single bit flip corrupts the credit_inc wire for one cycle (a "leaked" or "phantom" credit). What is the symptom of each, and how do production links make credit return self-healing?
Gemini Answer
- Leaked credit (
credit_returnbit flip): TX cred_cntpermanently loses 1 credit; max link bandwidth drops by, and if all credits leak, the link deadlocks. - Phantom credit (
credit_returnbit flip): TX thinks RX has 1 more free slot than actually exists RX FIFO overflows and silently drops data! - Self-Healing Credit Return: Instead of sending stateless 1-bit
credit_incpulses, have the receiver periodically send its absolute modular Read Pointerrx_rptr[K:0](protected by parity/CRC or Gray code), and have TX computecredits_avail = MAX_CREDITS - (tx_wptr - rx_rptr). Any single-cycle corruption is automatically overwritten and healed on the very next pointer update!
GPT Answer
A lost return slowly reduces usable capacity and can halt the link; an extra return can overflow the receiver. Production schemes use integrity protection, sequence/cumulative consumed counters, duplicate rejection and controlled resynchronization. Resynchronize only with known in-flight state/epoch; periodically resetting the counter to DEPTH can manufacture credits.
Claude Answer
Leaked credit (1→0): effective depth shrinks by one forever; enough leaks and the
link stalls. Phantom credit (0→1): TX sends into a full FIFO: overflow and corruption.
Self-healing: return an absolute, protected value (RX read pointer, Gray or CRC-protected)
periodically so TX recomputes avail = MAX − (tx_wptr − rx_rptr) and one bad cycle heals
at the next update.
M14Q6 · A5. You are designing a 64-bit to 40-bit gearbox (valid/ready). Why is and the first thing you should calculate, and what is the minimum size (in bits) of the internal shifting/holding buffer?
Gemini Answer
- Why
: Every transfer is an integer multiple of an 8-bit byte ( ): each 64-bit input is 8 symbols (bytes), and each 40-bit output is 5 symbols (bytes). You never need bit-level shifting—only byte-level muxing! - Why
: Every 5 input words ( bits) produce 8 output words ( bits), after which the gearbox residue phase repeats ( cnt = 0). - Minimum internal storage buffer: Maximum residue carry-over before accepting a new 8-byte input is
4bytes (bits); to register the 5-byte output directly and accept an 8-byte input when residue is 0..4, a 12-byte (96-bit) shift register (or 4-byte residue flop + 5-byte output flop = 72 bits) is sufficient.
GPT Answer
GCD=8 gives a byte-sized quantum; LCM=320 gives five 64-bit inputs to eight 40-bit outputs. A work-conserving shift/append accumulator can use 64+40-8=96 bits under a drain-before-append, no-bypass convention, not a 320-bit frame buffer. Minimum storage depends on bypass/ready/throughput contracts; extra storage may be needed for registered ready.
Claude Answer
gcd = 8 says the natural unit is a byte, so the shifter works in bytes, not bits;
lcm = 320 says one phase cycle is 5 inputs = 8 outputs, which sizes the residue counter
and the coverage sweep. Minimum state: the residue of up to out−1 = 4 bytes (32 bits) if
the output muxes straight from in_data; a registered-handshake design needs
in + out + (out−1) = 17 bytes.
M14Q7 · A6. How does adding a packet boundary signal (in_last + in_keep / valid byte count) change a downsizing or upsizing gearbox when a packet ends on an unaligned word?
Gemini Answer
- When
in_last == 1arrives, the remaining valid bytes in the gearbox buffer may be fewer than a full output word (e.g., only 1 or 2 bytes left in a 32-to-24 gearbox). - The gearbox must flush the partial residue as a final output beat with
out_last = 1andout_keep[2:0]indicating which bytes of that final beat are valid (3'b001for 1 byte,3'b011for 2 bytes,3'b111for 3 bytes), and reset its internal residue byte counter to0so the next packet starts cleanly aligned at byte 0!
GPT Answer
Count valid bytes, preserve packet boundaries, and flush a partial output with out_keep/out_last or an explicitly defined padding rule. Never fill the last output of one packet with bytes from the next. If outputs cannot describe partial words, arbitrary packet tails cannot be represented losslessly.
Claude Answer
On in_last the residue may be shorter than an output word: flush it as a final
beat with out_last = 1 and out_keep marking the valid bytes, then clear the residue
count so the next packet starts aligned. Upsizing also has to emit a padded last beat.
M14Q8 · A7. In your UCLA Hardware Neural Network Accelerator, you retrained weights to ternary . What does a single multiply-accumulate ( ) synthesize into compared to an 8-bit × 8-bit MAC, and how can you avoid switching activity when ?
Gemini Answer
- Hardware structure for
: // w_nz = (w != 0), w_neg = (w == -1) wire [7:0] x_gated = w_nz ? (x ^ {8{w_neg}}) : 8'd0; assign acc_next = acc_q + {{8{x_gated[7]}}, x_gated} + 16'(w_neg); - Why
w_nzgating matters: In trained ternary neural networks, 50–70% of weights are0(sparsity). Gatingxto8'd0with an AND mask whenw == 0stops the adder carry chain from toggling on changing activationsx, cutting adder switching power in half!
GPT Answer
Decode ternary weight into zero/+x/-x and add to the accumulator. Sign-extend before negation so -128 can become +128. Gate accumulator update for zero and isolate inactive operand/add logic where worthwhile; a mux alone does not ensure all upstream toggles disappear.
Claude Answer
An 8×8 multiply-accumulate (~60–80 gates of multiplier) becomes a conditional
invert and an adder: acc + (x ^ {8{sign}}) + sign, with the non-zero bit gating the
operand. ~5–6× smaller and lower energy. For w = 0, force the operand to zero (operand
isolation) or skip the cycle entirely so the adder doesn't toggle.
M14Q9 · A8. What is time-multiplexed folding in a DSP/neural-network datapath? If you fold a 64-MAC parallel layer by a factor of (down to 16 physical MACs), what happens to the multiplier area, the mux/register overhead, the throughput, and the weight memory bandwidth?
Gemini Answer
- Multiplier/Adder Area: Drops by
(64 16 PEs). - Mux & Control Overhead: Adds a 4:1 input activation mux per PE and a 2-bit modulo-4 cycle counter.
- Throughput / Latency: Frame latency increases by
(throughput drops by ). - Weight SRAM Bandwidth: Drops by
per clock cycle (reads 16 weights/cycle instead of 64, allowing a narrower, lower-power 1RW SRAM bank!).
GPT Answer
Folding by four reduces 64 simultaneous operators to 16 and takes four phases per original layer operation at the same clock. Ideal arithmetic area falls roughly fourfold, while mux/control/partial-sum storage grow. Throughput drops about fourfold unless compensated by frequency/other parallelism. Instantaneous weight bandwidth falls with active operators; total weights per inference do not vanish.
Claude Answer
Fold by 4: multiplier/MAC area ÷4, throughput ÷4 (latency ×4), weight-memory
bandwidth per cycle ÷4, plus a 4:1 input mux and an accumulator register per physical MAC
and a small schedule counter. Worth it when the layer isn't the throughput bottleneck.
M14Q10 · A9. Fixed-point word-length optimization: when accumulating products of signed Q1.7 (8-bit) activations with ternary weights, how many guard bits are required in the accumulator to guarantee zero overflow, and where do you saturate vs. truncate at the layer output?
Gemini Answer
- Guard bits required for zero intermediate overflow:
So an 8-bit activation + ternary weight product requires an signed accumulator. - Saturate vs. Truncate/Round: At the end of the 256-cycle accumulation:
- First round the fractional LSBs (
round-half-to-even). - Then saturate the upper guard bits: if the dropped upper bits do not all match the target MSB sign bit (
!(&acc[15:7] || ~|acc[15:7])), clamp the output to+MAX(8'h7F) or-MIN(8'h80)—never let an activation wrap around from positive to negative!
- First round the fractional LSBs (
GPT Answer
Full signed Q1.7 inputs include -128 in integer units. Multiplying by -1 permits +128, requiring a 9-bit signed term. With 256 arbitrary terms, the range includes +32768, requiring a 17-bit signed accumulator: eight accumulation-growth bits beyond the widened term, or nine extra bits relative to the original 8-bit activation. A 16-bit answer requires a tighter range such as excluding -128. Round at the chosen output scale, then saturate before narrowing; do not overflow first and try to repair it later.
Claude Answer
256 products of an 8-bit value times ±1 need log2(256) = 8 guard bits: a 16-bit
accumulator is exact. At the layer output saturate first (detect overflow from the dropped
MSBs not being all-equal to the new sign), then round the dropped LSBs to the next layer's
format; never truncate before the saturate check.
M14Q11 · A10. Convergent (round-half-to-even) rounding vs. round-half-up vs. truncation in DSP/PHY datapaths: why does simple truncation or round-half-up introduce a DC bias over long accumulations, and what is the 1-line bit expression for round-half-to-even?
Gemini Answer
- Why Truncation & Round-Half-Up bias:
- Truncation always rounds toward
, introducing a DC bias. - Round-half-up (
+ 0.5 LSBthen truncate) rounds0.00(no change),0.25(), 0.75(), and 0.50always UP ()—leaving a net positive DC bias of per tap!
- Truncation always rounds toward
- 1-line Round-Half-to-Even (when dropping
fractional bits [F-1:0]):- Round up on the exact midpoint (
val[F-1] == 1 && val[F-2:0] == '0) only when the retained integer LSBval[F]is odd (1):wire round_inc = val[F-1] & (|val[F-2:0] | val[F]); assign rounded = val[W-1:F] + round_inc;
- Round up on the exact midpoint (
GPT Answer
For two's-complement arithmetic right-shift with kept LSB L, first discarded bit G and sticky S over the rest: increment = G && (S || L). Round-half-up biases ties upward; truncation toward negative infinity biases negative. Half-to-even avoids systematic tie-direction bias, though actual bias still depends on the signal distribution. Keep the carry/overflow bit through saturation.
Claude Answer
Truncation always rounds toward −∞ (negative bias); round-half-up pushes every
exact midpoint up (+½ LSB bias); over long accumulations both become DC offsets.
Round-half-to-even when dropping F bits:
q = val[W-1:F] + (val[F-1] & (|val[F-2:0] | val[F])).
M14Q12 · A11. Pipelining a feedback loop (e.g., an IIR filter or a running CRC-32 / scrambler in an 802.11 MAC processing 32 bits per cycle): why can't you just insert a pipeline flop inside the feedback loop to double , and what transformation allows you to speed it up?
Gemini Answer
- Why you can't just add a flop in the loop: Adding a register inside a recursive feedback loop changes
into , which alters the polynomial/transfer function and interleaves two independent streams! - How to speed it up: Use Loop Unrolling / Look-Ahead Transformation: express state
directly as a linear matrix function of and the 32 incoming bits ( ), which reduces a 32-step serial LFSR chain into a 3-to-4-level parallel XOR tree that runs in 1 clock cycle at 640 MHz!
GPT Answer
A feedback recurrence depends on the immediately preceding state; inserting a delay changes that recurrence. Use algebraic lookahead/unrolling, parallel prefix/matrix transforms for CRC/scrambler, or interleave independent contexts. Merely adding flops does not preserve a single-context stream's behavior.
Claude Answer
A flop inside the loop changes the recurrence (z⁻¹ → z⁻²): wrong function.
Speed it up with look-ahead/unrolling: for CRC-32 or a scrambler, unroll 32 single-bit LFSR
steps into one XOR matrix per cycle (the loop stays one register deep); for IIR, clustered
look-ahead or pipelined-interleaved filters.
M14Q13 · A12. Write (speak) the combinational logic for a one-hot to binary encoder of width and explain why it is much faster and smaller than a general priority encoder.
Gemini Answer
// For one-hot input oh[7:0], binary output bin[2:0]:
assign bin[0] = oh[1] | oh[3] | oh[5] | oh[7];
assign bin[1] = oh[2] | oh[3] | oh[6] | oh[7];
assign bin[2] = oh[4] | oh[5] | oh[6] | oh[7];
- Why faster & smaller: Because at most 1 bit of
ohis high, there is no priority chain across bits—each output bit is an independent 4-input OR gate (1 logic level, 3 gates total for)!
GPT Answer
With one-hot input h: b[0]=h[1]|h[3]|h[5]|h[7], b[1]=h[2]|h[3]|h[6]|h[7], b[2]=h[4]|h[5]|h[6]|h[7]. It is just three OR reductions; assert one-hot (or onehot0 plus separate valid). A priority encoder must resolve multiple asserted inputs, which needs extra logic; mapped speed still depends on implementation.
Claude Answer
bin[k] = OR of oh[i] for all i whose bit k is 1: bin[0] = oh[1]|oh[3]|oh[5]|oh[7],
bin[1] = oh[2]|oh[3]|oh[6]|oh[7], bin[2] = oh[4]|oh[5]|oh[6]|oh[7]. One level of
4-input ORs, no priority chain, because one-hot guarantees exactly one term is true.
Part B — Live Coding (0:20–0:55)
M14Q14 · B1. Credit-Based Flow Control Transmitter and Receiver across a Multi-Cycle Pipe (~18 min)
Two blocks sit 4 pipeline stages apart across a chip partition (
FWD_PIPE = 4cycles on
link_valid/link_data,RET_PIPE = 3cycles oncredit_return). There is no backpressure
wire (ready) on the link.Write the synthesizable SystemVerilog for both the TX credit manager (
credit_tx) and RX
credit buffer (credit_rx):module credit_tx #( parameter int W = 32, parameter int MAX_CREDITS = 8 )( input logic clk, rst_n, // Upstream valid/ready input logic up_valid, output logic up_ready, input logic [W-1:0] up_data, // To multi-cycle link pipeline output logic link_valid, output logic [W-1:0] link_data, // From multi-cycle credit return pipeline input logic credit_return ); module credit_rx #( parameter int W = 32, parameter int DEPTH = 8 )( input logic clk, rst_n, // From multi-cycle link pipeline input logic link_valid, input logic [W-1:0] link_data, // To multi-cycle credit return pipeline output logic credit_return, // Downstream valid/ready output logic dn_valid, input logic dn_ready, output logic [W-1:0] dn_data );Requirements:
- What should
MAX_CREDITS/DEPTHbe set to so the link sustains 1 word/cycle when
FWD_PIPE = 4andRET_PIPE = 3?- Handle simultaneous send and
credit_returnatcredit_txwith zero drift.- State clearly when
credit_rxpulsescredit_return(on FIFO pop, not push!).- Write two SVAs: one proving the RX FIFO never overflows, and one proving no credits are
Ever created or destroyed (credits_at_tx + link_in_flight + rx_fifo_count + return_in_flight == MAX_CREDITS).
Gemini Answer
- Required
MAX_CREDITS/DEPTH: WithFWD_PIPE = 4cycles,RET_PIPE = 3cycles, and 1 cycle inside the RX FIFO / credit register, round-trip time. Setting MAX_CREDITS = 8andDEPTH = 8sustains 100% (1 word/cycle) throughput with zero bubbles.
// ============================================================================
// 1. Credit Transmitter (credit_tx)
// ============================================================================
module credit_tx #(
parameter int W = 32,
parameter int MAX_CREDITS = 8,
parameter int CRED_W = $clog2(MAX_CREDITS + 1)
) (
input logic clk,
input logic rst_n,
// Upstream valid/ready
input logic up_valid,
output logic up_ready,
input logic [W-1:0] up_data,
// To multi-cycle link pipeline
output logic link_valid,
output logic [W-1:0] link_data,
// From multi-cycle credit return pipeline
input logic credit_return
);
logic [CRED_W-1:0] cred_cnt_q;
wire has_credit = (cred_cnt_q != '0);
wire send_word = up_valid && has_credit;
assign up_ready = has_credit;
assign link_valid = send_word;
assign link_data = up_data;
// Simultaneous send_word (-1) and credit_return (+1) leaves cred_cnt_q unchanged
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cred_cnt_q <= CRED_W'(MAX_CREDITS);
end else begin
case ({send_word, credit_return})
2'b10: cred_cnt_q <= cred_cnt_q - CRED_W'(1);
2'b01: cred_cnt_q <= cred_cnt_q + CRED_W'(1);
default: cred_cnt_q <= cred_cnt_q; // 2'b00 or 2'b11
endcase
end
end
endmodule
// ============================================================================
// 2. Credit Receiver (credit_rx) with FWFT Synchronous FIFO
// ============================================================================
module credit_rx #(
parameter int W = 32,
parameter int DEPTH = 8,
parameter int PTR_W = $clog2(DEPTH),
parameter int CNT_W = $clog2(DEPTH + 1)
) (
input logic clk,
input logic rst_n,
// From multi-cycle link pipeline
input logic link_valid,
input logic [W-1:0] link_data,
// To multi-cycle credit return pipeline
output logic credit_return,
// Downstream valid/ready
output logic dn_valid,
input logic dn_ready,
output logic [W-1:0] dn_data
);
logic [W-1:0] mem [DEPTH];
logic [PTR_W-1:0] wptr_q, rptr_q;
logic [CNT_W-1:0] count_q;
wire push = link_valid;
wire pop = dn_valid && dn_ready;
assign dn_valid = (count_q != '0);
assign dn_data = mem[rptr_q];
// Return 1 credit whenever a word is popped by downstream!
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) credit_return <= 1'b0;
else credit_return <= pop;
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wptr_q <= '0;
rptr_q <= '0;
count_q <= '0;
end else begin
if (push) wptr_q <= (wptr_q == PTR_W'(DEPTH - 1)) ? '0 : (wptr_q + PTR_W'(1));
if (pop) rptr_q <= (rptr_q == PTR_W'(DEPTH - 1)) ? '0 : (rptr_q + PTR_W'(1));
case ({push, pop})
2'b10: count_q <= count_q + CNT_W'(1);
2'b01: count_q <= count_q - CNT_W'(1);
default: count_q <= count_q;
endcase
end
end
always_ff @(posedge clk) begin
if (push) mem[wptr_q] <= link_data;
end
// SVA 1: RX FIFO never overflows (link_valid never arrives when count_q == DEPTH and !pop)
ap_rx_no_overflow: assert property (@(posedge clk) disable iff (!rst_n)
(count_q == CNT_W'(DEPTH) && !pop) |-> !link_valid);
endmodule
- SVA 2 (End-to-End Credit Conservation Invariant across TX + Pipe + RX):
// Sum of available TX credits + words in forward pipe + words in RX FIFO // + credits in return pipe + credit_return_q register must always equal MAX_CREDITS: ap_credit_conservation: assert property (@(posedge clk) disable iff (!rst_n) (u_tx.cred_cnt_q + $countones(fwd_pipe_valid_q) + u_rx.count_q + u_rx.credit_return + $countones(ret_pipe_credit_q)) == MAX_CREDITS);
GPT Answer
Assume one clock, up to one word/cycle, lossless four-cycle forward and three-cycle return pipelines, coordinated reset of both endpoints and all pipeline-valid/credit bits, and one receiver dequeue cycle before credit return. A returned credit is usable for sending on the same TX sampling edge. Under that convention, C=4+1+3=8 suffices at an always-ready sink. Extra registered dequeue/return control or no same-edge bypass increases L and the required depth. Write the edge timeline before claiming an exact minimum.
module credit_tx #(
parameter int W=32, MAX_CREDITS=8,
parameter int CW=$clog2(MAX_CREDITS+1)
) (
input logic clk, rst_n,
input logic up_valid,
output logic up_ready,
input logic [W-1:0] up_data,
output logic link_valid,
output logic [W-1:0] link_data,
input logic credit_return
);
logic [CW-1:0] credits_q;
logic send;
assign up_ready = rst_n && ((credits_q != 0) || credit_return);
assign send = up_valid && up_ready;
assign link_valid = send; // Link has no ready; each pulse is committed.
assign link_data = up_data;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) credits_q <= CW'(MAX_CREDITS);
else case ({credit_return,send})
2'b10: credits_q <= credits_q + CW'(1);
2'b01: credits_q <= credits_q - CW'(1);
default: ; // simultaneous return/send has zero net change
endcase
end
endmodule
module credit_rx #(
parameter int W=32, DEPTH=8,
parameter int PW=(DEPTH>1 ? $clog2(DEPTH) : 1),
parameter int CW=$clog2(DEPTH+1)
) (
input logic clk, rst_n,
input logic link_valid,
input logic [W-1:0] link_data,
output logic credit_return,
output logic dn_valid,
input logic dn_ready,
output logic [W-1:0] dn_data
);
logic [W-1:0] data_q [DEPTH];
logic [PW-1:0] rd_q, wr_q;
logic [CW-1:0] count_q;
logic pop;
assign dn_valid = rst_n && (count_q != 0);
assign dn_data = data_q[rd_q];
assign pop = dn_valid && dn_ready;
assign credit_return = pop; // Space is returned on removal, never on arrival.
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rd_q <= '0;
wr_q <= '0;
count_q <= '0;
end else begin
if (link_valid)
wr_q <= (wr_q == PW'(DEPTH-1)) ? '0 : wr_q + PW'(1);
if (pop)
rd_q <= (rd_q == PW'(DEPTH-1)) ? '0 : rd_q + PW'(1);
case ({link_valid,pop})
2'b10: count_q <= count_q + CW'(1);
2'b01: count_q <= count_q - CW'(1);
default: ;
endcase
end
end
always_ff @(posedge clk)
if (rst_n && link_valid) data_q[wr_q] <= link_data;
endmodule
Parameters must be positive and MAX_CREDITS=DEPTH; do not advertise more credits than actual receiver slots. The supplied modules are endpoints; the fixed-delay data/valid and credit pipelines must be instantiated externally. Their contents are included in the conservation check.
// RX scope: arrival into a full FIFO is allowed only with simultaneous pop.
assert property (@(posedge clk) disable iff (!rst_n)
link_valid |-> (count_q < DEPTH || pop));
// Top-level checker: each in-flight quantity counts committed tokens at
// the same sampling boundary, using actual pipeline valid/credit bits.
assert property (@(posedge clk) disable iff (!rst_n)
int'(tx_credits) + $countones(fwd_valid_pipe) + int'(rx_count) +
$countones(return_credit_pipe) == MAX_CREDITS);
Also assert credit range, no return without a real pop, and end-to-end data/order via a reference queue. The conservation assertion assumes the interface/pipeline edge convention stated above; include any added endpoint holding register in the token count. Single-endpoint reset while stale words/credits remain in flight is unsafe; use coordinated drain/reset or an epoch protocol.
Claude Answer
module credit_tx #(parameter int W = 32, parameter int MAX_CREDITS = 8) (
input logic clk, rst_n,
input logic up_valid,
output logic up_ready,
input logic [W-1:0] up_data,
output logic link_valid,
output logic [W-1:0] link_data,
input logic credit_return
);
localparam int CW = $clog2(MAX_CREDITS + 1);
logic [CW-1:0] cred_q;
logic send;
assign up_ready = (cred_q != '0); // flop-only cone
assign send = up_valid & up_ready;
// registered launch into the link (counts as one stage of the round trip)
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cred_q <= CW'(MAX_CREDITS);
link_valid <= 1'b0;
end else begin
cred_q <= cred_q - CW'(send) + CW'(credit_return); // same-cycle send+return: net 0
link_valid <= send;
end
end
always_ff @(posedge clk) if (send) link_data <= up_data;
endmodule
module credit_rx #(parameter int W = 32, parameter int DEPTH = 8) (
input logic clk, rst_n,
input logic link_valid,
input logic [W-1:0] link_data,
output logic credit_return,
output logic dn_valid,
input logic dn_ready,
output logic [W-1:0] dn_data
);
localparam int AW = (DEPTH > 1) ? $clog2(DEPTH) : 1;
localparam int CW = $clog2(DEPTH + 1);
logic [W-1:0] mem [DEPTH];
logic [AW-1:0] wp_q, rp_q;
logic [CW-1:0] cnt_q;
logic push, pop;
assign push = link_valid; // never full: the credit loop guarantees it
assign dn_valid = (cnt_q != '0); // FWFT
assign dn_data = mem[rp_q];
assign pop = dn_valid & dn_ready;
always_ff @(posedge clk) if (push) mem[wp_q] <= link_data;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wp_q <= '0; rp_q <= '0; cnt_q <= '0; credit_return <= 1'b0;
end else begin
if (push) wp_q <= (wp_q == AW'(DEPTH-1)) ? '0 : wp_q + 1'b1;
if (pop) rp_q <= (rp_q == AW'(DEPTH-1)) ? '0 : rp_q + 1'b1;
cnt_q <= cnt_q + CW'(push) - CW'(pop);
credit_return <= pop; // one credit per POP, registered
end
end
ap_no_overflow: assert property (@(posedge clk) disable iff (!rst_n)
!(push && cnt_q == CW'(DEPTH)));
endmodule
- Sizing. A credit leaves TX at cycle 0;
link_validregisters (1) + 4 forward stages →
push at 5;cnt_qshows it at 6, pop at 6,credit_returnregisters at 7, 3 return
stages → back at 10,cred_qupdated at 11. Round trip 11, soMAX_CREDITS = DEPTH = 11
(I'd use 12 or 16). With no registers beyond the pipes it would be 8; count the flops you
add, every one is a credit. - Same-cycle send and return: the single
− send + credit_returnupdate is net zero,
no drift. - Credit on pop, not push: a credit means "a slot is free"; returning it on push would
let TX overrun a stalled FIFO. - Conservation SVA (bound at the link top where the pipes
fwd_v[3:0]andret_v[2:0]
are visible):
ap_conserve: assert property (@(posedge clk) disable iff (!rst_n)
u_tx.cred_q + u_tx.link_valid + $countones(fwd_v) + u_rx.cnt_q
+ u_rx.credit_return + $countones(ret_v) == MAX_CREDITS);
M14Q15 · B2. 32-bit to 24-bit Streaming Gearbox with valid/ready (~17 min)
Write a synthesizable module that converts a stream of 32-bit words (4 bytes) into packed
24-bit words (3 bytes) in little-endian byte order (in_data[7:0]is the first byte out):module gearbox_32to24 ( input logic clk, rst_n, input logic in_valid, output logic in_ready, input logic [31:0] in_data, output logic out_valid, input logic out_ready, output logic [23:0] out_data );Requirements:
- Over every 3 input words (
bits), the module emits 4 output words
(bits). - In the steady state (when
in_valid == 1andout_ready == 1),out_validmust be1
every cycle, andin_readyis1on 3 out of every 4 cycles (the cycle where the accumulated
residue already hasbytes does not need to consume an input word!). - Clean valid/ready semantics:
out_validmust not combinationally depend onout_ready.
Gemini Answer
- Design Insight: Work in 8-bit bytes. Using a 7-byte (56-bit) shift buffer
buf_q[55:0]and a 3-bit occupancy countercnt_q(bytes): out_data = buf_q[23:0]andout_valid = (cnt_q >= 3'd3)are 100% registered!in_readycan be either purely registered (cnt_q <= 3'd3) or look-ahead (cnt_after_pop <= 3'd3, which keepsbuf_qdown to 6 bytes = 48 bits and achieves full 1-output-every-cycle steady-state throughput!). Below is the 48-bit (6-byte) full-throughput implementation:
module gearbox_32to24 (
input logic clk,
input logic rst_n,
input logic in_valid,
output logic in_ready,
input logic [31:0] in_data,
output logic out_valid,
input logic out_ready,
output logic [23:0] out_data
);
// 6-byte (48-bit) shift buffer holds between 0 and 6 valid bytes,
// packed toward LSB (buf_q[23:0] is always the next 24-bit word to emit).
logic [47:0] buf_q, buf_d;
logic [2:0] cnt_q, cnt_after_pop, cnt_d;
wire out_fire = out_valid && out_ready;
wire in_fire = in_valid && in_ready;
// Registered output valid & data (zero combinational path on out_valid/out_data!)
assign out_valid = (cnt_q >= 3'd3);
assign out_data = buf_q[23:0];
// After removing 3 bytes (if out_fire), how many bytes remain in buf_q? (0..3)
assign cnt_after_pop = out_fire ? (cnt_q - 3'd3) : cnt_q;
// We can accept a new 4-byte input whenever remaining bytes <= 2 (so 2 + 4 <= 6 bytes max)
assign in_ready = (cnt_after_pop <= 3'd2);
always_comb begin
// Step 1: shift right by 24 bits (3 bytes) if an output word is consumed
logic [47:0] shifted_buf;
shifted_buf = out_fire ? {24'd0, buf_q[47:24]} : buf_q;
cnt_d = cnt_after_pop + (in_fire ? 3'd4 : 3'd0);
// Step 2: insert incoming 4 bytes at byte offset cnt_after_pop (0, 1, or 2)
buf_d = shifted_buf;
if (in_fire) begin
case (cnt_after_pop)
3'd0: buf_d[31:0] = in_data;
3'd1: buf_d[39:8] = in_data;
3'd2: buf_d[47:16] = in_data;
default: ;
endcase
end
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_q <= 3'd0;
buf_q <= 48'd0;
end else begin
cnt_q <= cnt_d;
if (in_fire || out_fire) begin
buf_q <= buf_d;
end
end
end
endmodule
- Steady-State Trace (
in_valid = 1,out_ready = 1):- Cycle 0:
cnt_q = 0in_ready = 1,out_valid = 0loads 4 bytes ( cnt_q -> 4). - Cycle 1:
cnt_q = 4out_valid = 1(cnt_after_pop = 1),in_ready = 1emits 3B, loads 4B ( cnt_q -> 5). - Cycle 2:
cnt_q = 5out_valid = 1(cnt_after_pop = 2),in_ready = 1emits 3B, loads 4B ( cnt_q -> 6). - Cycle 3:
cnt_q = 6out_valid = 1(cnt_after_pop = 3),in_ready = 0(pauses input for 1 cycle while emitting accumulated residue!)emits 3B ( cnt_q -> 3). - Cycle 4:
cnt_q = 3out_valid = 1(cnt_after_pop = 0),in_ready = 1emits 3B, loads 4B ( cnt_q -> 4, repeats Cycle 1 forever with zero output bubbles!).
- Cycle 0:
GPT Answer
A six-byte accumulator is sufficient. Drain three bytes first, then append four if they fit. Output valid depends only on stored byte count, not ready. The low bytes remain stable under output stall.
module gearbox_32to24 (
input logic clk, rst_n,
input logic in_valid,
output logic in_ready,
input logic [31:0] in_data,
output logic out_valid,
input logic out_ready,
output logic [23:0] out_data
);
logic [47:0] bytes_q, bytes_d;
logic [2:0] count_q, count_d, after_pop;
logic pop, push;
assign out_valid = rst_n && (count_q >= 3'd3);
assign out_data = bytes_q[23:0];
assign pop = out_valid && out_ready;
assign after_pop = count_q - (pop ? 3'd3 : 3'd0);
assign in_ready = rst_n && (after_pop <= 3'd2);
assign push = in_valid && in_ready;
always_comb begin
bytes_d = bytes_q;
count_d = after_pop;
if (pop) bytes_d = bytes_q >> 24;
if (push) begin
// Invariant: all bits above stored valid bytes are zero.
bytes_d = bytes_d | ({16'b0,in_data} << (8*int'(after_pop)));
count_d = after_pop + 3'd4;
end
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
bytes_q <= '0;
count_q <= '0;
end else begin
bytes_q <= bytes_d;
count_q <= count_d;
end
end
endmodule
With source/sink always active, the stored-byte cycle is 4 -> 5 -> 6 -> 3 -> 4; output transfers every cycle after initial fill and input transfers on three out of four cycles. Four outputs exactly preserve the byte order of three inputs. Under stalls, count stays in 0..6 and the oldest three bytes remain unchanged until accepted.
This interface has no last/keep: it is a continuous stream and cannot express an arbitrary partial packet tail. A packet-aware extension needs valid-byte count, a pending-last flag, out_keep/out_last, and a rule preventing the next packet from entering until the current tail retires. Do not silently pad or discard bytes.
Claude Answer
// Byte residue buffer. 9 bytes let in_ready be a pure function of the count
// (cnt <= 5 always leaves room for 4 more) while out_valid = (cnt >= 3) every cycle.
module gearbox_32to24 (
input logic clk, rst_n,
input logic in_valid,
output logic in_ready,
input logic [31:0] in_data,
output logic out_valid,
input logic out_ready,
output logic [23:0] out_data
);
logic [71:0] buf_q, buf_shift, buf_next;
logic [3:0] cnt_q, rem, cnt_next;
logic in_fire, out_fire;
assign in_ready = (cnt_q <= 4'd5);
assign out_valid = (cnt_q >= 4'd3);
assign out_data = buf_q[23:0];
assign in_fire = in_valid & in_ready;
assign out_fire = out_valid & out_ready;
always_comb begin
buf_shift = out_fire ? {24'h0, buf_q[71:24]} : buf_q; // drop 3 bytes
rem = out_fire ? (cnt_q - 4'd3) : cnt_q; // bytes still held, 0..5
cnt_next = rem + (in_fire ? 4'd4 : 4'd0);
buf_next = buf_shift;
if (in_fire)
for (int i = 0; i < 9; i++) // insert 4 bytes at byte rem
if (i >= rem && i < rem + 4)
buf_next[8*i +: 8] = in_data[8*(i - rem) +: 8];
end
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) cnt_q <= '0;
else cnt_q <= cnt_next;
always_ff @(posedge clk) buf_q <= buf_next; // bytes >= cnt are never read
endmodule
Steady state with in_valid = out_ready = 1: cnt cycles 4 → 5 → 6 → 3 → 4…: out_valid
every cycle, in_ready at 4, 5 and 3 (three of four), zero at 6 — exactly the one cycle
where the residue already holds ≥ 3 bytes. Maximum occupancy is 5 + 4 = 9, so the buffer
can't overflow; out_valid and in_ready depend only on cnt_q. (A 7-byte buffer with
in_ready = cnt <= 3 is the common attempt; it only reaches ~67% throughput.) Adding
in_last/in_keep: on the last input add keep_count instead of 4 to cnt, and once
last_seen is set emit the residue as a final beat with out_keep = rem and out_last.
Part C — Your questions (0:55–1:00)
M14Q16 · C1. Questions for the interviewer
Gemini Answer
- "Does the RTL team here maintain an internal library of parameterized, formally verified datapath primitives (FIFOs, gearboxes, credit transceivers, arbiters)?"
- "Where do you use credit-based flow control versus standard
valid/readyskid buffers across your chip partitions?" - "How are fixed-point quantization and saturation bit-widths co-designed between the systems/algorithm team and the RTL team?"
GPT Answer
Which primitives are centrally verified? What exact latency convention sizes credit loops? Who signs off arithmetic ranges, rounding and saturation against the algorithm model?
Claude Answer
- Is there a shared, verified library of datapath primitives (FIFOs, gearboxes, credit
loops), or does each IP roll its own? - How are bit widths and rounding modes agreed with the algorithm/systems team, and is
there a bit-exact C model? - Which link in the current chip uses credits, and how was its depth validated?
Additional notes
Notes. Material not tied to a question
Gemini Answer
No answer in gemini_answer.md.
GPT Answer
Validation scope
These are interview RTL examples. No HDL simulator is installed here; compilation, synthesis timing and RTL simulation remain to be checked in the target toolchain. Behavioral model checks can validate the intended arithmetic/queue algorithms but are not a substitute for those HDL checks.
Claude Answer
No answer in claude_answer.md.