RTL Review

← 13 DFT, Scan, MBIST and RTL Signoff | Index

Round 14 — Combined answers (Gemini · GPT · Claude)

Contents

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

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

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

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 Dfwd pipeline stages and the credit return path has Dret pipeline stages.

Gemini Answer

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.

Gemini Answer

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 gcd(64,40)=8 and lcm(64,40)=320 the first thing you should calculate, and what is the minimum size (in bits) of the internal shifting/holding buffer?

Gemini Answer

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

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 w∈{−1,0,+1}. What does a single multiply-accumulate (acc←acc+w⋅x) synthesize into compared to an 8-bit × 8-bit MAC, and how can you avoid switching activity when w=0?

Gemini Answer

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 F=4 (down to 16 physical MACs), what happens to the multiplier area, the mux/register overhead, the throughput, and the weight memory bandwidth?

Gemini Answer

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 K=256 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

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

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 fmax, and what transformation allows you to speed it up?

Gemini Answer

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 N=8 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];

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 = 4 cycles on
link_valid/link_data, RET_PIPE = 3 cycles on credit_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:

Gemini Answer

// ============================================================================
// 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

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
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:

Gemini Answer

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

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

  1. "Does the RTL team here maintain an internal library of parameterized, formally verified datapath primitives (FIFOs, gearboxes, credit transceivers, arbiters)?"
  2. "Where do you use credit-based flow control versus standard valid/ready skid buffers across your chip partitions?"
  3. "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

  1. Is there a shared, verified library of datapath primitives (FIFOs, gearboxes, credit
    loops), or does each IP roll its own?
  2. How are bit widths and rounding modes agreed with the algorithm/systems team, and is
    there a bit-exact C model?
  3. 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.