<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://cowardsa.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://cowardsa.github.io/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-08-10T09:08:49+00:00</updated><id>https://cowardsa.github.io/feed.xml</id><title type="html">Samuel Coward</title><subtitle>University College London
</subtitle><entry><title type="html">A CIRCT Project Tutorial</title><link href="https://cowardsa.github.io/blog/2026/demo/" rel="alternate" type="text/html" title="A CIRCT Project Tutorial" /><published>2026-06-30T15:12:00+00:00</published><updated>2026-06-30T15:12:00+00:00</updated><id>https://cowardsa.github.io/blog/2026/demo</id><content type="html" xml:base="https://cowardsa.github.io/blog/2026/demo/"><![CDATA[<h2 id="arith-tutorial-day">ARITH Tutorial Day</h2>
<p>Just back from <a href="todo">ARITH 2026</a> in Fulda, Germany, where they introduced a new <a href="todo">Tutorial Day</a>! 
Together, Louis and I, gave the attendees first a brief introduction to <a href="todo">MLIR</a> and <a href="todo">CIRCT</a>, then got them working hands-on building, optimizing and verifying ASIC hardware designs.<br />
If you want to have a go at home, all the tutorial materials can be found in our <a href="todo">public repo</a>.
To play through the tutorial, you’ll need to use either a docker build or the VScode dev-containers extension. 
The goal was to give researchers and industrial engineers a short teaser of the various CIRCT capabilities, people seemed to find it interesting at least, and potentially even useful.<br />
In this blog post I’ll give a short description of CIRCT and describe the features we presented (that way you can decide if you should care or not…).</p>

<h2 id="what-is-circt">What is CIRCT?</h2>
<p><a href="https://circt.llvm.org/">CIRCT (Circuit IR Compilers and Tools)</a> is an open-source compiler infrastructure project for digital hardware design.
It provides reusable compiler dialects, optimizations, and passes built on MLIR.
CIRCT enables hardware designers and compiler developers to express, transform, and generate circuit representations for FPGAs, ASICs, and other digital systems.</p>

<h3 id="key-features">Key Features:</h3>
<ul>
  <li>A hardware-centered IR and dialect ecosystem for digital circuit design.</li>
  <li>Reusable passes for lowering, verification, and code generation.</li>
  <li>Integration with <a href="https://mlir.llvm.org/">MLIR</a> and LLVM for compiler-based hardware flows.</li>
  <li>Support for multiple input formats and backends, including FIRRTL, HW, and system-level representations.</li>
</ul>

<h3 id="circt-inspiration">CIRCT Inspiration</h3>
<ul>
  <li>LLVM and <a href="https://mlir.llvm.org/">MLIR</a>: using compiler infrastructure patterns to make hardware transformation passes composable.</li>
  <li>Hardware description languages like <a href="https://www.chisel-lang.org/">Chisel</a>.</li>
  <li>The desire to unify hardware and software compiler techniques in a shared framework.</li>
</ul>

<p>CIRCT aims to make hardware compiler development more agile, enabling researchers and engineers to experiment with new optimizations and hardware dialects.</p>

<h2 id="tutorial-content">Tutorial Content</h2>
<p>The tutorial introduces a range of different tools and optimization passes that can be used to synthesize and verify combinational circuit designs expressed in System Verilog. 
First you’ll compile a Verilog design with <code class="language-plaintext highlighter-rouge">circt-verilog</code>, producing a snippet of CIRCT IR, that will look a lot like LLVM/MLIR if you’ve ever worked in these software compiler frameworks.</p>
<pre><code class="language-mlir">hw.module @fma(in %a: i4, in %b: i4, in %c: i4, out d: i9){
  %c0_i5 = hw.constant 0 : i5
  %0 = comb.concat %c0_i5, %a : i5, i4
  %1 = comb.concat %c0_i5, %b : i5, i4
  %2 = comb.mul %0, %1 : i9
  %3 = comb.concat %c0_i5, %c : i5, i4
  %4 = comb.add %2, %3 : i9
  hw.output %4 : i9
}
</code></pre>
<p>Given some IR, we then move onto optimization which we automate with passes, that literally pass over the IR and make small incremental changes (usually until some fixed point is reached).
CIRCT has loads of passes built in, and you apply them using <code class="language-plaintext highlighter-rouge">circt-opt</code>. 
For those wanting to extend CIRCT, a great starting point is trying to extend an existing pass with a new optimization.</p>

<p>Since most ASIC designers, are inherently sceptical (and rightly so), we then describe how one can verify the correctness of the transformation you just applied using <code class="language-plaintext highlighter-rouge">circt-lec</code>. 
The <code class="language-plaintext highlighter-rouge">circt-lec</code> tool takes two IR modules and checks that they are <em>logically equivalent</em> by formulating an SMT problem, which can be externally validated with your favourite SMT solver (Z3, Bitwuzla etc).
Lastly, for those wanting to integrate into an existing EDA flow, we learn how to generate Verilog using <code class="language-plaintext highlighter-rouge">firtool</code>.</p>

<p>In the second part of the tutorial, we focused more on my work developing a datapath synthesis engine for CIRCT.
In this part, we introduced <code class="language-plaintext highlighter-rouge">circt-synth</code> that leverages redundant number representations to produce realy efficient datapath netlists that can outperform the much more mature <a href="todo">Yosys synthesis tool</a>.
To give a final flavour of what can be done when combining MLIR and CIRCT, Louis demoed his high-level synthesis flow from Python code to a full chip layout.</p>

<p>Whilst preparaing for the tutorial, I discovered markdown slides in Obsidian, which is why the README is a little terse!</p>

<h2 id="arith-2026-summary">ARITH 2026 Summary</h2>
<p>In addition to the tutorials, ARITH is the main venue for research into computer arithmetic. Below is a list of my favourite papers from the conference (all papers are freely available from the <a href="https://www.arith2026.org/program.html">ARITH program</a>):</p>
<ul>
  <li><a href="https://www.arith2026.org/papers/A%20Conflict-Aware%20Learning%20Approach%20to%20SCA%20Verification%20for%20MAC%20Architectures.pdf">A Conflict-Aware Learning Approach to SCA Verification for MAC Architectures</a>
    <ul>
      <li>Hopeful that this might provide a way to verify the outputs of <code class="language-plaintext highlighter-rouge">circt-synth</code> much faster!</li>
    </ul>
  </li>
  <li><a href="https://www.arith2026.org/papers/Novel%20Aspects%20of%20IEEE%20SA%20P3109%20Arithmetic%20Formats%20for%20Machine%20Learning.pdf">Novel Aspects of IEEE SA P3109 Arithmetic Formats for Machine Learning</a>
    <ul>
      <li>Describing all the details behind the upcoming IEEE standard for AI number formats</li>
    </ul>
  </li>
  <li><a href="https://www.arith2026.org/papers/Efficacy%20of%20Pipelining%20to%20Reduce%20Energy%20of%20Floating-Point%20Adders%20and%20Multipliers.pdf">Efficacy of Pipelining to Reduce Energy of Floating-Point Adders and Multipliers</a>
    <ul>
      <li>An awesome way to visualize the switching and glitch frequency as a function of logical depth</li>
    </ul>
  </li>
</ul>

<p>Not available online but Erin Carson’s keynote was also great and raised lots of interesting questions!</p>

<h2 id="thanks">Thanks</h2>
<p>Huge thanks must go to Anastasia Volkova and Theo Drane who put a lot of effort into organizing both the tutorial day and a demo session during the conference. 
Martin Kumm and the Fulda team also organized a fantastic conference overall, good culture, good food and good beer the whole way through! 
Louis Ledoux was a great collaborator on the demo and provided a lot of insights into how far you can take HW design with CIRCT.</p>

<div class="row mt-3">
    <div class="col-sm mt-3 mt-md-0">
        <figure>

  <picture>
    
    <source class="responsive-img-srcset" media="(max-width: 480px)" srcset="/assets/img/arith_demo_1.JPG-480.webp" />
    <source class="responsive-img-srcset" media="(max-width: 800px)" srcset="/assets/img/arith_demo_1.JPG-800.webp" />
    <source class="responsive-img-srcset" media="(max-width: 1400px)" srcset="/assets/img/arith_demo_1.JPG-1400.webp" />
    

    <!-- Fallback to the original file -->
    <img src="/assets/img/arith_demo_1.JPG" class="img-fluid rounded z-depth-1" width="auto" height="auto" onerror="this.onerror=null; $('.responsive-img-srcset').remove();" />

  </picture>

</figure>

    </div>
    <div class="col-sm mt-3 mt-md-0">
        <figure>

  <picture>
    
    <source class="responsive-img-srcset" media="(max-width: 480px)" srcset="/assets/img/arith_demo_2.JPG-480.webp" />
    <source class="responsive-img-srcset" media="(max-width: 800px)" srcset="/assets/img/arith_demo_2.JPG-800.webp" />
    <source class="responsive-img-srcset" media="(max-width: 1400px)" srcset="/assets/img/arith_demo_2.JPG-1400.webp" />
    

    <!-- Fallback to the original file -->
    <img src="/assets/img/arith_demo_2.JPG" class="img-fluid rounded z-depth-1" width="auto" height="auto" onerror="this.onerror=null; $('.responsive-img-srcset').remove();" />

  </picture>

</figure>

    </div>
    <div class="col-sm mt-3 mt-md-0">
        <figure>

  <picture>
    
    <source class="responsive-img-srcset" media="(max-width: 480px)" srcset="/assets/img/fulda-480.webp" />
    <source class="responsive-img-srcset" media="(max-width: 800px)" srcset="/assets/img/fulda-800.webp" />
    <source class="responsive-img-srcset" media="(max-width: 1400px)" srcset="/assets/img/fulda-1400.webp" />
    

    <!-- Fallback to the original file -->
    <img src="/assets/img/fulda.jpg" class="img-fluid rounded z-depth-1" width="auto" height="auto" onerror="this.onerror=null; $('.responsive-img-srcset').remove();" />

  </picture>

</figure>

    </div>
</div>
<div class="caption">
    Desperately trying to convince industry folks that they should care and Fulda Cathedral
</div>]]></content><author><name>Samuel Coward</name></author><category term="optimization" /><summary type="html"><![CDATA[My ARITH 2026 tutorial and demo hopefully convinced a few folks to try building hardware with CIRCT!]]></summary></entry><entry><title type="html">Teaching Year 10 How CPUs Work</title><link href="https://cowardsa.github.io/blog/2026/cpus/" rel="alternate" type="text/html" title="Teaching Year 10 How CPUs Work" /><published>2026-05-17T15:12:00+00:00</published><updated>2026-05-17T15:12:00+00:00</updated><id>https://cowardsa.github.io/blog/2026/cpus</id><content type="html" xml:base="https://cowardsa.github.io/blog/2026/cpus/"><![CDATA[<h2 id="warming-up">Warming Up</h2>

<p>The opportunity came about through the <a href="https://royalcommission1851.org/">1851 Royal Commission</a>, who support the London Wonder outreach initiative led by the <a href="https://www.big-ideas.org/">Big Ideas</a> charity.
They let me take charge of a 2 hour class, containing 20 year 10 students (usually 14 or 15 years old).
Along for the ride we had one Big Ideas co-ordinator, Hayley, and my colleague from UCL <a href="https://ralphs16.github.io/">Ralph Sarkis</a>.</p>

<p>To start with I shared a little background about how I got into computer chips and why they should care about them (5 mins).
Before they got too bored we did a quick 3 question quiz that got them debating with each other.</p>

<ol>
  <li>Best selling phone ever? (iPhone 6 or Nokia 1100)</li>
  <li>What did Nintendo originally make? (Playing cards or Welly Boots)</li>
  <li>What has more computing power? (iPhone 15 or Apollo 11)</li>
</ol>

<p>To settle back down, we then did a quick run through of how the CPU is like the computer’s brain that runs on some form of clock (5 mins). This led us nicely into the activity!</p>

<h2 id="the-interactive-activity">The Interactive Activity</h2>
<p>How do you make CPUs fun? The advice from Big Ideas was it has got to be interactive but not cost a fortune…</p>

<p>My solution, students would form teams of 5, each working for one of the big chip design companies, and nominate one student to be a CPU.
The remaining team members would then write out instructions on sticky notes at each clock cycle and hand them to their CPU to execute. 
Of course, CPUs can only execute specific instructions, so I devised a custom instruction set architecture (ISA). 
Specifically, the goal was to produce a bottle of salty squash from a starting bottle containing just water.
Their goal was to get as close, in terms of squash colour and salanity, to the target bottle which we showed them right at the start then hid away, in as few cycles as possible.</p>

<p>So what instructions was the CPU allowed to execute:</p>

<ol>
  <li>Collect ?? ml/grams of squash/salt [2 cycles]</li>
  <li>Remove/Replace bottle lid [1 cycle]</li>
  <li>Add squash/salt [1 cycle]</li>
  <li>Shake bottle [2 cycles]</li>
  <li>Test colour/salinity [2 cycles]</li>
  <li>Submit [1 cycle]</li>
</ol>

<div class="row mt-3">
    <div class="col-sm mt-3 mt-md-0">
        <figure>

  <picture>
    
    <source class="responsive-img-srcset" media="(max-width: 480px)" srcset="/assets/img/LondonWonder1-480.webp" />
    <source class="responsive-img-srcset" media="(max-width: 800px)" srcset="/assets/img/LondonWonder1-800.webp" />
    <source class="responsive-img-srcset" media="(max-width: 1400px)" srcset="/assets/img/LondonWonder1-1400.webp" />
    

    <!-- Fallback to the original file -->
    <img src="/assets/img/LondonWonder1.jpg" class="img-fluid rounded z-depth-1" width="auto" height="auto" onerror="this.onerror=null; $('.responsive-img-srcset').remove();" />

  </picture>

</figure>

    </div>
</div>
<div class="caption">
    Ralph frantically measuring salt and squash to fulfill the CPU's requests. 
</div>

<p>Once all teams had execute their submit instruction they were ranked from 1st to 4th in three categories.</p>

<ul>
  <li>Number of cycles</li>
  <li>Squash accuracy (as judged by their teacher)</li>
  <li>Salt accuracy (measured using a refractometer)</li>
</ul>

<div class="row mt-3">
    <div class="col-sm mt-3 mt-md-0">
        <figure>

  <picture>
    
    <source class="responsive-img-srcset" media="(max-width: 480px)" srcset="/assets/img/LondonWonder4-480.webp" />
    <source class="responsive-img-srcset" media="(max-width: 800px)" srcset="/assets/img/LondonWonder4-800.webp" />
    <source class="responsive-img-srcset" media="(max-width: 1400px)" srcset="/assets/img/LondonWonder4-1400.webp" />
    

    <!-- Fallback to the original file -->
    <img src="/assets/img/LondonWonder4.jpg" class="img-fluid rounded z-depth-1" width="auto" height="auto" onerror="this.onerror=null; $('.responsive-img-srcset').remove();" />

  </picture>

</figure>

    </div>
    <div class="col-sm mt-3 mt-md-0">
        <figure>

  <picture>
    
    <source class="responsive-img-srcset" media="(max-width: 480px)" srcset="/assets/img/LondonWonder2-480.webp" />
    <source class="responsive-img-srcset" media="(max-width: 800px)" srcset="/assets/img/LondonWonder2-800.webp" />
    <source class="responsive-img-srcset" media="(max-width: 1400px)" srcset="/assets/img/LondonWonder2-1400.webp" />
    

    <!-- Fallback to the original file -->
    <img src="/assets/img/LondonWonder2.jpg" class="img-fluid rounded z-depth-1" width="auto" height="auto" onerror="this.onerror=null; $('.responsive-img-srcset').remove();" />

  </picture>

</figure>

    </div>
</div>
<div class="caption">
    Judging of bottle squash colour and final scores. 
</div>

<p>This worked rather nicely. Some teams didn’t spend cycles testing their bottle, letting them submit their result much faster. Whilst other teams did spend cycles testing and refining the mixture in their bottles to get a more accurate result. This gave them a bit of a demonstration of the typical accuracy vs performance tradeoffs we experience all the time when designing code/chips.</p>

<p>The great thing about this setup is that we can make a natural progression, just as university students do when they are taught about these concepts. 
In round 2, teams elected two CPUs and had to produce two bottles of salty squash. This gave them a chance to appreciate parallelism, where they could write instructions for the second CPU whilst their first was busy. 
In round 3 (which we ran out of time for), we move to compilation, where students need to write out all their instructions ahead of time.</p>

<h2 id="the-good">The Good</h2>
<ul>
  <li>Competition: making it competitive seemed to make the students very invested</li>
  <li>Getting their teacher involved: I asked their teacher to judge the squash rankings which proved a source of contention and lots of laughter, my apologies to setting the teacher involved to be like a football referee</li>
  <li>Chaos: the activity was chaotic but perhaps just the right level, the vast majority of students seemed very engaged</li>
</ul>

<h2 id="the-bad">The Bad</h2>
<ul>
  <li>Keeping track of the clock: many teams weren’t quite in sync - a new mechanism rather than me calling out clock cycles is needed</li>
  <li>Dispensing: distributing the salt and the squash was time consuming - having set amounts pre-measured would have helped greatly</li>
</ul>

<h2 id="conclusions">Conclusions</h2>
<p>I was really worried this wouldn’t work, but was shocked when the students really engaged. My favourite student comment overheard by Hayley, “I thought this was going to be dead, but it was actually pretty lit”. Not every student engaged, but at least 15 out of the 20 got really into the activity and seemed to take away something from it. So it was far from perfect, but with some refinement I think the core concept is pretty ideal! Huge thanks to Ralph, Hayley and Sam from Big Ideas who helped make it happen.</p>

<p>You can find the slides <a href="/assets/CPU%20Outreach%20Activity.pptx">here</a> in case any teachers or engineers want to try out the activity in a classroom.</p>]]></content><author><name>Samuel Coward</name></author><category term="outreach" /><summary type="html"><![CDATA[I recently led a 2 hour class for twenty 14 year olds and this is what I taught them]]></summary></entry><entry><title type="html">verifying EDA and compiler optimizations once and for all</title><link href="https://cowardsa.github.io/blog/2026/parabit/" rel="alternate" type="text/html" title="verifying EDA and compiler optimizations once and for all" /><published>2026-05-17T15:12:00+00:00</published><updated>2026-05-17T15:12:00+00:00</updated><id>https://cowardsa.github.io/blog/2026/parabit</id><content type="html" xml:base="https://cowardsa.github.io/blog/2026/parabit/"><![CDATA[<h2 id="eda-optimizations">EDA Optimizations</h2>

<!-- TODO: Introduce the problem of rewrite correctness in EDA/synthesis tools.
     Motivate why parametric bitvector equivalences are hard to prove automatically.
     Summarise the key contributions of ParaBit:
       1. BWLang representation
       2. Equality saturation-based solver
       3. Isabelle proof certificates
       4. Evaluation on hardware/software benchmarks
-->
<p>More often than not the goal of electronic design automation (EDA) tools is to optimize a given circuit design.
One of the key mechanisms they use is rewriting, namely searching for a smaller circuit within our given design that matches some specific strucutre and replacing it with a more efficient implementation.
A classic example is replacing <code class="language-plaintext highlighter-rouge">x*2</code> by <code class="language-plaintext highlighter-rouge">x &lt;&lt; 1</code>, as the shift circuit is much cheaper than a multiplication.</p>

<p>It is imperative that these rewrites, which are baked into the EDA tools source code, are correct. 
If the EDA tool contains an incorrect rewrite, it can silently introduce bugs into the circuit design it’s optimizing. 
Hardware bugs can be extremely costly so are best avoided otherwise you may have very unhappy tool users. 
What makes this problem tricky, is that EDA rewrites often manipulate bitvectors of parametric widths, meaning a rewrite can apply to 16-bit or 32-bit bitvectors as well as all the widths in-between.</p>

<p>Like all good researchers, the solution is a new solver called ParaBit, that automatically proves the correctness of the rewrites.
ParaBit first encodes the problem in a custom language then turns to equality saturation to construct a proof. 
We’re not the first to tackle this problem.
<a href="https://drops.dagstuhl.de/storage/00lipics/lipics-vol341-sat2025/LIPIcs.SAT.2025.4/LIPIcs.SAT.2025.4.pdf">Zvika Berger and the CVC5 team</a> made some great progress with the PBV project on these kind of rewrite verification problems, providing an admittedly more general purpose solution than us.
Our work differs from theirs, in that they attach bitwidths to operators whereas we attach bitwidths to operands removing the need for excessive extension operations. 
This lets us solve a good chunk more problems as we’ll see later on.</p>

<p>In July 2026, Luigi will be presenting this work at CAV, but further details <a href="/assets/pdf/Parabit.pdf">can be found in the version we submitted</a> (official version will be linked when published and in this version we’ll have proofs for all the axioms).</p>

<h2 id="an-example">An Example</h2>
<p>Much of the motivation for this project came from my own frustrations in developing EDA tool rewrites for the <a href="https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=9974492">Rover</a> project. We’ll take a ROVER rewrite as an example.</p>

\[(q\geq r) \Rightarrow
    \underbrace{(a_p + (b_p + c_p)_q)_r}_{\textrm{lhs}} \equiv \underbrace{((a_p + b_p)_q + c_p)_r}_{\textrm{rhs}} \hspace{0.5cm}\]

<p>This rewrite says that when our width parameters satisfy \(q\geq r\), we can safely replace the lhs by the rhs. Our goal is to prove that this is correct for all possible width parameters. We use the subscript notation to denote the width of a bitvector, namely \(a_p\) denotes a \(p\)-bit bitvector. Similarly, \((b_p + c_p)_q\) means we add up two \(p\)-bit bitvectors and store the result in a \(q\)-bit bitvector.</p>

<p>To construct a proof we’ll be composing axioms of modular arithmetic (which is what our notation is really a shorthand for). Crucial for this proof is the following, where we totally remove the inner width \(u\).</p>

\[u\geq v \Rightarrow (x + y_u)_v \equiv (x + y)_v.\]

<p>This essentially says that when I add up two bitvectors, if I store the result in a width smaller than one of the addends, then it doesn’t matter what the width of the inner addend is. It’ll just get truncated after the addition.</p>

<p>Under the given assumption \(q\geq r\), this axiom applies to both the lhs and rhs of the rewrite:</p>

\[lhs \equiv (a_p + (b_p + c_p)_q)_r \equiv  %\text{\{~using \eqref{eqn:mod_arith_axiom} with condition $q\geq r$~\}} \\
(a_p + (b_p + c_p))_r \\\]

\[rhs \equiv ((a_p + b_p)_q + c_p)_r \equiv ((a_p + b_p) + c_p)_r\]

<p>Having removed any dependencies on \(q\), the two resulting expressions can now be shown equivalent by associativity of integer addition. 
This constitutes a series of axiom applications (which we separately verify) that proves the correctness of the rewrite whenever it is applied.</p>

<p>Whilst this looks simple, when we get more complex rewrites and have more axioms to combine finding this proof becomes much more tricky. 
This is where we turn to <a href="https://en.wikipedia.org/wiki/E-graph">e-graphs and equality saturation</a>, a constructive rewriting procedure where the order in which axioms are applied is not important. We can see how the e-graph encodes the steps that were written out here.</p>

<div class="row mt-3">
    <div class="col-sm mt-3 mt-md-0">
        <figure>

  <picture>
    
    <source class="responsive-img-srcset" media="(max-width: 480px)" srcset="/assets/img/start-480.webp" />
    <source class="responsive-img-srcset" media="(max-width: 800px)" srcset="/assets/img/start-800.webp" />
    <source class="responsive-img-srcset" media="(max-width: 1400px)" srcset="/assets/img/start-1400.webp" />
    

    <!-- Fallback to the original file -->
    <img src="/assets/img/start.jpg" class="img-fluid rounded z-depth-1" width="auto" height="auto" data-zoomable="" onerror="this.onerror=null; $('.responsive-img-srcset').remove();" />

  </picture>

</figure>

    </div>
    <div class="col-sm mt-3 mt-md-0">
        <figure>

  <picture>
    
    <source class="responsive-img-srcset" media="(max-width: 480px)" srcset="/assets/img/mid-480.webp" />
    <source class="responsive-img-srcset" media="(max-width: 800px)" srcset="/assets/img/mid-800.webp" />
    <source class="responsive-img-srcset" media="(max-width: 1400px)" srcset="/assets/img/mid-1400.webp" />
    

    <!-- Fallback to the original file -->
    <img src="/assets/img/mid.jpg" class="img-fluid rounded z-depth-1" width="auto" height="auto" data-zoomable="" onerror="this.onerror=null; $('.responsive-img-srcset').remove();" />

  </picture>

</figure>

    </div>
    <div class="col-sm mt-3 mt-md-0">
        <figure>

  <picture>
    
    <source class="responsive-img-srcset" media="(max-width: 480px)" srcset="/assets/img/end-480.webp" />
    <source class="responsive-img-srcset" media="(max-width: 800px)" srcset="/assets/img/end-800.webp" />
    <source class="responsive-img-srcset" media="(max-width: 1400px)" srcset="/assets/img/end-1400.webp" />
    

    <!-- Fallback to the original file -->
    <img src="/assets/img/end.jpg" class="img-fluid rounded z-depth-1" width="auto" height="auto" data-zoomable="" onerror="this.onerror=null; $('.responsive-img-srcset').remove();" />

  </picture>

</figure>

    </div>
</div>
<div class="caption">
    Starting from the left ParaBit encodes the initial problem in an e-graph, which initially has only one node per e-class. Then ParaBit applies the axiom described to add new terms to the e-graph (as shown by the newly added nodes in the top e-classes). Lastly after applying the associativity axiom, the lhs and rhs e-classes get merged thus discovering a sequence of axioms that form a proof. 
</div>

<h2 id="the-parabit-solver">The ParaBit Solver</h2>

<p><a href="https://github.com/luigirinaldi/parabit">ParaBit</a> is implemented in roughly 2k lines of Rust and is built on-top of the <a href="https://github.com/egraphs-good/egg">egg equality saturation library</a>. ParaBit contains 99 axioms, <a href="https://github.com/luigirinaldi/parabit/tree/main/proofs">each of which is verified in the Isabelle theorem prover</a>. ParaBit takes problems specified in our custom BWLang language and discovers a sequence of axioms that prove the correctness of the rewrite using equality saturation.</p>

<p>If ParaBit is able to construct a proof, then ParaBit generates an associated Isabelle certificate script that can be checked independently to verify that our ParaBit proof is valid. 
This gives users an extra level of confidence in ParaBit’s proofs, so hopefully some industry folks might be willing to try it out!</p>

<p>To test out whether ParaBit is any good we gathered a couple of benchmark sets. Two from the software compilers community (<a href="https://github.com/AliveToolkit/alive2">Alive</a> and <a href="https://dl.acm.org/doi/10.1145/3649837">Hydra</a>) and two from the hardware domain (<a href="https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=9974492">Rover</a> and Industry). We compared against the PBV work from the CVC5 team referenced earlier. We see that ParaBit isn’t able to handle quite as many problems as PBV, due to current language limitations, but on the supported subset ParaBit is much more capable across all benchmarks.</p>

<div class="row mt-3">
    <div class="col-sm mt-3 mt-md-0">
        <figure>

  <picture>
    
    <source class="responsive-img-srcset" media="(max-width: 480px)" srcset="/assets/img/parabit_eval-480.webp" />
    <source class="responsive-img-srcset" media="(max-width: 800px)" srcset="/assets/img/parabit_eval-800.webp" />
    <source class="responsive-img-srcset" media="(max-width: 1400px)" srcset="/assets/img/parabit_eval-1400.webp" />
    

    <!-- Fallback to the original file -->
    <img src="/assets/img/parabit_eval.png" class="img-fluid rounded z-depth-1" width="auto" height="auto" data-zoomable="" onerror="this.onerror=null; $('.responsive-img-srcset').remove();" />

  </picture>

</figure>

    </div>
</div>
<div class="caption">
    Results table comparing PBV and ParaBit across four benchmark suites. We separate the benchmarks into single-width (those rewrites containing only a single width parameter) and multiple-width (those rewrites containing several width parameters) subsets. 
</div>

<p>Out of the 144 problems solved by ParaBit, we were able to successfully verify 142 of the generated Isabelle certificates. For two problems the proof production step actually ran out of memory. When ParaBit fails to produce a proof it is usually because the equality saturation step either times out or hit a memory limit.</p>

<p>There is clearly further work to be done to ParaBit to handle a larger class of problems and to improve performance across the problem set. The <a href="/assets/pdf/Parabit.pdf">submitted version of the paper</a> (official version will be linked when published) contains far more details and Luigi, John and I will all be at CAV in Lisbon in July 2026, so do come along if you’ve got any questions! If any of this work sounds interesting and you’re looking to collaborate or for Masters projects then get in touch.</p>]]></content><author><name>Luigi Rinaldi</name></author><category term="verification" /><summary type="html"><![CDATA[At CAV 2026 we'll be presenting our new solver called ParaBit, which proves the correctness of EDA and compiler transformations using the theory of parametric bitvectors.]]></summary></entry><entry><title type="html">Are Datapath Circuit Transformations just Compiler Optimisations?</title><link href="https://cowardsa.github.io/blog/2026/compilers/" rel="alternate" type="text/html" title="Are Datapath Circuit Transformations just Compiler Optimisations?" /><published>2026-04-17T15:12:00+00:00</published><updated>2026-04-17T15:12:00+00:00</updated><id>https://cowardsa.github.io/blog/2026/compilers</id><content type="html" xml:base="https://cowardsa.github.io/blog/2026/compilers/"><![CDATA[<p>During a visit to Cornell, I presented a bunch of hardware optimisations to fuse arithmetic operators. Being from a compiler background, Nate asked whether we could view these as more traditional compiler transformations.</p>

<p>TLDR: Yes, you can, but it gets pretty ugly…</p>

<p>In this blog, we’re going to show how we answered this question for a specific example, namely discovering carry-save addition (a hardware optimisation) via loop fusion (a compiler optimisation).</p>

<h2 id="carry-save-addition">Carry-Save Addition</h2>
<p>Consider the problem of adding up three n-bit bitvectors <code class="language-plaintext highlighter-rouge">(x+y+z)</code>. Assuming we know how to add two bitvectors, the simplest solution is to add <code class="language-plaintext highlighter-rouge">x</code> and <code class="language-plaintext highlighter-rouge">y</code>, then add <code class="language-plaintext highlighter-rouge">z</code> to the result. However, each time we add two bitvectors, we have to propagate the carries all the way from the least-significant to the most significant bit.</p>

<p>The time to perform this computation unfortunately scales at least logarithmically with the width of the bitvectors. In hardware we can go much faster and compute <code class="language-plaintext highlighter-rouge">(x+y+z)</code> in the same time as <code class="language-plaintext highlighter-rouge">(x+y)</code> plus a small constant overhead. To achieve this, we use a full-adder which takes three individual bits of equal weight (<code class="language-plaintext highlighter-rouge">x[i], y[i], z[i]</code>) and produces two bits <code class="language-plaintext highlighter-rouge">s</code> and <code class="language-plaintext highlighter-rouge">c</code> such that: <code class="language-plaintext highlighter-rouge">2*c + s = x[i] + y[i] + z[i]</code>. In terms of gates:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>c = MAJ(x[i], y[i], z[i])
  = (x[i] AND y[i]) XOR (x[i] AND z[i]) XOR (y[i] AND z[i])
s = x[i] XOR y[i] XOR z[i]
</code></pre></div></div>

<p>Using n of these full-adders operating in parallel on bits of equal weight, we can take our three addends and reduce them down to two. Consider this example:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  1010 = 10 (x)
  1011 = 11 (y)
 +0011 =  3 (z)
 ----------
  0010 =  2
+10110 = 22
 ----------
 11000 = 24
</code></pre></div></div>
<p>Here we first reduced three values to two which takes constant time as we only look locally, so it is much faster than the complete addition. We of course do have to pay the price for the slow addition of two bitvectors once, but that’s much better than doing it twice sequentially. This technique is known as carry-save addition (and it generalises to an arbitrary number of addends).</p>

<h2 id="loop-fusion">Loop Fusion</h2>
<p>Now we’ll briefly describe a compiler concept, loop fusion, which we’ll later use to “rediscover” carry-save addition. Consider the following example containing two loops:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Loop 1: Square each element
</span><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
    <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">*</span> <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>

<span class="c1"># Loop 2: Add 1 to each element
</span><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
    <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span>
</code></pre></div></div>

<p>A classic compiler optimisation is to fuse these two loops together, so that you only iterate over <code class="language-plaintext highlighter-rouge">a</code> once:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
    <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">*</span> <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span>
</code></pre></div></div>

<p>This is more efficient because we only execute the loop once in the program and have also been able to fuse operations together to eliminate some loads/stores. Nate’s question was, can we use loop fusion as a lens through which we can discover carry-save addition?</p>

<h2 id="the-challenge">The Challenge</h2>
<p>Consider the following program fragment, which takes in three arrays of booleans <code class="language-plaintext highlighter-rouge">x</code>, <code class="language-plaintext highlighter-rouge">y</code>, and <code class="language-plaintext highlighter-rouge">z</code>, and produces an array of booleans <code class="language-plaintext highlighter-rouge">s</code> (<code class="language-plaintext highlighter-rouge">= x+y+z</code>):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Perform (x+y) - store result in w
</span><span class="n">a</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
<span class="err">    </span><span class="n">w</span><span class="p">[</span><span class="n">i</span><span class="p">],</span><span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nc">FullAdd</span><span class="p">(</span><span class="n">x</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">y</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
<span class="c1"># Perform (w+z) - store result in s
</span><span class="n">b</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
<span class="err">    </span><span class="n">s</span><span class="p">[</span><span class="n">i</span><span class="p">],</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nc">FullAdd</span><span class="p">(</span><span class="n">w</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">z</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
</code></pre></div></div>

<p>It should be easy to see that this program adds its three bitvector inputs together using two separate additions that are performed sequentially. We’ve implemented bitvector addition as a sequence of full-adders (<code class="language-plaintext highlighter-rouge">FullAdd</code>) each feeding their carry to the next full-adder. So the question is, what happens when we perform loop fusion?</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">a</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">b</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
<span class="err">    </span><span class="n">w</span><span class="p">[</span><span class="n">i</span><span class="p">],</span><span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nc">FullAdd</span><span class="p">(</span><span class="n">x</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">y</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
<span class="err">    </span><span class="n">s</span><span class="p">[</span><span class="n">i</span><span class="p">],</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nc">FullAdd</span><span class="p">(</span><span class="n">w</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">z</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
</code></pre></div></div>

<p>This isn’t that different (although we might save some memory if we executed this sequentially), but now consider a fused version of the adders with the carry-save trick:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">c</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">d</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
    <span class="n">w</span><span class="o">*</span><span class="p">[</span><span class="n">i</span><span class="p">],</span><span class="n">c</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nc">FullAdd</span><span class="p">(</span> <span class="n">x</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">y</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">z</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
    <span class="n">s</span><span class="o">*</span><span class="p">[</span><span class="n">i</span><span class="p">],</span><span class="n">d</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nc">FullAdd</span><span class="p">(</span><span class="n">w</span><span class="o">*</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">c</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">d</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
</code></pre></div></div>

<p>The two programs above at least <em>look</em> very similar; do they produce the same final output (does <code class="language-plaintext highlighter-rouge">s=s*</code>)? We know (from global analysis of the carry-save trick) that they do, but is there a <strong>local</strong> argument? That is, can we show that these two programs are equivalent without appealing to the actual <em>meaning</em> of <code class="language-plaintext highlighter-rouge">s</code> and <code class="language-plaintext highlighter-rouge">s*</code> as bitvectors, just by manipulating values? The answer is yes; we’ll do the proof next.</p>

<h2 id="the-proof">The Proof</h2>

<p>Let’s do some bit-level analysis. We’ll do all our analysis in $\mathbb{F}_2$ (so XOR will be addition, and AND will be multiplication). Note that <code class="language-plaintext highlighter-rouge">MAJ(x, y, z) = xy + yz + xz</code> (this is usually defined where + is OR, but XOR works just as well).</p>

<p>From the first program:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>s[i] = w[i] + z[i] + b[i]
     = x[i] + y[i] + a[i] + z[i] + b[i]
</code></pre></div></div>
<p>From the second program:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>s*[i] = w*[i] + c[i] + d[i]
      = x[i] + y[i] + z[i] + c[i] + d[i]
</code></pre></div></div>

<p>So we can see that these <code class="language-plaintext highlighter-rouge">s</code> and <code class="language-plaintext highlighter-rouge">s*</code> values are equivalent if <code class="language-plaintext highlighter-rouge">a[i] + b[i] = c[i] + d[i]</code>. We’re going to prove this by induction. The base case <code class="language-plaintext highlighter-rouge">a[0] + b[0] = c[0] + d[0]</code> is true since <code class="language-plaintext highlighter-rouge">a[0] = b[0] = c[0] = d[0] = 0</code>. For the inductive case we’ll assume <code class="language-plaintext highlighter-rouge">a[i] + b[i] = c[i] + d[i]</code>.</p>

<h3 id="step-1-ai1--bi1--ci1--majxi--yi--zi-ai-bi">Step 1: a[i+1] + b[i+1] = c[i+1] + MAJ(x[i] + y[i] + z[i], a[i], b[i])</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  a[i+1] + b[i+1]
  = MAJ(x[i], y[i], a[i]) xor MAJ(w[i], z[i], b[i])
  = MAJ(x[i], y[i], a[i]) xor MAJ(x[i] + y[i] + a[i], z[i], b[i])
</code></pre></div></div>

<p>For simplicity, let’s drop the <code class="language-plaintext highlighter-rouge">[i]</code> subscripts.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>= MAJ(x, y, a) + MAJ(x + y + a, z, b)
= xy + ya + xa + (x + y + a)z + zb + (x + y + a)b
= xy + ya + xa + xz + yz + az + zb + xb + yb + ab
= xy + xz + yz + (x + y + z)a + (x + y + z)b + ab
= MAJ(x, y, z) + MAJ(x + y + z, a, b)
= c[i+1]       + MAJ(x + y + z, a, b)
</code></pre></div></div>

<p>It remains to show that <code class="language-plaintext highlighter-rouge">d[i+1]=MAJ(x[i] + y[i] + z[i], a[i], b[i])</code>, which will be easy to show if we also prove by induction <code class="language-plaintext highlighter-rouge">a[i]b[i] = c[i]d[i]</code> (strengthening the inductive hypothesis).</p>

<h3 id="step-2-assuming-abcd-show-that-di1majx--y--z-a-b">Step 2: Assuming ab=cd show that d[i+1]=MAJ(x + y + z, a, b):</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>MAJ(x + y + z, a, b) = (x + y + z)a + ab + (x + y + z)b
                     = (x + y + z)(a + b) + ab
                     = (x + y + z)(c + d) + cd
                     = (x + y + z)c + (x + y + z)d + cd
                     = MAJ(x + y + z, c, d)
                     = MAJ(w*, c, d)
                     = d[i+1]
</code></pre></div></div>

<p>So lastly let’s prove <code class="language-plaintext highlighter-rouge">a[i]b[i] = c[i]d[i]</code> by induction as well. The base case again is trivial. The inductive case is where it gets annoying so strap in.</p>

<h3 id="step-3-assuming-abcd-show-that-ai1bi1ci1di1">Step 3: Assuming ab=cd show that a[i+1]b[i+1]=c[i+1]d[i+1]</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a[i+1]b[i+1] = MAJ(x, y, a)MAJ(w, z, b)
             = (xy + ya + xa)(wz + zb + wb)
             = xyzw + xyzb + xywb + 
               yzwa + yzab + ywab + 
               xzwa + xzab + xwab
</code></pre></div></div>
<p>Now we need to expand <code class="language-plaintext highlighter-rouge">w</code> everywhere, but that’ll be a lot of terms. To make it easier, note that in every term where <code class="language-plaintext highlighter-rouge">w</code> appears in the expression, it happens that exactly 2 of its components <code class="language-plaintext highlighter-rouge">{x, y, a}</code> appear as well, so you get something like <code class="language-plaintext highlighter-rouge">xyzw = xyzx + xyzy + xyza</code>. Two of these terms (where we used a component that already appears) are equivalent (since <code class="language-plaintext highlighter-rouge">xx=x</code> and <code class="language-plaintext highlighter-rouge">yy=y</code>) and cancel out. So basically the <code class="language-plaintext highlighter-rouge">w</code> just gets replaced by the one component that doesn’t already appear in the term:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>= xyza + xyzb + xyab + xyza + yzab + xyab + xyza + xzab + xyab
= xyzb + yzab + xyab + xyza + xzab
= xyz(a + b) + ab(xy + yz + xz)
= xyz(c + d) + cd(xy + yz + xz)
</code></pre></div></div>

<p>This looks pretty simple, so let’s turn our attention to <code class="language-plaintext highlighter-rouge">c[i+1]d[i+1]</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>c[i+1]d[i+1] = MAJ(x, y, z)MAJ(x + y + z, c, d)
             = (xy + yz + xz)(xc + yc + zc + cd + xd + yd + zd)
             = (xyzc + xycd + xyzd) + 
               (xyzc + yzcd + xyzd) + 
               (xyzc + xzcd + xyzd) 
</code></pre></div></div>

<p>Note that in that step, we neglected to write some things that immediately cancel out within individual sub-expressions, but there’s still more to cancel out between them:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>= xyzc + xyzd + xycd + yzcd + xzcd
= xyz(c + d) + cd(xy + yz + xz) = a[i+1]b[i+1]
</code></pre></div></div>

<p>This is pretty gnarly, but it means at least in principle what we wanted to show: a compiler which can fuse loops and then rewrite the loop body under this kind of inductive argument can automatically discover carry-save adders.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Obviously, we don’t need to rediscover the carry-save trick, since we already know it.</p>

<p>Rather, the hope is that other similar tricks can be discovered as well. In hardware, it’s easy to see how we can build a library of operator-level optimizations, and how we can build a library of bit-level optimizations for unrolled circuits, but compiler optimizations operate in a space in the middle, where the regularity inherent in the program must be taken advantage of to build global optimizations out of local transformations without unrolling. Hardware has regularity too, and we should perhaps be exploiting it so that we are not stuck with the other two choices of optimization.</p>]]></content><author><name>Nate Young</name></author><category term="optimization" /><summary type="html"><![CDATA[An investigation into whether carry-save arithmetic can be viewed as loop fusion.]]></summary></entry></feed>