Abstract

The previous post closed with the ITTAGE planning document set complete but not yet reconciled, and ittage_table_interfaces.md still a hard blocker for RTL. This post covers six sessions that closed that blocker, wrote ittage_table.sv and ittage_cntrl.sv, assembled them into ittage.sv with a full arbitration layer, and produced the first passing integrated testbench. A response buffer added by copying the TAGE arbitration pattern was later traced and confirmed dead logic, since ITTAGE and SC are mutually exclusive consumers, and removed. The first integrated testbench found four DUT bugs that neither module’s standalone testbench had caught. The post ends with ITTAGE unit-and-integration tested at 35/35 checks and five new technical debt items opened for the next cleanup pass.


Opening

Before ITTAGE RTL work resumed, two closeout items ran that are not part of this post’s main thread. A TAGE-side audit (BP-031, BP-032) cross-checked the USE and UAON update-rules documents against the actual tage_cntrl.sv RTL and found one real defect: the module was storing the raw USEFUL field in prediction metadata instead of the aged u_eff value. Two assignments were fixed; 68/68 regression passed. The UAON audit found no discrepancies. Separately, a redundancy-collapse pass across all eight ITTAGE planning documents ran, and an unplanned second piece of work rode along with it: a size reduction of PROJECT_STATUS.md that split it into three files. Neither item is ITTAGE implementation work, and neither gets its own section below. Both are noted here because the debt numbers they opened and closed — TD# 43, TD# 44, and TD# 45 — get reused for unrelated ITTAGE items later in this same range, which the Technical Debt Referenced section addresses directly.

With the planning set collapsed and reconciled, RTL work began at ittage_table.sv and closed, six sessions later, with a passing integrated testbench for ittage.sv.


ittage_table.sv and ittage_cntrl.sv: Discipline Learned the Hard Way

The first attempt at ittage_table.sv (BP-033) passed simulation at 24/24 checks and lint-clean, and was abandoned anyway. Review found that ittage_pred_val_p0[s], a p0-stage signal, was gating hit_p1[s], a p1-stage output — a pipeline-stage violation. The testbench had not caught it because the prompt did not specify expected values; Claude Code wrote tests that matched the RTL it had just written, not the specification the RTL was supposed to implement.

BP-033-FIX-1 re-ran with two changes to the prompt: a binding decision requiring hit_p1[s] to be derived solely from ram_dout and tag_hash_p1, copied verbatim from tage_table.sv’s pattern, and a Test Vector Table with every expected value computed from spec before any code was written. A specific test, TC-PRED-VAL-ZERO — valid entry loaded, predict with pred_val=0, expect hit_p1=1 — was added to target the exact defect BP-033 had shipped. The result was 32/32 checks, lint-clean, in 41 minutes at 83% context with compaction. Pre-computed expected values in the prompt, and splitting implementation and testbench into separate prompts to reduce context pressure and correlated-bug risk, became standing requirements for every ITTAGE testbench prompt written afterward.

ittage_cntrl.sv took one abandoned attempt and three completed ones. BP-034a was abandoned outright: the planning-document context required to write the prompt ran close to 4,000 lines of markdown, the PA session was repeatedly mishandling formatting and file paths, and Jeff restarted with a fresh PA session rather than continue debugging the one in progress. BP-034 then implemented the prediction path — provider scan, alternate-provider scan, allocation candidate selection, the UAON mux, ittage_pred_meta_t assembly — with the update path fully stubbed to zero. BP-035 added the update path: CTR, USE, EPC, and TGT updates, allocation write-data assembly, UAON counter update, and the u_eff aging computation that replaces raw USE in the prediction path.

BP-035 also introduced a defect that BP-036 later found and named precisely: prm_tbl_sel_u0 and alt_tbl_sel_u0, the update-path table selectors, were driven from live prediction-scan intermediates — placeholders left over from when the update path was still stubbed — rather than from the predict-time metadata captured into the FTQ and returned at update time. The project’s own meta-at-predict / consume-at-update pattern requires the latter; BP-034’s stub-era placeholder violated it and survived into BP-035 uncorrected. BP-036’s testbench caught it, along with two Verilator scheduling defects: a prv_alt_scan always_comb block that read only module inputs and was therefore classified stl_sequent by Verilator — evaluated once at simulation start and never again — fixed by gating the block on a flopped input to force nba_sequent classification; and a meta_p1 intermediate wire that caused a scheduling inversion, fixed by removing the wire and having meta_p2_reg read the scan outputs directly. The stl_sequent finding was written up as a standing CLAUDE.md rule: any always_comb block that must re-evaluate after a flop update has to read at least one flop output, not module inputs alone. BP-036 closed at 76 PASS, 0 FAIL.


ittage.sv: Wrapper, Arbitration, and a Response Buffer That Didn’t Need to Exist

ittage.sv instantiates ittage_cntrl and five ittage_table instances (IT1-IT5) with per-table parameter assignments. The first attempt, BP-037, got the module structurally correct and lint-clean but instantiated sram_init once per table — five instances instead of one shared instance at the module top level. BP-037a fixed the instance count; the fix itself removed the fast_init strapping mux that zeroed tbl_ri_wr, tbl_ri_wa, and tbl_ri_wd during fast-init, which BP-037b then had to restore. Three fix-prompt discipline rules came directly out of this two-pass cycle: a fix prompt must state the required end state in full rather than a delta (“restore X” is not actionable to a tool with no memory of the prior session), must state explicitly what must not change with the exact working behavior reproduced rather than referenced, and any prompt instantiating sram_init must state the required instance count explicitly rather than deferring to a planning document.

BP-038 added the arbitration layer — PQ, UQ, credit arbiter, competing-stage mux, and a prediction response buffer — by following the tage.sv arbitration pattern with ITTAGE_-prefixed parameters. The response buffer was included because tage.sv’s pattern has one, built to support a downstream SC consumer that can apply backpressure. consumer_ready was hardwired to 1'b1 internally rather than exposed as a port, which put the buffer permanently in bypass mode without anyone yet confirming it needed to exist at all. BP-038a closed a related item, adding a trx_type input port to ittage_cntrl.sv so write enables could be gated correctly by transaction type.

The next session traced the response buffer’s actual necessity and found it had none. ITTAGE predicts targets for indirect branches only; SC operates on conditional branches only. The two are mutually exclusive branch classes, so there is no ITTAGE-to-SC chaining for a response buffer to arbitrate. TD# 48 was closed by removing it: BP-038b deleted the RB memories, its always_ff and always_comb blocks, and replaced the buffered output path with direct assigns from ittage_cntrl’s prediction-ready and metadata outputs, simplifying the arbiter’s Rules 3 and 5 by dropping their resp_buf_full_w guards. Six active arbitration rules remained. Lint passed clean on the first attempt.


tb_ittage.sv: What Unit-Clean Modules Still Hid

BP-039 wrote the first testbench exercising ittage.sv with real ittage_table instances wired in, using the FAST_INIT and round-trip-hit methodology established in BP-033-FIX-1. Thirteen test cases, 35 checks, all passing, zero warnings — and four DUT bugs found and fixed along the way, none of which either module’s standalone testbench (32/32 for the table, 76/76 for the controller) had surfaced.

Two of the four were specific to wiring real tables into the controller and could not have been exercised by a controller-only testbench: ittage_table.sv’s addr_mux/din_mux used tbl_ri_active as their top-priority guard, and with FAST_INIT=1, sram_init’s active signal stays high for the full 512-cycle init sequence regardless of the fast-init flag — forcing the write address and data muxes to zero and silently dropping every allocation write during that window. The fix changed the guard from tbl_ri_active to ri_we (tbl_ri_active & tbl_ri_wr), so with fast-init active and tbl_ri_wr=0, the normal write path is used. The same session opened TD# 50 to check whether tage_table.sv has the same latent pattern. Separately, ittage_cntrl.sv’s alc_index_u0 was computed from the prediction’s provider index — zero for any no-hit prediction — instead of from ittage_pred_meta.ittage_alc_idx, the actual allocation index carried in the prediction metadata; a round-trip allocate-then-hit test is the only kind of test that exposes this, since it requires an entry that was actually allocated to actually be found again.

The other two were within a controller-only testbench’s reach but were not caught by BP-036’s 76 checks. branch_id was read directly from pred_inp_p0 at the meta_p2_reg stage, one cycle after the testbench had already deasserted the input — fixed by adding a branch_id_p1 register that captures the value at the correct pipeline stage. And the ctr_upd block’s using_primary branches were inverted: using_primary=1 was updating the alternate provider’s counter and using_primary=0 was updating the primary’s, backward from the ITTAGE algorithm’s requirement that the provider actually used gets its own counter updated. The branches were swapped. Because this defect sat in the same class of logic as three other update rules (USE, TGT, and allocation write data) that BP-036’s suite had not independently exercised, TD# 51 was opened: a systematic audit, one round-trip test per rule row in ittage_cntrl_ctr_update_rules.md and ittage_cntrl_use_update_rules.md, independent of the existing test set.

One planned test, TC-ARB-07, is marked N/A rather than PASS or FAIL — it tested response-buffer behavior that BP-038b had already removed by the time BP-039 ran.


Experiment Summary

Experiment Description Status Checks Runtime Context
BP-031 TAGE debt #44: USE update rules audited against RTL, one defect fixed PASS 68/68 5m 44s 39%
BP-032 TAGE debt #45: UAON update rules audited against RTL, no changes PASS 68/68 3m 53s 37%
BP-033 ittage_table.sv, first attempt – p0/p1 gating defect ABANDONED 24/24 40m 23s 24%
BP-033-FIX-1 ittage_table.sv, corrected re-implementation PASS 32/32 41m 33s 83% + compaction
BP-034a ittage_cntrl.sv prediction path, first attempt ABANDONED
BP-034 ittage_cntrl.sv prediction path PASS lint only 18m 48s 80%
BP-035 ittage_cntrl.sv update path PASS lint only 24m 52s 75%
BP-036 ittage_cntrl.sv testbench, three RTL defects found and fixed PASS 76/76 1h 35m 59s 30% + compaction
BP-037 ittage.sv structural wrapper PASS lint only 27m 12s 70%
BP-037a sram_init instance-count fix PASS lint only 5m 47s 32%
BP-037b fast_init strapping restore PASS lint only 6m 18s 26%
BP-038 ittage.sv arbitration layer added, including response buffer PASS lint only 10m 42s 79%
BP-038a trx_type gating added to ittage_cntrl.sv PASS lint only 5m 11s 37%
BP-038b Response buffer removed, confirmed dead logic PASS lint only 3m 1s 25%
BP-039 tb_ittage.sv, four DUT bugs found and fixed PASS 35/35 1h 43m 42% + 2 compactions

What Comes Next

Five technical debt items are open at the close of this range: TD# 43 and TD# 44 (both requiring ittage_cntrl_decisions.md corrections), TD# 45 (TAGE T0 index handling), TD# 50 (the FAST_INIT sram_init pattern, and whether tage_table.sv shares it), and TD# 51 (the systematic CTR/USE/TGT update-rule audit). TD# 46 also needs attention: it was marked closed after BP-038a and reopened when tb_ittage_cntrl.sv was found still missing the trx_type port that closure depended on, so sim_ittage_cntrl currently fails with a PINMISSING error. None of these block further ITTAGE work, but the update-rule audit in particular should run before the next module in the ITTAGE thread begins, since it was opened specifically because one inverted condition was found by accident rather than by systematic coverage.


Technical Debt Referenced

TD# 43, TD# 44, and TD# 45 were opened in session-036 and closed in session-037, over TAGE-side documentation and RTL-audit work unrelated to this post’s main thread (see Opening, above). All three numbers were subsequently reused for unrelated ITTAGE items, opened during session-040’s ittage_cntrl.sv work and still open at the close of this range. Both versions are quoted directly from session_handoff-038.md (closing text) and session_handoff-041.md / session_handoff-043.md (current, reused text) — not reconstructed.

# Item (as of Part 37, closed) Resolution path (as of Part 37)
43 tage_cntrl_use_update_rules.md needs a background paragraph explaining the USE field’s purpose in tage table entries. Add background section before the aging section: field width, eviction-protection role, epoch-decay interaction, update-trigger condition. Closed session-037.
44 Claude Code to verify USE update rules against current tage_cntrl.sv RTL. Run BP-031. Closed session-037: one mismatch found (raw USEFUL vs u_eff in prediction metadata) and fixed.
45 Claude Code to verify UAON update rules against current tage_cntrl.sv RTL. Run BP-032. Closed session-037: no discrepancies found.

# Item (current, session-043) Resolution path (current)
43 ittage_pred_strong definition in ittage_cntrl_decisions.md is incorrect. Needs update to CTR > 0 (not NULL). Deferred. Correct ittage_cntrl_decisions.md when scheduled.
44 ittage_cntrl_decisions.md decoration flags section needs correction once TD #43 above is implemented. Deferred, dependent on TD #43.
45 TAGE T0 index handling needs revisit. Recorded during upd_index_u0 discussion in session-040. Deferred.

# Item (current, session-043) Resolution path (current)
46 tb_ittage_cntrl.sv is missing the trx_type port added to ittage_cntrl.sv in BP-038a. sim_ittage_cntrl fails with a PINMISSING error. Previously marked closed in error. Reopened. Fix before the next integration session.

# Item (current, session-043) Resolution path (current)
48 ittage.sv response buffer bypass behavior with consumer_ready hardwired to 1’b1 needed verification against bp_cluster backpressure expectations. Closed. RB confirmed dead logic – ITTAGE and SC are mutually exclusive consumer classes (indirect vs. conditional branches) – and removed in BP-038b.

# Item (current, session-043) Resolution path (current)
50 sram_init runs its full initialization sequence even when FAST_INIT=1; its active signal stays high for 512 cycles, overriding ittage_table.sv’s write-mux paths during that window (BP-039 Bug 3). When FAST_INIT=1, sram_init should not run its init sequence and active should not assert. Open. Scope: sram_init.sv, ittage_table.sv. Audit tage_table.sv for the same pattern.

# Item (current, session-043) Resolution path (current)
51 BP-039 found using_primary inverted in ittage_cntrl.sv’s ctr_upd block. Systematic risk that other update-logic blocks (USE, TGT, allocation) may have similar errors not yet exercised by the existing test set. Open. New round-trip test set required, one test per rule row in ittage_cntrl_ctr_update_rules.md and ittage_cntrl_use_update_rules.md, independent of TC-P01 through TC-UAON-01.

Design Process Notes

What the sessions exposed about the methodology

Two distinct kinds of testbench failure appear in this range, and they call for different fixes. The first is a testbench with no independent power to catch a defect at all: BP-033’s testbench wrote its expected values from the RTL under test, so it could only ever agree with whatever the RTL happened to do. The fix that followed — pre-computed expected values fixed in the prompt before any code is written — closed this failure mode completely for the rest of the range; no later session repeats it.

The second kind is narrower and did not fully close. BP-039 found four defects that BP-036’s 76-check, unit-level ittage_cntrl.sv testbench had not caught. Two of the four — the FAST_INIT/sram_init write-mux override and the allocation-index source error — involve real ittage_table instances wired into the controller and could not have been exercised by a controller-only testbench regardless of how thorough its test set was. The other two — the branch_id pipeline-timing error and the inverted using_primary condition — sit entirely within ittage_cntrl.sv and were, in principle, reachable by a controller-only test. Whether extending BP-036’s test set would have caught them was not investigated in this range; TD# 51 opens that question as a systematic audit rather than assuming an answer.

The BP-037/BP-037a/BP-037b sequence is a fix-prompt discipline failure, not a design failure: the underlying wrapper structure was correct from BP-037 onward, but two rounds of fix prompts were needed because a fix prompt specifying a delta from an unstated prior state does not tell a tool with no session memory what the required end state actually is. The three rules recorded in that section were written directly against this failure, not as anticipatory guidance.

What the PA contributed

The PA identified the BP-033 p0/p1 gating defect on review before it reached a second session, and designed the pre-computed Test-Vector-Table fix that prevented the same class of defect from recurring anywhere else in the range. It made the call to trace and remove the response buffer once the ITTAGE/SC consumer relationship was examined directly, rather than carrying it forward as unverified. It also converted three fix-prompt failures into standing prompt-writing rules, and scoped and split all fourteen prompts in this range, including the decision to abandon BP-034a outright rather than continue debugging a degraded PA session.

What the IA contributed

The IA’s BP-036 and BP-039 testbenches found and root-caused six RTL defects between them autonomously, with file-and-line-level fixes applied and verified against the regression suite in the same session — the stl_sequent/nba_sequent scheduling diagnosis in BP-036 in particular required tracing Verilator’s block-classification behavior, not just re-running a failing test. The BP-037a and BP-037b fix passes were each single-attempt, lint-clean, no-iteration results once the prompt stated the required end state explicitly.

The generalization

Across this range, test discipline established early prevented one entire class of defect from recurring — no session after BP-033-FIX-1 shipped a testbench that derived its own expected values from the RTL it was testing. That discipline did not, on its own, guarantee coverage at every module boundary: BP-039’s four defects surfaced only once ittage_table.sv and ittage_cntrl.sv were assembled into ittage.sv and driven through arbitration, a configuration neither module’s standalone testbench exercised. This is consistent with BLOG_bpu_9’s finding that a testing method surfaces exactly the class of problem it is built to catch and no other — a coverage tool found gaps that passing tests had hidden; here, a controller-only testbench could not have found defects that require a real table wired in, regardless of how well-disciplined its own expected-value methodology was. The response buffer follows the same shape from a different angle: it was built by copying a working pattern from tage.sv without first confirming the pattern’s precondition — a downstream consumer requiring backpressure — held for ITTAGE at all.


No references required for this post.