Directed Validation: Proving the Test Before Trusting the Result

Abstract

Three RVA23 Co-Design sessions ran twenty-one tasks to finish unit-level verification of the Pacino TAGE and ITTAGE branch predictors. Going in, the counter and usefulness write paths had been proven by reading them back out of RAM and the rest had not. Aging had never been enabled to this point. Sixteen of the tasks were verification work: one specification rule row at a time, with entries seeded directly into table memory, the result read back out of RAM, and every field proven alone before any test mixed fields together.

Four times in the range something plausible was asserted with nothing behind it. A regression count was carried forward without a run behind it, which hid 46 failing tests through several sessions of RTL change. A failure classification named the RTL as inverted without checking the specification first; the specification showed the RTL conforming on all 33 rows and five tests transposed, so repairing the RTL would have broken a working block. A defect was reported, argued harmless, and the task marked itself complete, with no test behind the argument; the defect was real and corrupted the higher-priority table. A test passed without ever having been seen to fail, and that task was abandoned.

The last of these produced a standing requirement in the methodology that every check demonstrate it can fail, which took three forms. Where a defect was fixed, the test ran against the unfixed design first. Where the design already conformed, a defect was introduced deliberately and then reverted, leaving no net RTL change. Where neither applied, the stimulus was seeded so that a wrong answer would be visible.

Twelve of the sixteen verification tasks ended with no RTL change. Four defects were found and fixed, two of which are the same defect in the two predictors: a missing guard that let the use-alternate counter move on a comparison carrying no training signal. Fifteen technical debt items closed, and both predictors ended the range directed-validated at the unit level.

What had not been tested

Before this range, the TAGE and ITTAGE update paths had been fixed where they were known to be broken and left alone where they were not. The counter and usefulness write paths had been proven by RAM readback. The epoch, target, allocation, aging and prediction-side paths had not. Some had never been driven at all: aging ran with tage_enable_aging and ittage_enable_aging held at zero in every test written to that point, so the entire epoch mechanism was dark. Every remaining untested path was enumerated as technical debt items #55 through #74.

Two rules governed how that work would be done. The first was isolation before round-trip: prove each field alone, then mix. The second was the backdoor RAM write, a testbench task that seeds a complete entry directly into a table’s memory using hierarchical paths to the RAM entry.

Both rules were inherited. This range added a third, that every check must demonstrate it can fail. The requirement was written after BP-050 was abandoned for arguing its test would have caught a defect rather than showing that it did.

Structural rework, and what it exposed

The redundant bus dimensioning was not found in this range. It was recorded in session-040 as TD #45, an update-index simplification, and the description was wrong: it asked for per-table ports when the per-table dimension was itself the defect. Session-046 invalidated the entry, marked it wrong in CLOSED_TECH_DEBT.md, and moved the real fix to TD #66. BP-045 was the first task to work it, seven sessions after it was first recorded.

The TAGE controller drove ten separate update and allocation buses dimensioned [table][slot], one slice per table. Only one table per slot is the provider or the allocation target on any update, so every update drove one meaningful slice and TAGE_NUM_TABLES-1 unused ones. BP-045 collapsed them to [slot], with the target table identified by the *_tbl_sel_u0 selects that already existed. The per-table strobe assignments stayed in the existing gen_tbl generate loop; the collapsed data, selector and address buses moved to a new gen_upd_bus block keyed on slot alone. tage_table.sv needed no change. Its input ports were already dimensioned by slot, and only the interconnect above the module had the redundant dimension.

One of the ten could not collapse. The primary and alternate CTR writes can both assert in the same cycle, to different tables, at different history lengths, and therefore at different hashed indices. A single shared index bus carries one value per slot. t_alt_upd_index_u0 was added alongside the primary update index, giving three index buses: primary update, alternate update, and allocation. None of this was in the prompt, which assumed a single shared index. The IA derived the requirement from the CTR update rules and reported it as a deviation. It was accepted as the correct structure rather than a workaround, and it exposed that ITTAGE had the same requirement and lacked the same bus. That became TD #66’s counterpart, TD #76, closed across BP-046 and BP-048.

BP-046 mirrored the change into ITTAGE and renamed twenty-two ittage_cntrl ports that cross the table boundary to the t_ convention. Renaming ports broke tb_ittage_cntrl.sv compilation, and the task edited that file to keep the count stable even though it was not in the manifest. The edit was mechanical and was reported under the stop-and-report policy. The prompt-writing rule that followed is that a task which renames a module’s ports lists every instantiating testbench in its manifest from the start.

The regression counts that BP-045 and BP-046 ran to confirm no behavioral change reported something else. sim_ittage_cntrl returned 32 passing and 44 failing. sim_ittage_table returned 30 and 2. Neither figure had appeared in any status file. tb_ittage_cntrl.sv had been authored green at 76 checks several sessions earlier; the ITTAGE RTL had changed underneath it repeatedly since, and that suite had not been run again. The proven-by-readback claims from the intervening work were true, but they were made against directed rows in tb_ittage, not against the controller suite, and PROJECT_STATUS.md carried the stale 76 throughout.

Classification is not adjudication

BP-047 ran triage and fixed nothing. It classified all 46 failures into two root-cause groups.

Thirty-two were stale testbench scaffolding. Thirty of those traced to one defect in the pred_s0 task: it drove tbl_hit_p1, tbl_cntrl_bits_p1 and tbl_pred_tgt_p1 before the p0-to-p1 posedge and cleared them one time unit after it. The prv_alt_scan block in ittage_cntrl.sv is classified nba_sequent because it reads a flop output, as required. After pred_val_p1 captures, the block evaluates against the correct values, then re-evaluates against the cleared inputs and produces zeros. Every failing check in TC-PRED-02 through TC-PRED-06 returned an actual value of zero regardless of stimulus, and TC-PRED-01, which expects all zeros, passed trivially.

The remaining fourteen were classified as an RTL counter routing inversion in ittage_cntrl.sv. The reasoning was that USE and EPC pass under the same using_primary signal, which isolates the fault to the CTR branch assignments.

That classification was diagnosed entirely against what the testbench expected. TC-UPD-01 through TC-UPD-05 asserted that using_primary=1 should fire alt_ctr_wr, and the RTL fired prm_ctr_wr. Treating the test as the reference makes the RTL wrong. Standard TAGE and ITTAGE train the provider’s counter toward the resolved outcome, which is the opposite of what the tests expected. The repair task was therefore required to load ittage_cntrl_ctr_update_rules.md and settle the direction against the rules table before changing any code, with the swap pre-authorized only if the document agreed with the test.

It did not. BP-048 read the 33-row table and found rows 2 through 17 govern the UP=0 regime with alt_ctr_wr as the governing strobe, and rows 18 through 33 govern UP=1 with prm_ctr_wr. The RTL matched the document on every row. All five test cases had the strobes transposed. Only the tests were repaired; g_ctr_upd was not modified. Had the RTL swap been pre-authorized on the strength of the classification, a conforming block would have been broken and the suite would have gone green over it.

BP-048 also fixed a real RTL defect in the same task. t_alc_index_u0 was driven from the primary update index rather than from the allocation index field carried in the prediction metadata. No existing test could see it, because the test fixture happened to make alc_idx equal prm_idx. TC-UPD-06 was modified so the two differ, the modified test was shown to fail before the fix and pass after, and the defect was corrected against ittage_cntrl_alloc_rules.md.

One item did not resolve. On TC-UPD-05 the automated task found two authority documents in conflict: rows 22 through 29 of the CTR rules table imply the primary strobe fires on a null counter under misprediction with the data saturating at zero, while ittage_cntrl_decisions.md stated there is no counter write in that case. The task chose the rules table, called the decisions note subordinate implementation guidance, and updated the test to match the RTL. Test and RTL then agreed, and the suite was green. Agreement between two artifacts is not evidence when the authority governing both is itself in dispute. The resolution was a specification change rather than a code change: ittage_cntrl_decisions.md gained a “Concurrent CTR and TGT Writes” section relaxing the mutual-exclusivity claim, on the grounds that writing zero to a counter already at zero has no effect.

A defect reported and argued away

TD #57 covered the ITTAGE target write path, which had never been audited. BP-049 verified that the target is written when the provider’s counter is zero on misprediction, added four directed tests covering both provider regimes, and took the suite from 81 to 105 checks with no failures. It marked itself complete and reported TD #57 closed.

In its own notes it recorded that the per-table target write enable in ittage_table.sv gated on (prm_match | alt_match), so a target write reached every hitting table rather than only the provider. It classified this as existing behavior not prohibited by the specification, and stated that the incidental update had no observable effect on correctness.

Both statements were wrong. The interfaces document specifies that only the component that provided the prediction has its target field modified; absence of an explicit prohibition is not authorization. The effect is also observable. In the UP=0 case the non-provider is the primary table, which has the longer history and the higher priority. Its target field is overwritten with a value resolved under a different history length, and on the next prediction at that address the primary hits and is selected as provider ahead of the alternate. The corrupted entry is the one consulted first.

The tests could not have caught it. Each read back only the entry it expected to change. A test that verifies a write landed says nothing about whether a second write landed somewhere it should not have. The same blind spot had produced the alc_index defect one task earlier.

BP-049a ran with ittage_interfaces.md in the manifest, where the provider-only invariant is stated explicitly, and the defect was fixed rather than rationalized. The single t_tgt_wr_u0 strobe was split into t_prm_tgt_wr_u0, asserted only for UP=1 with the primary counter at zero, and t_alt_tgt_wr_u0, asserted only for UP=0 with the alternate counter at zero. The table’s target write enable became prm_tgt_wr & prm_match | alt_tgt_wr & alt_match, mutually exclusive by construction, with the non-provider gating to zero in both regimes. The tests were extended to read back the non-provider entry and assert it unchanged. Both extended checks failed before the fix and passed after, with the failing values showing the exact corruption: expected c000, actual e000, and expected c000, actual b000.

An earlier attempt in the same task passed using_primary into the table and gated there. It was backed out after Verilator scheduling on partial-bit generate assignments produced incorrect results. The split-strobe form reuses the per-slot strobe pattern already proven for the counter writes.

Showing that a check can fail

BP-050 took up the ITTAGE epoch write. It found the write enable provider-only, added tests that read back both the provider and the non-provider entry, and passed. It was abandoned.

The gate was correct and the finding was right, but the task never demonstrated that its non-provider check could fail. It argued that a (prm_match | alt_match) defect would make the check trip. It did not run the test against a defective design to find out. A test that has only ever been run against conforming RTL is indistinguishable from a test that cannot fail, and BP-049 had just shown what that costs.

BP-050b redid the work with the missing step. The epoch write enable was established as provider-only by construction: it rides the mutually exclusive primary and alternate counter strobes, and the epoch write condition is a strict subset of the counter write condition, so no path writes the epoch field without also writing the counter. No RTL change was required. Then the gate was deliberately corrupted to (prm_match | alt_match), both non-provider checks failed with an expected value of 2 against an actual of 0, and the gate was reverted.

Demonstrating that a check can fail became a required step in every verification task that followed, and it took three forms.

Where a defect was fixed, the test ran against the unfixed design first. BP-049a extended its tests to read back the non-provider entry and recorded an expected c000 against an actual e000 before the target write was corrected. BP-051 added the missing UAON guard, removed it again to watch TC-UAON-08 return an expected 8 against an actual 9, and restored it.

Where the design already conformed there was no defect to run against, so one was introduced on purpose. BP-050b widened the epoch gate to (prm_match | alt_match), watched both non-provider checks fail with an expected 2 against an actual 0, and reverted. BP-056 reverted a TAGE epoch gate to prm_match alone, watched two tests fail with the epoch field unwritten, and restored it. BP-057 removed the guard it had just added and watched the counter move. Breaking the design and putting it back leaves no net RTL change, which is why a task reports both an injection and a design it did not modify.

Where neither applied, the discrimination came from the stimulus. BP-052 seeds a pre-state of USE=2’b10 so an aging-disabled result cannot be mistaken for an age-1 result. BP-058 seeds USE=10 in TC-87 for the same reason. BP-060 seeds pred_strong at 111 against the weak boundary 100 so the bit has to move for the test to pass.

What each task delivers is a test that has been seen to fail for the defect it exists to catch.

BP-050 produced one more finding. Running every target in the Makefile rather than the one target under test revealed that tb_ittage_cntrl.sv and tb_ittage_table.sv did not compile at all. BP-049a had renamed t_tgt_wr_u0 across ittage_cntrl.sv and ittage_table.sv and had run only sim_ittage. The 77 and 32 passing counts recorded as current truth in the session handoff were false from the moment of the rename. BP-050a enumerated all twenty targets, repaired both testbenches with no RTL change, and the escape was recorded as BUG-002. BP-050’s own claim to have repaired them did not hold either, which is how the same class of error appeared twice one task apart.

The rule that came out of this is that every simulation and lint target in the Makefile runs, whether or not it is a dependency of all, and that status counts come from a run in the current session. Its cost is measurable. BP-054a ran for three and a half minutes and consumed 72% of context, almost entirely from twenty-two targets’ console output. Compaction occurred in eight of the eleven tasks in the final session. A phased application of the rule, so that early tasks in a sequence run fewer targets, is the planned revision.

Completing ITTAGE

With the epoch write proven, the remaining ITTAGE items ran in dependency order. Aging reads the epoch field, so the epoch write is proven first.

BP-051 found the second real RTL defect of the range. The UAON update block was missing its single-hit guard: with either component count at zero the counter should hold, and without the guard a single-hit transaction with a stale alternate target defaulting to zero could move it on a comparison carrying no training signal. The guard was added and proven by removal, TC-UAON-08 failing with an expected 8 against an actual 9. The fix invalidated a test elsewhere. tc_tgt_b_ext in tb_ittage had encoded its UAON decrement step around the missing guard, using a single-hit scenario that only produced a decrement because the guard was absent. It was rewritten as a two-hit setup.

BP-052 proved the aging and epoch path, dark until this point. All seven rules conformed. The task found that the epoch advances the tick after the interval reaches zero rather than on it, and wrote its tests to match the RTL while noting the document did not specify the boundary. Matching the RTL where the specification is silent is the same move BP-047 made against the tests, and it was handled the same way: the behavior was adjudicated rather than assumed, and the N+1 timing was added to tage_cntrl_use_update_rules.md so the test encodes a documented requirement rather than an observation.

BP-053 proved the thirteen allocation rules with no RTL change and found a specification error. The write-data field order in the allocation rules document did not match the structural entry layout, with the target and epoch fields transposed. Rather than correct one document, the field order was extracted into ittage_table_entry_formats.md and tage_table_entry_formats.md as the single source, referenced from the interface documents. The task also carried more planning documents in its manifest than it read, and after a request timeout it was rerun with three of them removed. From that point a manifest listed only the reference documents, the RTL under test, the packages needed to compile, the testbench and the Makefile.

BP-054 proved the twelve prediction-path rules and found two further specification errors, both carried across from TAGE. ittage_pred_strong was documented as the provider counter differing from 3 and from 4, which is the TAGE weak-band definition for a direction counter. ITTAGE’s counter is a confidence counter and the correct condition is non-zero. Separately, the final-target section referred to a single stored ittage_pred_tgt field. It was rewritten so the consumer selects between the primary and alternate targets using ittage_using_primary, which avoids storing a third 35 to 40 bit target. BP-054a confirmed the corrected document against the RTL and testbench with no code change.

BP-055 ran the ITTAGE round-trip capstone across counter, usefulness, allocation, epoch and target writes in one flow. Its first failure was again a test defect rather than an RTL defect. IT3 was seeded with an epoch value of 3 against a local epoch of 0, giving an age of 1 and an effective usefulness of USE>>1, which is zero. The usefulness increment then wrote 1 over a seed of 1 and produced no visible change. Three expectations were corrected. The rule recorded from it is that when a usefulness delta must be observable, the epoch field is seeded equal to the local epoch so the effective and raw values agree.

TAGE, the same sequence

The ITTAGE sequence became the template for TAGE, and the outcomes mirrored it closely enough to be useful as a comparison.

BP-056 proved the TAGE epoch write. The gate had been changed in an earlier hand fix to prm_match | alt_match and never proven. The local epoch was forced to 2'b10 against a seeded epoch of 00 so the write was a visible change rather than a rewrite of the same value. Injection reverted the gate to prm_match alone and the two alternate-match tests returned with the epoch field unwritten. The TAGE epoch write rides the usefulness write enable, not the counter subset relation that ITTAGE uses; the two predictors reach the same invariant by different structures.

BP-057 found the fourth defect, and it is the ITTAGE defect from BP-051 in the other predictor. The uaon_upd_ff gate was missing && u_alt_tagged[s]. When the provider hit a tagged table and the alternate fell through to the untagged base table T0, the counter moved on a comparison with no tag behind it. The two-line guard was added, TC-81 failed before and passed after. The TAGE manifestation is more specific than the ITTAGE one: not merely a single hit, but a tagged provider with an untagged alternate.

BP-058 proved the aging path with no RTL change, confirming age = (lcl_epoch - EPC) mod 4 and an effective usefulness of the raw value at age 0, a right shift at age 1, and zero at age 2 or greater. One test seeds usefulness at 11 so the four ages produce 11, 01, 00 and 00, which distinguishes a wrong age from a right one. Another proves the behavioral consequence rather than the counter value: a fresh entry is not an allocation candidate, and the same entry aged to an effective usefulness of zero is. Unlike the ITTAGE interval, the TAGE aging interval is a 32-bit input port rather than a static parameter, so the boundary is always reachable and there is no risk of a mechanism untestable at current parameters.

BP-059 proved allocation and folded RAM-level write isolation into the same task rather than deferring it to the capstone, since the task already ran at sim_tage with the tables present. The selected table is read back changed and a non-selected table seeded to a distinct value is read back unchanged. This is the check the ITTAGE allocation task could not reach, having run controller-only. The write-data field order was cross-checked against the entry-format document and matched, so the transposition found in ITTAGE has no TAGE counterpart.

The task also corrected the prompt rather than the design. The requirement asked for a no-consecutive-skip test, a description carried over from the ITTAGE allocation work. The TAGE guard is alc_comp_p1[s] == 0, which stops the scan at the first candidate rather than skipping past a selected table. The two policies differ only when two allocatable candidates are adjacent, which the seeded stimulus did not produce, so the test proves first-match selection and the label was corrected to match.

BP-060 proved the prediction path with no RTL change. The pred_strong definition was the specific item under watch, given the carryover error found in the ITTAGE document. The TAGE document states the provider counter differs from 3 and from 4, the RTL implements (ctr != 3'b011) && (ctr != 3'b100), and they agree. The carryover ran one direction only.

BP-061 closed the range with the TAGE round-trip capstone. Entries were placed to collide at a single RAM address, with T1 through T4 all mapping to index 512, which is the arrangement most likely to expose a cross-path write. Aging was disabled and the epoch seeded equal to the local epoch so usefulness deltas stayed visible, applying the BP-055 lesson directly. The interference checks confirm that a counter and epoch write survive a usefulness write to a different table, and that an allocation disturbs neither the provider entry nor a fourth-table reference held constant through all four steps. One field is not discriminating: with the epoch equal to the local epoch throughout, the epoch writes are zero over zero, so a stuck value would pass. Epoch landing is proven separately with a real delta and an injection, and the capstone’s purpose is interference.

Counts as evidence

Four times in this range a status count was carried forward without a run behind it.

The controller suite carried 76 passing through several sessions of RTL churn without being re-run. BP-049a’s rename left two testbenches uncompilable while their previous counts were recorded as current. BP-050 claimed a repair that had not held. And the TAGE arithmetic did not close across three consecutive tasks: one task reported 69, the next added five test cases and reported 73, which resolves only if the prior baseline was 68; the task after that reported a pass with no integer at all, which does not satisfy a rule requiring a count from the current session. The ledger closed once an integer was produced again, at which point the whole chain of 73, 81, 87, 95, 102 and 103 reconciled end to end.

The pattern is the same as the one the injection step addresses. A count and a passing test are both assertions about a system’s state, and both are worth exactly as much as the run that produced them.

Experiment Summary

Experiment Description Status Checks Runtime Context
BP-045 TAGE update/alloc buses collapsed to per-slot form, third index bus added (#66) PASS 68/68 17m 54s 82%
BP-046 ITTAGE alternate index bus added, table-facing ports renamed to t_ (#76) PASS counts held 32/44, 30/2, 81/0 17m 15s 83%
BP-047 ITTAGE escape triage, 46 failures classified into two root-cause groups TRIAGE ONLY 46 classified, 0 fixed 11m 52s 60%
BP-048 CTR direction adjudicated against rules table: test transposed, RTL conforming; alc_index defect fixed PASS cntrl 77/0, table 32/0 11m 9s 58%
BP-049 ITTAGE target write verification (#57): non-provider write reported and rationalized, marked complete in error PARTIAL 105/0 16m 55s 82%
BP-049a Target write strobe split, provider-only invariant proven by non-provider readback (#57) PASS 113/0 1h 1m 34s 60% + compaction
BP-050 ITTAGE EPC write, first attempt: discriminating power argued but never demonstrated ABANDONED 17m 3s 20% after compaction
BP-050a All Makefile targets enumerated and run; BP-049a port-rename escape found and repaired (BUG-002) PASS 20/20 targets 8m 54s 57%
BP-050b ITTAGE EPC write proof with defect injection and revert (#56) PASS sim_ittage 125/0 24m 20s 47%
BP-051 ITTAGE UAON single-hit guard: real RTL defect found and fixed (#59) PASS cntrl 92/0, ittage 125/0 35m 7s 53%
BP-052 ITTAGE aging / epoch path, previously dark (#61) PASS cntrl 112/0 22m 34s 30%
BP-053 ITTAGE allocation policy and gating; entry-format documents created (#63) PASS cntrl 147/0 30m 45s 57% + compaction
BP-054 ITTAGE prediction-side correctness; two document errors corrected (#65) PASS sim_ittage 164/0 28m 7s 57%
BP-054a Corrected decisions document verified against RTL and testbench (#65) PASS 22/22 targets 3m 32s 72%
BP-055 ITTAGE round-trip capstone across CTR, USE, alloc, EPC, TGT (#72) PASS 211/0, 20 targets 1h 12m 54s 58% + compaction
BP-056 TAGE EPC write proof with defect injection and revert (#55) PASS sim_tage 73/0 46m 6s 24% + compaction
BP-057 TAGE UAON single-hit guard: real RTL defect found and fixed, BUG-003 (#58) PASS sim_tage 81/0 36m 33s 66% + compaction
BP-058 TAGE aging / epoch path, previously dark (#60) PASS sim_tage 87/0 26m 41s 48% + compaction
BP-059 TAGE allocation with RAM-level write isolation folded in (#62) PASS sim_tage 95/0 29m 28s 51% + compaction
BP-060 TAGE prediction-side correctness (#64) PASS sim_tage 102/0 20m 45s 25% + compaction
BP-061 TAGE round-trip capstone across CTR, USE, alloc, EPC (#71) PASS sim_tage 103/0 29m 5s 50% + compaction

What comes next

Both predictors close this range directed-validated at the unit level. The items that remain are deferred rather than open questions: the sram_init non-fast path for both units, rollback and history recompute, which move to cluster integration, the dual-slot configuration, the ITTAGE counter width reduction from three bits to two, and a missing fast-init simulation target. The counter width reduction will churn any counter and aging tests written before it lands, which argues for doing it before further ITTAGE test work rather than after.

The arbitration and cluster items are gated on bp_cluster integration, which has open design questions of its own. The next predictor units (FTB, SC and RAS) are not started. The directed-validation sequence used here is the template for them.

Technical Debt Referenced

The table below reports status as of the close of this range. Later experiments outside the range have since changed the state of some items carried alongside these.

# Item Resolution path
55 tage EPC write proof. Changed RTL, never proven by readback. CLOSED BP-056. epc_we gate changed as a USE rider in earlier work. Seed entry, drive EPC-writing update, read EPC back via prediction, confirm landing.
56 ittage EPC write proof. Changed RTL, never proven by readback. CLOSED BP-050b. epc_we_s0/s1 fixed alongside use_we with no positive test. Readback-verify per provider, UP=1 and UP=0.
57 ittage TGT target replacement. Untested. Successor to #51. CLOSED BP-049a. Suspect for the same provider-gating defect as CTR and USE. Target written on mispredict when CTR null only. Trace path, readback-verify reachable rows. ITTAGE only; TAGE has no target field.
58 tage UAON trigger rules. Tested only as setup, never as DUT. CLOSED BP-057. tage_cntrl_uaon_update_rules.md promoted from Draft to authority, directed test per row, use_alt_on_na asserted and cleared per rule. BUG-003 found and fixed.
59 ittage UAON trigger rules. Tested only as setup, never as DUT. CLOSED BP-051. ittage_cntrl_uaon_update_rules.md promoted from Draft. USE tests had relied on UAON asserting as a precondition without it ever being verified.
60 tage aging / epoch path. Entire path dark. CLOSED BP-058. Supersedes #41. All prior tests ran with tage_enable_aging=0. Drive aging enabled, exercise EPC-versus-epoch compare and USE decrement over the interval.
61 ittage aging / epoch path. Entire path dark. CLOSED BP-052. Same as #60 for ITTAGE. Consumes the EPC field whose write changed in earlier work; see #56.
62 tage allocation policy + write gating. Never the feature under test. Successor to #51. CLOSED BP-059. Allocation had been treated as residue to invalidate, never verified. Which table allocates, write-enable gating, allocation index. RAM-level write isolation verified.
63 ittage allocation policy + gating. Never the feature under test. Successor to #51. CLOSED BP-053. Same as #62. Allocation on mispredict, CTR-null condition, alloc_we gating, allocated entry state read back.
64 tage prediction-side correctness. Not directed-tested. CLOSED BP-060. Prediction path had been exercised only as setup for update tests. Directed-test provider selection, using_primary, pred_strong and the target mux against seeded entries.
65 ittage prediction-side correctness. Not directed-tested. CLOSED BP-054 and BP-054a. Same as #64 for ITTAGE. Resolves the #42 test aspect: provider, using_primary and target operate at s2, not s3.
66 TAGE structural rework. Per-table 2D update and allocation buses where shared per-slot buses are correct. Sequence before TAGE allocation #62. CLOSED BP-045. Collapsed to shared per-slot buses routed by the existing selects; t_alt_upd_index_u0 added for the concurrent primary and alternate CTR write case.
71 tage round-trip. Combined test, run only after individual tests pass. CLOSED BP-061. Mixed CTR, USE, allocation and EPC in one flow. Run only after #55, #58, #60, #62 and #64 were each proven alone; mixing before isolation reproduces multi-cause ambiguity.
72 ittage round-trip capstone. Combined test, run only after individual tests pass. CLOSED BP-055. Mixed CTR, USE, allocation, EPC and TGT in one flow, after #56, #57, #59, #61, #63 and #65. Allocation RAM-level write isolation verified.
76 ittage should have independent index buses for primary and alternate table updates. CLOSED across BP-046 and BP-048. History lengths differ and indices are hashed per length, so primary and alternate updates require separate buses. The t_alc_index_u0 source defect was the remainder, closed in BP-048.

Design Process Notes

What the implementation assistant contributed

The implementation assistant produced every directed test in the range, approximately 120 test cases across the two predictors, each citing the specification row it exercises. It root-caused four RTL defects to file and line and fixed them in the same session, and it produced the diagnostic reasoning behind the largest failure group in BP-047, the nba_sequent re-evaluation against cleared inputs, which required tracing Verilator block classification rather than reading a failing comparison.

Three of the range’s specification errors were found by the implementation assistant reporting a discrepancy it was not asked to look for: the allocation write-data field order, the pred_strong carryover, and the stored final-target field. In BP-045 it went further and contradicted the task itself, deriving the three-index bus requirement from the CTR update rules and reporting the prompt’s single-index assumption as wrong. It also backed out its own first approach in BP-049a after Verilator scheduling on partial-bit generate assignments gave incorrect results, and documented why.

Its failure mode in this range was uniform. Where the task allowed judgment about what a finding meant, it resolved toward the artifact in front of it: BP-047 treated the testbench as the reference for the counter direction, BP-049 treated the absence of an explicit prohibition as authorization, BP-050 argued a check’s discriminating power instead of demonstrating it, and BP-052 wrote tests to match RTL behavior the document did not specify. In each case the reasoning is defensible in isolation and wrong against the specification. The pattern is not carelessness in execution; it is the absence of the authority document at the moment the judgment was made.

What the planning assistant contributed

The planning assistant scoped and sequenced all twenty-one tasks, including the dependency order that put every epoch write proof before its corresponding aging proof, and the isolation-before-round-trip ordering that held both capstones until every path was proven alone.

Its most consequential contribution was the guard on BP-048. Presented with a classification naming the RTL as inverted, it identified that the diagnosis rested entirely on the testbench being correct, and required the repair task to settle the direction against the rules table before changing code, with the swap pre-authorized only on the document’s agreement. The document disagreed and a conforming block was left alone.

It also caught the argued-not-demonstrated gap in BP-050 and the rationalized defect in BP-049, and identified the count arithmetic failures across BP-055 through BP-058.

Its own errors were in scope and in labelling. Manifests were oversized on several tasks, once contributing to a timeout, and the manifest omission on BP-049, which left the interfaces document out, is the direct cause of the rationalized target-write defect. The BP-059 requirement carried a no-consecutive-skip label across from the ITTAGE allocation work when the TAGE policy is stop-at-first, which is the same cross-track contamination that appeared in the specification documents, in a prompt rather than a design file.

What the architect contributed

Every expected value in the range originated with the architect, as did every specification correction. The rejection of BP-049’s completion claim, the decision to extract the entry field order into standalone format documents rather than patch one rules table, the ruling that the N+1 epoch timing belonged in the aging document rather than only in a test, and the judgment that the epoch wrap needed no documentation change were all architect decisions.

The all-targets rule and its planned phased revision are also architect decisions, made after measuring the rule’s context cost rather than before.

The generalization

Sixteen of the twenty-one tasks were verification work, and twelve of those ended with no RTL change. The design conformed to its specification nearly everywhere it was checked, which means the range’s output is almost entirely tests, and their passing does not establish what they are worth.

A test written against conforming RTL and never run against anything else has an unknown detection capability. It may encode the requirement, or it may encode whatever the design does. The two are indistinguishable from the outside, and both produce the same green result. Separating them is cheap. Break the design in the way the test claims to detect, confirm the test fails, restore. Where there is nothing to break, choose stimulus that makes a wrong answer visible. The values recorded in this range are what make the passing results mean anything. An expected 2 returned 0 when the epoch gate was widened. An expected 8 returned 9 when the UAON guard was removed. An expected c000 returned e000 against the uncorrected target write path. Each took minutes.

The same principle explains the range’s other failures, which are not about tests at all. A count carried forward is an assertion about a system state without a run behind it. A defect reported and argued harmless is an assertion about consequences without a test behind it. A classification that names the RTL as inverted is an assertion about which artifact is authoritative without an adjudication behind it. Each was plausible, and each was wrong or unproven in the direction that would have cost the most: the rationalized target write would have corrupted the higher-priority table, and the RTL swap would have broken a block that conformed on all 33 rows.

The two predictors provide the range’s one controlled comparison. The same UAON defect exists in both, found independently by the same directed method rather than by transferring the fix, and the TAGE manifestation is more specific than the ITTAGE one. Where a document error crossed from one predictor to the other, the directed test on the receiving side found it, and the deliberate check of the same item on the originating side came back clean. A method that finds the same class of defect twice in two independent implementations, and that distinguishes a genuine carryover from a suspected one, is doing something a single pass cannot demonstrate.


References

No references required for this post.


Jeff Nye is a microprocessor architect with 35 years of industry experience spanning performance modeling, RTL implementation, and architecture for high-performance OOO processors. He has contributed RTL to Pentium 4, ARM V7, TI C6x and RISC-V designs, and recently served as sole architect and full-stack implementer of the TAGE-SC-L + ITTAGE branch prediction cluster in an 8-issue RVA23 RISC-V processor, from research through timing closure at 2.75 GHz. He holds +20 issued patents in processor design, architecture, and hardware virtualization. He is the author of Pacino and the uarchlabs methodology documented here.

Connect on LinkedIn.