<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Notebook · Fabio Baravalle</title><link>https://commontypes.dev/posts/</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>Mon, 28 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://commontypes.dev/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>Ventriloquist: how the processor is put together</title><link>https://commontypes.dev/posts/ventriloquist-how-the-processor-is-put-together/</link><pubDate>Mon, 28 Sep 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://commontypes.dev/posts/ventriloquist-how-the-processor-is-put-together/</guid><description>The core of the protocol-emulator chip exists and has been through the layout tools. How four threads ended up sharing one datapath, what it cost, and what the tools found.</description><category>asic</category><category>tiny-tapeout</category><category>rust</category><category>hardware</category><content:encoded><![CDATA[<p>In the first post I described the processor as a barrel with four threads, one instruction each every four cycles, and said I wasn&rsquo;t sure all four would fit in the chip. Now it&rsquo;s built and has been through the layout tools on the IHP process several times, so I have real sizes and real timing instead of guesses, and along the way I changed my mind about how the threads are stored, which is the part I actually want to write about. This is the whole chip as it stands, boxes only:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">   host, over SPI
</span></span><span class="line"><span class="cl">        |
</span></span><span class="line"><span class="cl">        v
</span></span><span class="line"><span class="cl">  +---------------+   next word   +-----------------+          +------------------+
</span></span><span class="line"><span class="cl">  | program store |--------------&gt;|                 |  writes  |  8 timed pins    |
</span></span><span class="line"><span class="cl">  |  (1 KB SRAM)  |               |    the one      |---------&gt;|  8 plain pins    |---&gt; 24 pins
</span></span><span class="line"><span class="cl">  +---------------+               |    datapath     |&lt;---------|  (each pin cell  |&lt;---
</span></span><span class="line"><span class="cl">                                  |                 |  edges   |   has its own    |
</span></span><span class="line"><span class="cl">  +-----------------------------+ |                 |          |   little clock)  |
</span></span><span class="line"><span class="cl">  | T3 | T2 | T1 | T0 &lt;- serving  |&lt;+               |          +------------------+
</span></span><span class="line"><span class="cl">  +-----------------------------+ +-----------------+                  ^
</span></span><span class="line"><span class="cl">     four copies of the state,                                         |
</span></span><span class="line"><span class="cl">     turning one step per tick               one shared 24-bit tick counter
</span></span></code></pre></div><p>The first number was not great. A single thread came out at 7020 standard cells placed and routed, 10.6% of the tile, against the 2000 I had budgeted, so three and a half times off, which for a first estimate is about what I deserved. The plan had a fallback for exactly this (&ldquo;drop to 2 or 3 threads&rdquo;) and for a day or two I thought I&rsquo;d be using it. I didn&rsquo;t, because those cells are logic, and in a barrel the logic exists once: the threads take turns on it, one instruction each per 20 ns tick, so each one issues every 80 ns whatever the others are doing. What has to exist four times is only the state a thread carries between turns, program counter, cursor, delay table, two shift registers, x, y, the CRC and some flags, 410 bits. I knew that when I wrote the plan, I just hadn&rsquo;t measured how lopsided logic and state were, and it turns out very.</p>
<p>What I hadn&rsquo;t thought about at all is how the datapath gets at the right thread&rsquo;s state on each tick. The way I&rsquo;d assumed, without ever writing it down, is four copies of every register and a multiplexer on every wire the datapath reads. A few gates per bit, no big deal, except they land at the start of the slowest paths in the design, from the delay table into the compare against the counter, so they eat straight into the margin at 50 MHz. The alternative is to not select at all: make every register a ring of four, let the datapath read and write position 0 only, and shift the ring one position every tick. A thread&rsquo;s state gets written at the end of its turn, goes round the back over the next three ticks and is at position 0 again exactly when its turn comes round.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">tick n      position 0    position 1    position 2    position 3
</span></span><span class="line"><span class="cl">            [ T0 state ]  [ T1 state ]  [ T2 state ]  [ T3 state ]
</span></span><span class="line"><span class="cl">                 ^
</span></span><span class="line"><span class="cl">            the datapath reads and writes here, and only here
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">tick n+1    [ T1 state ]  [ T2 state ]  [ T3 state ]  [ T0 state ]
</span></span><span class="line"><span class="cl">                                                       (updated, on
</span></span><span class="line"><span class="cl">                                                        its way round)
</span></span></code></pre></div><p>At four threads the multiplexer version synthesises to 9491 cells and the ring to 6371 (the synthesis tool&rsquo;s own count, before layout roughly doubles it), and each thread you add to the ring costs 915 of those, most of which are the 410 flops themselves. It&rsquo;s not a new trick, but I was quite pleased with it for an afternoon.</p>
<p>The ring isn&rsquo;t free though, and it was the layout tool that told me, not me. Each bit is a flip-flop feeding the next with no logic in between, and a flop needs its input to stay put for a moment after the clock edge (the hold time), so the tool inserts delay buffers on those paths, about 3000 of them, an eighth of the standard-cell area, still cheaper than the multiplexers. It does mean every bit I add to a thread is a flop, a buffer and wiring, times four, so the 410 bits are frozen now, and I&rsquo;ve already caught myself twice wanting one more.</p>
<p>The program is in a 1 KB SRAM macro, 512 words of 16 bits, and its clock-to-output is 6.61 ns out of the 20 ns tick at the slow corner (the tools&rsquo; worst case for process, voltage and temperature), too much to decode in what&rsquo;s left, so a thread&rsquo;s word is fetched two ticks before its turn and held in a register. With the ring, who is up in two ticks is whoever sits at position 2. The macro has never been on silicon in this process (nobody has taped it out on this particular flavour of the IHP node yet, as far as I can tell), so there&rsquo;s a fallback that feeds the program a byte at a time over the pins, at a third of the speed. I built it, tested it, and I would very much like to never use it.</p>
<p>Timing didn&rsquo;t change since the first post, which was a relief: one 24-bit counter for the whole chip, a cursor per thread, and a write to a pin is a level plus a tick (<code>cursor + D[k]</code>) handed to the pin cell, which applies it when the counter gets there. The open question was how many pending writes a pin cell needs to buffer. It&rsquo;s one, and the argument is short enough that I like it: a thread can post a write at most four ticks before it&rsquo;s due and doesn&rsquo;t get another turn for four ticks, so the first write has gone out before a second can be posted. That needs one thread per pin, so the host assigns pins before it starts anything and a write from the wrong thread is refused and flagged.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">tick        0     1     2     3     4     5     6     7     8
</span></span><span class="line"><span class="cl">T0&#39;s turn   *                       *                       *
</span></span><span class="line"><span class="cl">            |                       |
</span></span><span class="line"><span class="cl">            posts &#34;pin high at 3&#34;   posts &#34;pin low at 7&#34;
</span></span><span class="line"><span class="cl">                              |                       |
</span></span><span class="line"><span class="cl">pin cell                      high                    low
</span></span></code></pre></div><p>Before the barrel existed I ran four copies of the single-thread design through the flow together, to see whether that much logic would route at all. It routed, and missed 50 MHz by 0.42 ns at the slow corner, and I spent a while blaming the design before it turned out to be the tool: the step that resizes gates was working with a wire capacitance from the process kit that is about half of what the finished layout measures, so it saw no problem and did nothing. With that figure scaled by 2.1 the same design closed at +0.62 ns. The real chip, four threads, the SRAM, sixteen protocol pins and the SPI link, closes at +1.94 ns, fills 44.2% of the tile, and ran a UART transmitter and an I2C EEPROM on two threads at once on the gate-level netlist, the circuit as it comes out of layout.</p>
<p>In the first post I said I might change my mind about having no arithmetic once I had numbers, and the device side looked like the place it would happen: an I2C target has to check whether the address on the bus is its own, and nothing in the ISA compares two values. There is <code>wait</code> on a pin with a timeout of zero, then <code>jmp</code> on the timeout flag, which tests one bit as it arrives, so the address check is unrolled, four words per bit, and the ISA stayed as it was. Whether a small ALU is worth its silicon is a study I&rsquo;ve scheduled for later on, same rule as before: nothing goes in unless a protocol can&rsquo;t be written without it. I keep going back and forth on this one.</p>
<p>The thing I left out of the first post is in the pin cells: a chain of delay gates on the input pin, each sampled by a flip-flop at the clock edge, so counting how many have switched tells you how far into the tick the edge arrived. I was fairly sure the tools would look at a chain of buffers doing nothing and helpfully remove it; they didn&rsquo;t, and 44 taps of two gates each cover the 20 ns tick at 0.735 ns per tap. More on that once I&rsquo;ve calibrated it. As before, if you have opinions I&rsquo;d like to hear them.</p>
]]></content:encoded></item><item><title>Ventriloquist: 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 (the file with the actual layout, the one that goes to the foundry) 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>
Now there is a follow-up, <a href="https://blog.janestreet.com/protocol-emulator-asic-competition/" rel="noopener noreferrer">&ldquo;Can you design a chip?&rdquo;</a>
:they want a protocol emulator (or that&rsquo;s what I understood): a small chip with a CPU in it, with an instruction set made for reading and writing pins, counting cycles and getting the timing right, so you can do UART, SPI and I2C in firmware instead of fixed logic. Those three are the minimum, low-speed USB and 10 Mbit Ethernet are stretch goals. They also suggest JTAG, SWD, PS/2 and CAN as other ones worth looking at and it should be able to run protocols nobody had in mind when it was designed. It gets made through <a href="https://tinytapeout.com/" rel="noopener noreferrer">Tiny Tapeout</a>
 on IHP&rsquo;s 130 nm process, 24 tiles, which by their numbers is about 0.7 mm² and roughly 24 thousand logic cells. The Tiny Tapeout harness gives you 24 pins and a clock, which I am planning to run at 50 MHz, though so far the only thing I have shown will actually run that fast is a counter. Deadline is 18 January 2027, it has to be open source and they care about how you verify it as much as what it does. I&rsquo;m going to give it a try, worst case the chip never gets fabricated. I decided to call the project Ventriloquist, since it is a chip that makes other devices talk.</p>
<p>I think the biggest problem is that a normal CPU cannot promise when a pin will change. You can get close with a tight loop that flips the pin and counts cycles, with everything else on the chip switched off so nothing interrupts it. People have done it for decades and it works but while that loop runs the processor does nothing else, so one protocol at a time and it breaks as soon as anything else needs attention. This is why microcontrollers have a separate little circuit for each protocol and the processor just hands them bytes. The competition says that this is not the point. Sure, sometimes you can trick one of those circuits into speaking something it was not designed for: the LED strips in every hobby project are usually driven by a circuit meant for a different protocol that happens to produce similar pulses. But it only works when the new protocol looks like the old one. If it looks like none of them there is nothing to trick.</p>
<p>What I am planning to do is to move the timing out of the CPU and into the pins. Every program has a time cursor on a 24-bit counter shared by every thread 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 at that moment. 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; instead of &ldquo;the line is low&rdquo;. So the CPU can be late, it doesn&rsquo;t matter, the pin cell has the time. If it is so late that the tick has already passed, for example because it was blocked waiting for the host or for a pin, the write goes out immediately and the pin cell raises a missed-deadline signal, one per pin. How the program gets to see it I have not decided yet. The counter wraps every 335 ms at 50 MHz, which is longer than any protocol timeout and the compares are done so that wrapping doesn&rsquo;t matter. The CPU is a barrel processor: four hardware threads take turns on one datapath, one instruction each, so every thread gets an instruction every four cycles, 12.5 million a second and one thread cannot slow down another. Four is the plan but I&rsquo;m not sure they all fit in the chip. Fewer threads means more instructions per thread, so all the numbers below only get better. The host talks to the chip over SPI to load programs and move bytes in and out. How many pins that leaves for protocols is still open.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">thread, one instruction every 4 ticks        pin cell                       pin
</span></span><span class="line"><span class="cl">                                             
</span></span><span class="line"><span class="cl">  at +1 set pin 0 low  -------------------&gt;  holds &#34;low at tick 8213&#34;
</span></span><span class="line"><span class="cl">                                             ... counter reaches 8213 ---&gt;  goes low
</span></span><span class="line"><span class="cl">  wait rise pin 0      &lt;-------------------  &#34;rose at tick 8201&#34;    &lt;---  edge seen
</span></span></code></pre></div><p>For the instruction set I started from what a program actually needs to say for each of the protocols I care about and it turned out to be quite little. 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 of eight. Why a table and not a number in the instruction: after the prefix and a 4-bit opcode there are 9 bits left and delays go from a few cycles up to 1.25 million cycles for a 25 ms bus timeout, which is 21 bits, so they were never going to fit. The state of a thread is the cursor, that delay table, an outgoing and an incoming shift register, two small counters called x and y, a CRC register and a few flags. That is what <code>mov</code> copies between.</p>
<table>
	<thead>
			<tr>
					<th>group</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 shift register on one or more pins</td>
			</tr>
			<tr>
					<td></td>
					<td><code>in</code></td>
					<td>read one or more pins into the incoming shift register</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 where the pin only ever pulls low and a resistor pulls it up, which is what I2C needs, or 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. <code>pull</code> loads the outgoing shift register from the host FIFO and <code>out</code> shifts bits from it onto a pin. <code>at +1</code> means &ldquo;one bit period after the cursor&rdquo;. Here that is the same as one period after the previous edge because every instruction in the chain has it, except <code>at +1 nop</code>, which moves the cursor without any edge. An instruction with no prefix at all is <code>at +0</code>, and <code>D[0]</code> is always zero, so its effect lands on the cursor and leaves it there:</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: jmp x-- runs the body 8 times
</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">; pin 0, one bit, one 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, but I might change my mind on some of that once I have real numbers for what things cost. 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 an instruction gets in only when a protocol cannot be written without it. <code>sync ev</code> is there because UART receive has to sample relative to the real edge of the start bit and 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. By the way, four threads times 128 words of 16 bits is exactly one of the 1 KB SRAM macros the process offers.</p>
<p><code>crc</code> is the one I went back and forth on. CAN and USB need a checksum in the middle of a frame, so the host cannot do it. Doing it in software would need an <code>xor</code>, a shift that doesn&rsquo;t touch the pins, a branch on a single bit and a third register for the polynomial. That is more silicon than the CRC unit itself and it would take about ten instructions per bit instead of one. At 1.5 Mbit/s low-speed USB a thread gets eight instructions per bit, so the software version does not fit and the unit stays.</p>
<p>Since I&rsquo;m counting instructions per bit, here are the numbers for the required protocols. UART at 115200 gets about a hundred instructions per bit and I2C at 400 kHz about thirty, so no problem there. SPI is the tight one. One bit of an SPI master looks like this:</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">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">; clock low
</span></span></span><span class="line"><span class="cl">        <span class="nf">out</span> <span class="mi">1</span> <span class="mi">1</span>              <span class="c1">; data bit on pin 1
</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">; clock high, the other side samples here
</span></span></span><span class="line"><span class="cl">        <span class="nf">in</span> <span class="mi">2</span> <span class="mi">1</span>               <span class="c1">; read its bit back
</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></code></pre></div><p>Five instructions, so with a thread issuing 12.5 million a second the clock tops out around 2.5 MHz, a bit more if you don&rsquo;t need to read anything back. Enough for most SPI devices but not all of them. If that becomes a problem the fix is a side-set field, so that <code>out</code> can move the clock and the data in one instruction. 10 Mbit Ethernet is out of reach for the core at this clock, less than two instructions per bit, so its CRC-32 stays on the host if Ethernet happens at all.</p>
<p>None of the pieces are 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 had timed ports for years (their <code>out port @ time</code> is almost exactly the cursor idea) and the Propeller 2 has smart pins. What none of them have is all of this together with the protocol written as an automaton that a model checker has looked at before it runs and that is the part I am interested in.</p>
<p>The part I actually want to play with is how the protocols get written. Instead of assembly I want a small DSL where you say things like 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 gets lowered to the instructions above, but the same automaton can also go to a model checker. So before a protocol ever runs I can ask if it can deadlock, if every wait is bounded, if the I2C master always lets go of the bus even when the slave stretches the clock and then stops answering and I get an answer for every possible interleaving instead of the handful my tests happen to hit.</p>
<p>I&rsquo;m trying to do the same on the hardware side. The chip is not hand-written Verilog but a Rust program that builds the circuit and writes out the Verilog for the tools and a simulator for the tests, from the same description. Then I use <a href="https://github.com/YosysHQ/sby" rel="noopener noreferrer">SymbiYosys</a>
, a formal verification tool for Verilog, to prove the few properties everything else depends on instead of checking them on a handful of test programs. The main one is that if an instruction asks for a pin change at tick N, the pin changes at tick N, whichever turn the thread got, as long as the thread is not stuck waiting. A thread can get stuck in a <code>wait</code> on a pin, in a <code>pull</code> on the host or in an <code>irq</code> on another thread. The first case is what the model checker takes care of on the automaton side when it proves every wait is bounded. The other two are outside the automaton, so there is a rule for programs: block on the host or on another thread only when no deadline is pending, like the UART program does with its <code>pull</code> before the <code>sync</code>. With that rule the two proofs together cover the whole path from the protocol description to the pin. There is also one thing in the pin cells I have not talked about here, because I don&rsquo;t know yet if the tools will let me build it. I will write about it when I have numbers.</p>
<p>They also say that verification matters more as AI-assisted chip design becomes common. I agree, so I am using more than one LLM (a combination of Claude Opus and OAI Astra) for two jobs, drafting properties and planting bugs in the Verilog. Proving a drafted property does not tell me it was worth stating, a weak or vacuous one proves just fine, that is what the planted bugs are for. If the property set does not catch them it is not doing its job. I will report which of the planted bugs got through.</p>
<p>More as it goes. If you have opinions, especially on the formal side, I would like to hear them. The next post, <a href="/posts/ventriloquist-how-the-processor-is-put-together/">how the processor is put together</a>
, has the measured numbers for the core.</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>