<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Asic · Fabio Baravalle</title><link>https://commontypes.dev/tags/asic/</link><description>Notes from the hardware/software boundary: systems programming, silicon, Rust, and the things that break in between.</description><generator>Hugo</generator><language>en</language><copyright>CC BY 4.0 — Fabio Baravalle</copyright><lastBuildDate>Fri, 18 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://commontypes.dev/tags/asic/index.xml" rel="self" type="application/rss+xml"/><item><title>Can you design a chip?</title><link>https://commontypes.dev/posts/ventriloquist-can-you-design-a-chip/</link><pubDate>Fri, 18 Sep 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://commontypes.dev/posts/ventriloquist-can-you-design-a-chip/</guid><description>Jane Street is running an ASIC design competition. Notes on what I am building for it.</description><category>asic</category><category>tiny-tapeout</category><category>rust</category><category>hardware</category><content:encoded><![CDATA[<p>Recently I spent some time on a Jane Street challenge, <a href="https://blog.janestreet.com/can-you-reverse-engineer-an-asic/" rel="noopener noreferrer">&ldquo;Can you reverse engineer an ASIC?&rdquo;</a>
. You get the GDS of a chip, and you have to work out what the circuit does and simulate it to find the input that makes it say success. I found it very interesting and it brought back things I thought I had lost a long time ago.<br>
The follow-up is <a href="https://blog.janestreet.com/protocol-emulator-asic-competition/" rel="noopener noreferrer">&ldquo;Can you design a chip?&rdquo;</a>
, if your design get selected, they will fabricate it.</p>
<p>From what I understood they want a protocol emulator: a small chip with a tiny CPU inside, with an instruction set made for reading and writing pins, counting cycles and precise timing, so that UART, SPI and I2C can be done in firmware rather than fixed logic. Those three are the minimum, low-speed USB and 10 Mbit Ethernet are stretch goals and it may have to run protocols nobody thought about when it was designed. It goes through <a href="https://tinytapeout.com/" rel="noopener noreferrer">Tiny Tapeout</a>
 on IHP&rsquo;s 130 nm process, about 0.9 mm², roughly 24 thousand logic cells, 24 pins, 50 MHz. Deadline 18 January 2027, open source, and they care about how you verify it as much as what it does.</p>
<p>I will give it a try, worst case scenario the chip will not be printed.</p>
<p>I think the biggest problem is that a normal CPU cannot say when a pin will change: a loop that toggles a pin every 434 cycles will sometimes do it at 435 or 437, because an interrupt came in or a cache missed, and on a serial line a late edge is a wrong byte. That is why every microcontroller has one hardware peripheral per protocol, which is exactly what the competition says is not the point: UART, SPI and I2C blocks on one die would not be able to speak a protocol invented after the chip was made.</p>
<p>What I am doing is moving the timing out of the CPU and into the pins. Every program has a time cursor on a counter shared by the chip, and any instruction can say &ldquo;execute at cursor plus this delay&rdquo;. The CPU does not touch the pins, it passes the write to a pin cell together with the tick it should happen at, and the pin cell applies it at that tick whatever the CPU is doing. On the input side the pin cell records the time of every edge, so the CPU sees &ldquo;the line went low at tick 8201&rdquo; rather than &ldquo;the line is low&rdquo;. The CPU can be late, the timing is decided at the pin. The CPU is a barrel processor, a few hardware threads taking turns on one datapath, so one cannot slow down another.</p>
<p>The instruction set came out after thinking about what a program actually needs to say for each of the protocols I care about, and it turned out to be quite minimal. It has 13 instructions in 16-bit words, each with a 3-bit <code>at</code> prefix that selects a delay from a per-thread table.</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>instruction</th>
					<th>what it does</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>time</td>
					<td><code>nop</code></td>
					<td>nothing, so with the <code>at</code> prefix it is a timed delay</td>
			</tr>
			<tr>
					<td></td>
					<td><code>sync</code></td>
					<td>move the cursor to now</td>
			</tr>
			<tr>
					<td></td>
					<td><code>sync ev</code></td>
					<td>move the cursor to the timestamp of the last edge on a pin</td>
			</tr>
			<tr>
					<td></td>
					<td><code>wait</code></td>
					<td>wait for a level or an edge, with a mandatory timeout</td>
			</tr>
			<tr>
					<td>pins</td>
					<td><code>out</code></td>
					<td>put the next bits of the outgoing byte on one or more pins</td>
			</tr>
			<tr>
					<td></td>
					<td><code>in</code></td>
					<td>read one or more pins into the incoming byte</td>
			</tr>
			<tr>
					<td></td>
					<td><code>set</code></td>
					<td>load a constant: a pin high or low, a pin&rsquo;s drive mode (push-pull, open-drain, off), or a loop counter</td>
			</tr>
			<tr>
					<td>data</td>
					<td><code>mov</code></td>
					<td>copy between registers</td>
			</tr>
			<tr>
					<td></td>
					<td><code>jmp</code></td>
					<td>jump, always or on a condition, including decrement-and-branch</td>
			</tr>
			<tr>
					<td></td>
					<td><code>crc</code></td>
					<td>feed the bits just shifted into a serial CRC</td>
			</tr>
			<tr>
					<td>host</td>
					<td><code>push</code></td>
					<td>send a word to the host FIFO</td>
			</tr>
			<tr>
					<td></td>
					<td><code>pull</code></td>
					<td>take a word from the host FIFO</td>
			</tr>
			<tr>
					<td>threads</td>
					<td><code>irq</code></td>
					<td>set, clear or wait on a flag shared between threads</td>
			</tr>
	</tbody>
</table>
<p>This is the UART transmitter, the whole program. <code>D[1]</code> is the bit period, which the host sets, and <code>at +1</code> means &ldquo;one bit period after the previous edge&rdquo;:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-asm" data-lang="asm"><span class="line"><span class="cl">        <span class="nf">sync</span>                 <span class="c1">; cursor := now
</span></span></span><span class="line"><span class="cl">        <span class="nf">set</span> <span class="no">pin</span> <span class="mi">0</span> <span class="no">high</span>       <span class="c1">; line idles high
</span></span></span><span class="line"><span class="cl">        <span class="nf">set</span> <span class="no">dir</span> <span class="mi">0</span> <span class="no">pushpull</span>
</span></span><span class="line"><span class="cl"><span class="nl">tx:</span>
</span></span><span class="line"><span class="cl">        <span class="nf">pull</span>                 <span class="c1">; wait for a byte from the host
</span></span></span><span class="line"><span class="cl">        <span class="nf">set</span> <span class="no">x</span> <span class="mi">7</span>              <span class="c1">; 8 data bits
</span></span></span><span class="line"><span class="cl">        <span class="nf">sync</span>
</span></span><span class="line"><span class="cl">        <span class="nf">at</span> <span class="err">+</span><span class="mi">1</span> <span class="no">set</span> <span class="no">pin</span> <span class="mi">0</span> <span class="no">low</span>  <span class="c1">; start bit
</span></span></span><span class="line"><span class="cl"><span class="nl">bit:</span>
</span></span><span class="line"><span class="cl">        <span class="nf">at</span> <span class="err">+</span><span class="mi">1</span> <span class="no">out</span> <span class="mi">0</span> <span class="mi">1</span>        <span class="c1">; one data bit per period, LSB first
</span></span></span><span class="line"><span class="cl">        <span class="nf">jmp</span> <span class="no">x--</span> <span class="no">bit</span>
</span></span><span class="line"><span class="cl">        <span class="nf">at</span> <span class="err">+</span><span class="mi">1</span> <span class="no">set</span> <span class="no">pin</span> <span class="mi">0</span> <span class="no">high</span> <span class="c1">; stop bit
</span></span></span><span class="line"><span class="cl">        <span class="nf">at</span> <span class="err">+</span><span class="mi">1</span> <span class="no">nop</span>            <span class="c1">; hold it a full period
</span></span></span><span class="line"><span class="cl">        <span class="nf">jmp</span> <span class="no">tx</span>
</span></span></code></pre></div><p>There is no arithmetic apart from decrement-and-branch, no memory access, no call and return. A protocol program does not compute anything, it moves bits between pins and shift registers at the right moments, so what it needs is a way to say when. Every instruction costs cells, so one gets in only when a protocol cannot be written without it. <code>crc</code> is there because CAN and USB need a checksum in the middle of a frame. <code>sync ev</code> is there because UART receive has to sample relative to the start bit&rsquo;s real edge, not relative to when the program noticed it. Call and return is not there because I2C, the most awkward of the required protocols, fits in 124 of the 128 words a thread can address without it.</p>
<p>None of this is new. The PIO blocks in the RP2040 do the same job, <a href="https://ptolemy.berkeley.edu/projects/chess/pubs/1048.html" rel="noopener noreferrer">FlexPRET</a>
 is a barrel processor with deadline instructions, XMOS has timed ports and the Propeller 2 has smart pins.</p>
<p>What I want to try is to build the protocols in a small DSL where you say wait for the clock to go high, then after this delay set the data pin, and so on. That compiles to a timed automaton (<a href="https://doi.org/10.1016/0304-3975%2894%2990010-8" rel="noopener noreferrer">Alur and Dill, 1994</a>
), which is lowered to the instructions above and also given to a model checker. So before running a protocol I can check that it cannot deadlock, that every wait is bounded, that the I2C master always releases the bus even if the slave stretches the clock and then stops responding, over every interleaving rather than the ones my tests hit. The hardware follows the same idea: the chip is written in Rust with a small netlist builder that emits Verilog, and the guarantees the design rests on, for example that when an instruction says &ldquo;change this pin at tick N&rdquo; the pin changes at tick N and not at N-1 or N+1 whatever turn the thread happened to get, are proved with SymbiYosys for every case rather than checked on a few test programs. Since they are curious about how people would use LLMs, I am also using one (likely Claude Opus or OpenAI Astra) to draft properties and inject bugs into the RTL, with the rule that nothing it writes is trusted until proved, and I will report which bugs got through.</p>
<p>More as it goes. If you have opinions, especially on the formal side, I would like to hear them.</p>
<hr>
<p>References: Zimmer, Broman, Shaver and Lee, <a href="https://ptolemy.berkeley.edu/projects/chess/pubs/1048.html" rel="noopener noreferrer">FlexPRET</a>
, RTAS 2014. Edwards and Lee, <a href="https://www.cs.columbia.edu/~sedwards/papers/edwards2007case.pdf" rel="noopener noreferrer">The Case for the Precision Timed (PRET) Machine</a>
, DAC 2007. David May, <a href="https://docs.alexrp.com/xcore/xmos_xs1.pdf" rel="noopener noreferrer">The XMOS XS1 Architecture</a>
, 2009. <a href="https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf" rel="noopener noreferrer">RP2040 datasheet</a>
, chapter 3. Alur and Dill, <a href="https://doi.org/10.1016/0304-3975%2894%2990010-8" rel="noopener noreferrer">A theory of timed automata</a>
, 1994. <a href="https://tinytapeout.com/" rel="noopener noreferrer">Tiny Tapeout</a>
, <a href="https://github.com/IHP-GmbH/IHP-Open-PDK" rel="noopener noreferrer">IHP Open PDK</a>
.</p>
]]></content:encoded></item></channel></rss>