ScreamingPigeon

I like computers...


My experience working on CBP-NG

If you want to get into the meat and gravy of this project, find my paper, code and video slides here under “RABT - Run-Ahead Block TAGE. Prakhar Gupta, Yuwei Sun, Rishav Sen, Reet Sinha, Swetha Karthikeyan (University of Illinois Urbana-Champaign)”. (For the most up to date (and bug-free) code, check out my repo)

My presentation at the CBP-NG workshop at ISCA 2026 at Raleigh, NC
My presentation at the CBP-NG workshop at ISCA 2026 at Raleigh, NC

Back in November 2025, when I was done shuttling the GDS for WRAITH, I was hit with a strong wave of existentialism. “What am I going to do now?”. I had just gained back the 25+ hours/week that I had been using to crank tapeout. That very same week, I got a fortuitous email from CBP, announcing a pilot program for another championship in 2026.

For some context, Early last year, when I was still co-oping in the Chicago suburbs, I wanted to participate in CBP 2025 - a competition for designing the most accurate branch predictor! For multiple reasons, I was unable to carve out time to participate. Thankfully, I had signed up for the mailing list, which put the 2026 championship on my radar.

At the time, I had got out of a fairly rigorous interview process for a big hardware company for an architecture modeling role. A big part of the interview involved discussions on performant Cpp written with OOP principles in mind, which has been a big weakness in my skillset in the past. That, and a discussion of GPU frontend architectures. I didn’t end up getting the opportunity, and had made a mental note for getting better at both frontend and CPP.

The 2026 championship, involved a new framework, that emphasized CPP so I figured it was the perfect opportunity to kill two birds with one stone. And thus, RABT was born. This project lasted way longer than I anticipated.

The championship

So CBP has a long history! The first one was back in 2004. It was at MICRO and sponsored by Intel. Andre Seznec and Pierre Michaud entered the O-GEHL and PPM predictors. After the championship, these 2 GOATs collaborated, to invent the TAGE predictor, which has been the dominant branch prediction paradigm ever since.

The next iteration was in 2006, where the L-TAGE predictor won (TAGE with better loop detection). Ever since, TAGE based designs have consistently dominated the championship.

In the past, the championships have always emphasized defining a good algorithm, so the entries have always been constrained to high level architecture with some sort of “algorithmic implementation”. With high area budgets, the championship tended to encourage incredibly large and “crazy” (i mean it in a good way) predictors.

This comes at the cost of making these algorithms hard to port to real silicon (whether that should be the goal of the championship or not is a whole other debate). In order to address this, this year’s “next-generation” championship implemented a new framework, that models power and energy, and forces entries to submit a microarchitectural model of their predictor, adding considerable implementation complexity. This is done with a CPP library called HARCOM. If you’re curious about the departures from conventional championships, you can read more about the infrastructure here.

My development chronology

So the original plan was to do a weekly dev log for this project. As per usual, I forgot :(. So what I did here, is used all my chat history with LLMs+harnesses, as well as my git commit log, to reverse engineer the chronology of the project. I guess this allows for me to also annotate this with hindsight, which hopefully makes for a more pleasant reading experience.

Jan 2026

So January was the slowest month. My main focus was recruiting teammates to help me out with the project effort, and familiarizing myself with the basics of branch prediction.

After chatting with a bunch of people and getting a few underclassmen (Reet, Rishav, Swetha) and a grad student (Yuwei) to join me team, I also setup some github webhooks for our discord server. This is a neat QoL thing I adopted from Sam Ruggerio, which makes code visibility in team projects a lot easier.

It also involved reading the HARCOM docs and exploring what the language is capable of. Another important order of work was understanding the new scoring framework, VFS.

Prior CBP championships scored on a single metric, the misprediction rate while constrained by a storage budget. Voltage-Frequency-Scaled Speedup combines three figures of merit: IPC (prediction throughput), CPI (cycles lost per instruction due to mispredictions), and EPI (energy the predictor burns per predicted instruction). These feed into a formula that asks how much speedup is possible if we clocked this predictor at the voltage and frequency needed to match a reference power budget. The intuition here is that a predictor using less power than the reference can be clocked faster. Conversely, one burning more power has to be clocked slower. So energy and latency aren’t second-class citizens anymore. A predictor that reduces mispredictions by 5% but doubles energy consumption can actually lose VFS.

VFS isocontours showing importance of throughput and power alongside misprediction penalty
VFS isocontours showing importance of throughput and power alongside misprediction penalty

One of my initial goals was to reimplement the baseline TAGE predictor in a highly parameterizable way, with the idea that a parameter sweep could brute-force its way toward a better configuration. I figured a “monkey on a typewriter eventually writes Shakespeare” strategy would more than compensate for my inexperience.

To that end, I started messing around with OOP in HARCOM to build a templated TageTable<> class. This object wraps HARCOM’s ram type and exposes parameters like usefulness bits, hysteresis, prediction counter width, confidence, table size, and capacity. Since HARCOM doesn’t support arrays of objects, I used an IIFE pattern to construct a tuple of table instances instead.

In hindsight, this was premature. A behavioral model in Python would have been much faster to instrument and iterate on at this stage. HARCOM is essential once you care about layout, timing, and power, but it adds friction in the early exploration phase when you’re just trying to prune the design space.

Feb 2026

In February, I started working on a uArch monitor that attaches to the TageTable, alongside the parameterizable TAGE model itself. This instantiates each table, the monitor and the glue that deals with entry allocation, eviction, issuing of predictions and more. Basically the predictor part of the branch predictor. It took me a bit longer than I would have liked to build an intuition for multi-entry predictors (i.e superscalar, >1 prediction per cycle). Almost all of the literature I encountered hand waves this part away, which i am NOT a big fan of.

Oh also, the reasoning behind building the monitor first was to better understand what the performance of the predictor on different traces actually looked like. The impacts on eviction, allocation, history utilization, patterns, etc. Having those numbers would help quantitatively tune the design parameters and help me debug where needed. I actually ended up finding a pretty huge bug in the design after the submission deadline, that I wouldn’t have caught without the presence of a monitor.

My teammates also helped set up a more scalable build pipeline. Yuwei wired up a YAML based build flow, so every set of parameters/configurations became a data object instead of a hand-edited Makefile/build command. This sounds like a small thing, but it was really helpful for the large parameter sweeps we ran later.

I also started messing around with agentic coding workflows around this time, which is probably where the way I program changed the most. Finally wrote my first few skill files haha.

Yuwei designed a perceptron predictor, Reet worked on notes around ahead prediction, and Rishav worked on enhancements to the trace reader tooling. We were still figuring out what direction to go in, so lots of literature review, trying to understand how to actually beat the baseline. It was around now that we came across this ISCA'25 paper that became the key inspiration for the work we did.

Mar 2026

All the real work for the championship started this month. After completing and somewhat verifying (subtle foreshadowing ;) ) my parameterized TAGE, I setup a gaussian ascent script that would use an agent to optimize and tune the parameters of the branch predictor. This ended up going all the way up from 0.55 to 0.88 VFS (for reference, the example TAGE was at 0.87 and the best example was at 0.96 VFS). Beating 0.9 VFS required considerable effort and intuition. I could get the effort part down but intuition needs time which I didn’t have.

I modeled some metric changes on this TAGE (which takes 2 cycles to issue predictions), and found that dropping the delay ceiling from 2 to 1 is worth roughly +0.077 VFS. This was much more than the modeled VFS gain by improving the accuracy (lowering MPKI) by adding a SC/L component. The SC/L would increase power, latency and the MPKI improvements would wholly depend on my implementation skill. This results from this experiment convinced me that ahead pipelining the predictor was the most rewarding direction to explore given the time constraints.

So I started writing an “AheadTAGE”. Getting it to actually work was really hard. My first attempt (my commit message being “sorta works”) had ~8× more mispredictions than the direct version because of bugs to do with reading stale data from pipeline registers.

Towards the end of the month, the panic really started to sink in. I was having a hard time getting even close to Pierre Michaud’s example ahead-pipelined Gshare at 0.96 VFS. If I can’t outperform an example, how do I even qualify for the championship?

Apr 2026

The implementation effort for the ahead-pipelined TAGE was not moving as fast as I would have liked it to. I (my agents) kept making mistakes that I was unable to catch. I started phase out the use of agents for writing code and started hand-writing HARCOM to reduce the number of errors encountered. One of the particularly annoying bugs that took forever to find was that the hash assigned to a new entry on allocation was computed from a value read from the wrong pipeline stage. So all the secondary-tags were holding wrong hashes, and would rarely match when issuing predictions (aliasing). I saw a very consistently low rate of predictions issued from the TAGE across all traces. On a more thorough check, I found that this was primarily attributed to secondary-tag mismatches, which helped me identify the source of the error.

During literature review, I identified a few more features like gating silent writes which I implemented. This saves some energy on SRAM activations during writes where the value doesn’t change..

Another major pain point for me was fanout auditing. Improper fanout accounting would add a linear delay to my signals as opposed to logarithmic. So I wrote some helper python scripts to do some ref counting for my C++ that ended up made it easier to do fanout counting.

Once I had an ahead-pipelined implementation I was not disgusted with, I started running many parameter sweeps. One of parameters was associative banking, where I tried using two-way banked entries so the predictor could pick the right prediction when multiple branches could be the next one executed, but it didn’t really complement the secondary-tag based approach. And I didn’t feel like I had the time to revert a feature that took up a considerable amount of time to try this other idea.

I also fooled around with varying levels of aggresison for an LSFR based decay policy. Entries that miss w.r.t primary or secondary tags had a random chance of being aged. This mechanism over time, would expose more candidates for eviction, instead of discrete epochs, that require entire SRAMs to be reset.

May 2026

Submissions were due on May 8 so finishing up my design and writing a paper were the top goals for me this month. Nothing else mattered (well they did but I tried to delegate as much of my school work to my project partners) (W teammates).

Till now my predictor floorplanned its RAMs in declaration order, which isn’t great for wire length. I rewrote it, and got rid of all the parameterizations. This fully unrolled predictor had explicit placement zones between each table’s read cluster and write cluster. The goal was to get the fold registers physically adjacent to the RAMs they index and keep the timing-critical read path away from the non-critical write wiring. (What I thought was good floorplanning was actually quite poor, more on this later).

I also increased the banking on the smaller RAMs (hysteresis, usefulness, prediction) to 8-way. This was overly aggressive in hindsight, but helped improve VFS at that point by reducing the SRAM activation energy. Additionally, the write buffer for each RAM holds only one entry at a time, so when consecutive accesses to the same block all land on the same bank, writes get overwritten before they flush. Increasing the number of banks led to fewer lost training writes which improved accuracy by a smidge. Another “hack” I applied was
splitting the fallback bimodal into a full-size prediction table and a half-size hysteresis table. This saved about 3KB of SRAM.

Writing the paper was REALLY REALLY REALLY hard. Readers - heed my advice - do not defer writing a paper to the least 2 days. Writing is difficult. And annoying.

I had to condense the last 4 months of madness into a coherent writeup that answered all the obvious questions/concerns. Other than starting early, my other piece of advice to people would be to have some sort of excel sheet that tracks parameters and performance. Do not rely on your commit history. Had i recorded information and results in a more organized manner, making my ablation tables and figures would have been much easier.

Same information, but the graph on the R.H.S does not overwhelm the reader
Same information, but the graph on the R.H.S does not overwhelm the reader

Another thing I realized is, picking the right graph/figure/visualization is a really good skill to develop. The right graph can really help convey what you want to say, and having a poor graph can totally derail the reader.

/projects/cbp-ng/old_pipeline.png /projects/cbp-ng/new_pipeline_1.png /projects/cbp-ng/new_pipeline_2.png

Splitting a figure and simplifying it can make it so much easier to read!

While on this topic, I really want to thank the program committee to endure reading my first draft. It was terrible. My second “camera-ready” paper should be much better.

Wrapping it up

I think I will need to do a separate blog post that actually explains how my branch predictor works and what’s interesting about it. This one really just serves to document the effort on my part. I will probably need another post about my experience at the actual workshop as well. There will be some more about me working on some tooling/harness optimizations for the simulator framework.

And some more because I have ideas and fixes for improvements to my predictor (just to keep you guys interested, I found a bug that costed me 0.05 VFS for my championship submission).

Wish I had infinite time and energy to do blog posts. Letting such a backlog buildup and not doing a devlog/diary was such a L for me.

I’ll keep any new posts tagged with cbp-ng so they should be easy to find!

Email me if you have any thoughts or questions!