<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>Felipe Balbi</title>
  <subtitle>Notes on embedded systems, Rust, and the craft of writing software.</subtitle>
  <link href="https://balbi.sh/atom.xml" rel="self" type="application/atom+xml"/>
  <link href="https://balbi.sh"/>
  <generator uri="https://www.getzola.org/">Zola</generator>
  <updated>2026-08-14T09:00:00+00:00</updated>
  <id>https://balbi.sh/atom.xml</id>
  
  <entry xml:lang="en">
    <title>Tamal: Everything, Wired</title>
    <published>2026-08-14T09:00:00+00:00</published>
    <updated>2026-08-14T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-top/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-top/</id>
    <summary type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-io/&quot;&gt;pads exist&lt;/a&gt; now, and they are wired to nothing.&lt;/p&gt;
&lt;p&gt;That is the odd position the series has arrived in. We have read a CRC unit, a
baud generator, a transmitter, a receiver and the umbrella over them; a COBS
codec and the loader that streams it; two block RAMs; an engine, in eight
instalments, from its eight-phase map down to the two bits of a lane; two pure
format models for the bytes that go in and the words that come out; and, last
week, the tri-state buffers that turn a &lt;code&gt;(value, enable)&lt;/code&gt; pair into a pin that
floats. Every piece is open. Not one of them is connected to another.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Tamal.Top&lt;/code&gt; connects them. It is a hundred and fifteen lines, and it contains
exactly one function that matters — &lt;code&gt;system&lt;/code&gt;, which takes a serial line and
some sampled bits and hands back a serial line, a bus drive and a blinking LED
— and four small pure helpers that exist so the parts of the wiring that could
have been logic are not hidden inside it. Reading it is less like reading a
module and more like reading a table of contents, because every import is a post.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-io/&quot;&gt;pads exist&lt;/a&gt; now, and they are wired to nothing.&lt;/p&gt;
&lt;p&gt;That is the odd position the series has arrived in. We have read a CRC unit, a
baud generator, a transmitter, a receiver and the umbrella over them; a COBS
codec and the loader that streams it; two block RAMs; an engine, in eight
instalments, from its eight-phase map down to the two bits of a lane; two pure
format models for the bytes that go in and the words that come out; and, last
week, the tri-state buffers that turn a &lt;code&gt;(value, enable)&lt;/code&gt; pair into a pin that
floats. Every piece is open. Not one of them is connected to another.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Tamal.Top&lt;/code&gt; connects them. It is a hundred and fifteen lines, and it contains
exactly one function that matters — &lt;code&gt;system&lt;/code&gt;, which takes a serial line and
some sampled bits and hands back a serial line, a bus drive and a blinking LED
— and four small pure helpers that exist so the parts of the wiring that could
have been logic are not hidden inside it. Reading it is less like reading a
module and more like reading a table of contents, because every import is a post.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;six-names-and-the-series-behind-them&quot;&gt;Six names, and the series behind them&lt;/h2&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Top&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; system&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; stepM&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ringWrite&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RigState&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rigState&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ledPattern&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Clash.Prelude&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Bus.Serdes&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Lanes&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Engine&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BusIn&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusOut&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; initState&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; step&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Loader&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;LoaderIn&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderOut&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; loader&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Mem&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;instrRam&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ringRam&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Params&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;RW&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Uart&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;uart&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Six exports, and the asymmetry between them is the design. One of the six is the
machine. The other five are &lt;em&gt;pure functions the machine happens to use&lt;/em&gt;, hoisted
to the door so they can be tested without a clock: &lt;code&gt;stepM&lt;/code&gt;, an adapter;
&lt;code&gt;ringWrite&lt;/code&gt;, a projection; and &lt;code&gt;RigState&lt;/code&gt; with &lt;code&gt;rigState&lt;/code&gt; and &lt;code&gt;ledPattern&lt;/code&gt;, a
three-state enum and the two total functions over it that decide how an LED
blinks. Nothing in that list computes anything the design needs at speed. They
are there because a wiring module that quietly grew logic inside it would be a
wiring module you could not check, and the author would rather export four small
things than hide them.&lt;/p&gt;
&lt;p&gt;The imports are the more striking half. Six modules, and every one of them has a
post: &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-serdes/&quot;&gt;&lt;code&gt;Serdes&lt;/code&gt;&lt;/a&gt; for the &lt;code&gt;Lanes&lt;/code&gt; type, &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;&lt;code&gt;Engine&lt;/code&gt;&lt;/a&gt; for the state
machine and the four record types that plug into it, &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;&lt;code&gt;Loader&lt;/code&gt;&lt;/a&gt; for the
frame FSM, &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;&lt;code&gt;Mem&lt;/code&gt;&lt;/a&gt; for the two block RAMs, &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;&lt;code&gt;Params&lt;/code&gt;&lt;/a&gt; for the &lt;code&gt;RW&lt;/code&gt;
address width, &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;&lt;code&gt;Uart&lt;/code&gt;&lt;/a&gt; for the serial umbrella. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape
post&lt;/a&gt; opened with eight closed boxes and called that &lt;em&gt;the exact inversion
of the leaves-first UART&lt;/em&gt; — a map drawn before any of its territories had been
walked. This import list is the same map with every box now open, and it is the
first module in the project whose header you cannot read without having read the
project.&lt;/p&gt;
&lt;h2 id=&quot;the-moment-the-engine-gets-its-clock&quot;&gt;The moment the engine gets its clock&lt;/h2&gt;
&lt;p&gt;The engine’s keystone type has been sitting still since the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape post&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;State&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A pure function. Give it a state and this cycle’s inputs; get back the next
state, the pins to drive, and maybe a trace word. Eight posts have described
what it computes, and in all eight it has never once run in time — it is a
transition, not a machine, and a transition needs something to iterate it.&lt;/p&gt;
&lt;p&gt;That something is &lt;code&gt;mealy&lt;/code&gt;, which the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt; introduced and the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;transmitter&lt;/a&gt; first put at the top of a module. Its type wants a
transition of a particular shape:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;mealy&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;HiddenClockResetEnable&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; NFDataX&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; i&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; o&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; s&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; i&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; o&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A pair. State and output, two elements. &lt;code&gt;step&lt;/code&gt; returns three, because the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape post&lt;/a&gt; argued at length that splitting the output into pins and
trace-word was worth doing — &lt;code&gt;BusOut&lt;/code&gt; is &lt;em&gt;what the wires carry right now&lt;/em&gt; and
&lt;code&gt;Maybe Ring&lt;/code&gt; is &lt;em&gt;a thing to remember&lt;/em&gt;, and conflating them would have been a
smaller type and a worse one. So the two shapes do not quite meet, and three
lines bridge them:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;stepM&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;State&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BusOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;stepM s i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;bo&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; mr&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; bo&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; mr&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; step s i&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One pair of parentheses moved. &lt;code&gt;(State, BusOut, Maybe Ring)&lt;/code&gt; becomes
&lt;code&gt;(State, (BusOut, Maybe Ring))&lt;/code&gt;, and now the second element is a single &lt;code&gt;o&lt;/code&gt; and
&lt;code&gt;mealy&lt;/code&gt; will take it. That is the entire function, and it is worth stopping on
precisely &lt;em&gt;because&lt;/em&gt; it is nothing, because the line that uses it is the most
consequential in the file:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;busOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; maybeRing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; unbundle &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mealy stepM initState busInS&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read that slowly. &lt;code&gt;mealy stepM initState&lt;/code&gt; takes the pure transition and gives
back a function from a &lt;code&gt;Signal&lt;/code&gt; of inputs to a &lt;code&gt;Signal&lt;/code&gt; of outputs: it allocates
the register that holds &lt;code&gt;State&lt;/code&gt;, initialises it to &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;&lt;code&gt;initState&lt;/code&gt;&lt;/a&gt;, and
arranges for the transition to be applied once per clock edge for the rest of the
design’s life. Eight posts of arithmetic acquire a clock in one application.
&lt;code&gt;unbundle&lt;/code&gt; then splits the paired output back into two signals, so the rest of
the module can use &lt;code&gt;busOut&lt;/code&gt; and &lt;code&gt;maybeRing&lt;/code&gt; separately — the pairing existed
only to satisfy &lt;code&gt;mealy&lt;/code&gt;, and it is undone the moment it has.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;step&lt;/code&gt; describes what happens next. &lt;code&gt;mealy stepM initState&lt;/code&gt; is what makes next
happen.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader&lt;/a&gt; was called &lt;em&gt;the first block that owns a clock&lt;/em&gt;, and it
earned the phrase: it had a &lt;code&gt;mealy&lt;/code&gt; at its top and eighteen fields of state
turning underneath. The engine is bigger than the loader by every measure —
seventeen state fields, eight phases, thirty-six opcodes — and it does not own
a clock at all. It never did. It borrows one here, in the top, on one line, and
that is the whole reason the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape post’s&lt;/a&gt; property tests could hammer
&lt;code&gt;step&lt;/code&gt; with random inputs and no simulation: a function you can call is a
function you can test a hundred thousand times a second.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;stepM&lt;/code&gt; gets a test of its own, and the test is a statement that the adapter is
not a place where behaviour can hide:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;stepM = step re-associated&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; H.&lt;/span&gt;&lt;span&gt;property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; H.&lt;/span&gt;&lt;span&gt;forAll genBusIn&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; bo&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; mr&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; step initState i&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  stepM initState i &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;H.&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;===&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;bo&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; mr&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a random &lt;code&gt;BusIn&lt;/code&gt; — random instruction word, four random sampled IO bits, a
random &lt;code&gt;ALERT#&lt;/code&gt;, a random start flag — &lt;code&gt;stepM&lt;/code&gt; must return exactly what &lt;code&gt;step&lt;/code&gt;
returned, only re-parenthesised. It looks like testing that a tuple is a tuple.
It is really a guard on a &lt;em&gt;seam&lt;/em&gt;: &lt;code&gt;stepM&lt;/code&gt; is the last thing that touches the
engine’s output before the clock does, and a swapped element or a dropped &lt;code&gt;Maybe&lt;/code&gt;
here would be invisible in every engine test and fatal on the board.&lt;/p&gt;
&lt;h2 id=&quot;no-bisignal-on-purpose&quot;&gt;No BiSignal, on purpose&lt;/h2&gt;
&lt;p&gt;Now the signature that the whole module is arranged around:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;HiddenClockResetEnable&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;            --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; uart RX line&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;    --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; ioIn&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;            --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; alertIn&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;             --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; uart TX line&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Lanes&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;           --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; lanesOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;             --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; csOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;             --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; sckOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;             --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; rstOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;             --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; led&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three in, six out, and not a &lt;code&gt;BiSignal&lt;/code&gt; anywhere. The module’s own doc comment
says why in half a sentence — &lt;em&gt;no &lt;code&gt;BiSignal&lt;/code&gt;, so the whole integration is
cosim-testable&lt;/em&gt; — and the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-io/&quot;&gt;previous post&lt;/a&gt; is the long version of that
half-sentence.&lt;/p&gt;
&lt;p&gt;Recall the shape of the trouble. A bidirectional net’s value is a function of
every driver attached to it, so a driver derived from a read of the same net is a
value defined in terms of itself, and Clash’s simulator does not resolve it — it
diverges. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-io/&quot;&gt;pad post’s&lt;/a&gt; test harnesses had to be split into two
single-driver directions to say anything at all about four lanes and one
synchroniser. That is a tolerable amount of ceremony for a hundred and four lines.
It would be an intolerable amount for a test that wants to serialise a program
onto a UART, watch a loader parse it, watch an engine execute it, and read the
result back out.&lt;/p&gt;
&lt;p&gt;So the boundary is drawn to keep the hard part small. &lt;code&gt;system&lt;/code&gt; speaks entirely in
ordinary unidirectional &lt;code&gt;Signal&lt;/code&gt;s: &lt;code&gt;ioIn&lt;/code&gt; arrives already sampled, four plain
&lt;code&gt;Bit&lt;/code&gt;s, and &lt;code&gt;lanesOut&lt;/code&gt; leaves as a plain &lt;code&gt;Lanes&lt;/code&gt;, four &lt;code&gt;(value, enable)&lt;/code&gt; pairs
that are still just numbers. Everything bidirectional lives &lt;em&gt;outside&lt;/em&gt;, in
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-io/&quot;&gt;&lt;code&gt;espiPads&lt;/code&gt;&lt;/a&gt;, and the board shell is what puts the two together. The result is
a partition with a pleasing property: the part of the design that cannot be
simulated conveniently is a hundred and four lines with nine tests, and the part
that is the actual machine — fifteen hundred lines of UART, loader, memories and
engine — can be driven end to end with a list of &lt;code&gt;Bit&lt;/code&gt;s.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Draw the boundary where the tooling gets hard, not where the diagram looks
tidy.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There is a second thing hiding in that signature, quieter than the first: it is
generic in &lt;code&gt;dom&lt;/code&gt;. &lt;code&gt;system&lt;/code&gt; does not name a clock frequency, a board, or a
domain. It says &lt;code&gt;HiddenClockResetEnable dom&lt;/code&gt; and lets whoever instantiates it
supply the clock — which is what makes one &lt;code&gt;system&lt;/code&gt; serve two board shells,
and what leaves the whole question of &lt;em&gt;which silicon&lt;/em&gt; for the post after this
one.&lt;/p&gt;
&lt;h2 id=&quot;the-confluence&quot;&gt;The confluence&lt;/h2&gt;
&lt;p&gt;The body is thirty lines, and it is the entire design:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;system rxLine ioIn alertIn &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;txLine&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; lanesO&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; csO&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; sckO&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; rstO&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; ledOut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; UART @ 2MBaud&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;rxByte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; _rxErr&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; txLine&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; txReady&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; uart &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;SNat&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;2_000_000&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; rxLine txByteL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; Loader FSM&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  lOut &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; loader &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;LoaderIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; rxByte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span&gt; txReady &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span&gt; halted &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span&gt; ringPtrO &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span&gt; ringData&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  txByteL &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; txByte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; lOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  instrWrL &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; instrWr &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; lOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ringAddrL &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; ringAddr &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; lOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  startO &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; startOut &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; lOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; Memories&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  instrWord &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; instrRam pcO instrWrL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ringData &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; ringRam ringAddrL &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ringWrite &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; maybeRing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; Engine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;busOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; maybeRing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; unbundle &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mealy stepM initState busInS&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  busInS &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; BusIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; instrWord &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span&gt; ioIn &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span&gt; alertIn &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span&gt; startO&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  pcO &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; pcOut &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; busOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  lanesO &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; lanesOut &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; busOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  …&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Four comments, four stages, and the four stages are the series in order.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;UART&lt;/strong&gt; comes first, and it is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;umbrella&lt;/a&gt; read whole: &lt;code&gt;uart (SNat @2_000_000) rxLine txByteL&lt;/code&gt; returns four things, and the design uses three
of them. &lt;code&gt;rxByte&lt;/code&gt; is a &lt;code&gt;Maybe (BitVector 8)&lt;/code&gt; strobing once per received byte,
&lt;code&gt;txLine&lt;/code&gt; is the outgoing wire, &lt;code&gt;txReady&lt;/code&gt; is the transmitter’s back-pressure. The
baud rate is passed as a type-level number in the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;&lt;code&gt;SNat&lt;/code&gt;&lt;/a&gt; the umbrella
demanded, and everything the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator&lt;/a&gt; did with a fractional
3.125 follows from it and from whatever &lt;code&gt;dom&lt;/code&gt; turns out to be.&lt;/p&gt;
&lt;p&gt;And &lt;code&gt;_rxErr&lt;/code&gt; is dropped. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;UART top post&lt;/a&gt; noted that the framing
error had no consumer yet and left it unwired, and here at the very top — the
last place it could have found one — it still has none. That underscore is the
project being honest about an unfinished edge rather than inventing a use for it.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;loader&lt;/strong&gt; is next, and its input is built by &lt;em&gt;applying a constructor across
signals&lt;/em&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;lOut &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; loader &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;LoaderIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; rxByte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span&gt; txReady &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span&gt; halted &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span&gt; ringPtrO &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span&gt; ringData&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;LoaderIn&lt;/code&gt; is an ordinary five-field record. &lt;code&gt;&amp;lt;$&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;*&amp;gt;&lt;/code&gt; lift its constructor
over &lt;code&gt;Signal&lt;/code&gt;, so what comes out is a &lt;code&gt;Signal dom LoaderIn&lt;/code&gt; — one record per
cycle, its five fields taken from five separate signals at the same
instant.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-applicative-1&quot;&gt;&lt;a href=&quot;#fn-applicative&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; The four outputs come back the same way, each a field
projection mapped over &lt;code&gt;lOut&lt;/code&gt;: &lt;code&gt;txByte &amp;lt;$&amp;gt; lOut&lt;/code&gt; for the byte to transmit,
&lt;code&gt;instrWr &amp;lt;$&amp;gt; lOut&lt;/code&gt; for a write into the instruction memory, &lt;code&gt;ringAddr &amp;lt;$&amp;gt; lOut&lt;/code&gt;
for the drain’s read address, &lt;code&gt;startOut &amp;lt;$&amp;gt; lOut&lt;/code&gt; for the trigger. The
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader’s&lt;/a&gt; three lives — receive a frame, hold it, drain the ring —
are all behind that one call.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;memories&lt;/strong&gt; are two lines, and both are the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;mem post’s&lt;/a&gt; four-line leaf
instantiated:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;instrWord &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; instrRam pcO instrWrL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ringData &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; ringRam ringAddrL &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ringWrite &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; maybeRing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;instrRam&lt;/code&gt; is read by the engine’s program counter and written by the loader.
&lt;code&gt;ringRam&lt;/code&gt; is read by the loader’s drain and written by the engine’s trace
emitter. Each memory has exactly one reader and one writer, and in both cases
they are &lt;em&gt;different modules pointing in opposite directions&lt;/em&gt; — which is the
shape a rig wants: the host writes programs and reads results; the engine reads
programs and writes results.&lt;/p&gt;
&lt;p&gt;And the &lt;strong&gt;engine&lt;/strong&gt; closes it. &lt;code&gt;busInS&lt;/code&gt; assembles a &lt;code&gt;BusIn&lt;/code&gt; from four sources —
&lt;code&gt;instrWord&lt;/code&gt; from the instruction memory, &lt;code&gt;ioIn&lt;/code&gt; and &lt;code&gt;alertIn&lt;/code&gt; from the caller
(and thence from &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-io/&quot;&gt;the pads&lt;/a&gt;), &lt;code&gt;startO&lt;/code&gt; from the loader — and &lt;code&gt;mealy stepM initState&lt;/code&gt; runs it. &lt;code&gt;busOut&lt;/code&gt; is then fanned out one field at a time: &lt;code&gt;pcOut&lt;/code&gt; back
to the memory, &lt;code&gt;lanesOut&lt;/code&gt;/&lt;code&gt;csOut&lt;/code&gt;/&lt;code&gt;sckOut&lt;/code&gt;/&lt;code&gt;rstOut&lt;/code&gt; out to the caller,
&lt;code&gt;haltedOut&lt;/code&gt; to the loader and the LED, &lt;code&gt;ringPtrOut&lt;/code&gt; to the loader so the drain
knows how far to sweep.&lt;/p&gt;
&lt;p&gt;Three of those wires are &lt;em&gt;loops&lt;/em&gt;.&lt;/p&gt;
&lt;figure class=&quot;tp-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;tp&quot; viewBox=&quot;0 0 920 400&quot; role=&quot;img&quot; aria-labelledby=&quot;tp-t tp-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;tp-t&quot;&gt;The whole Tamal design as system wires it, with its three feedback loops&lt;/title&gt;
&lt;desc id=&quot;tp-d&quot;&gt;A dashed boundary labelled system encloses six blocks. Along the top row, left to right: uart, loader, instrRam, and an accented block labelled mealy stepM initState. The rxLine enters uart from outside the boundary and txLine leaves it. Between uart and loader, rxByte runs right and txByte runs left. The loader sends instrWr to instrRam, which sends instrWord to the engine. A feedback wire labelled pcO runs from the engine&#39;s top, left along a corridor above the row, and down into instrRam. Below, a ringRam block sits under the loader, exchanging ringAddr downward and ringData upward with it; a wire labelled ringWrite runs from the engine&#39;s bottom, left, and down into ringRam&#39;s right side. A ledPattern block sits below the engine, fed by a wire labelled halted, and drives led out through the boundary. On the right, lanesOut and the cs, sck and rst sidebands leave the engine through the boundary, and ioIn with alertIn enter it.&lt;/desc&gt;
&lt;style&gt;
.tp{max-width:920px;width:100%;height:auto;display:block;margin:0 auto}
.tp .bx{fill:var(--bg-dim);stroke:var(--fg-dim);stroke-width:1.6}
.tp .bxa{fill:var(--bg-dim);stroke:var(--accent);stroke-width:2.6}
.tp .bnd{fill:none;stroke:var(--fg-dim);stroke-width:1.6;stroke-dasharray:7 5}
.tp .w{stroke:var(--fg-dim);stroke-width:1.7;fill:none}
.tp .wa{stroke:var(--accent);stroke-width:2.2;fill:none}
.tp .m{fill:var(--fg-main);font-family:var(--mono);font-size:13px}
.tp .ma{fill:var(--accent);font-family:var(--mono);font-size:13px}
.tp .l{fill:var(--fg-dim);font-family:var(--mono);font-size:9.5px}
.tp .la{fill:var(--accent);font-family:var(--mono);font-size:9.5px}
.tp .s{fill:var(--fg-dim);font-family:var(--sans);font-size:11px}
.tp .ah{fill:var(--fg-dim)}
.tp .aha{fill:var(--accent)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;tp-a&quot; markerWidth=&quot;9&quot; markerHeight=&quot;7&quot; refX=&quot;8&quot; refY=&quot;3.5&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L8,3.5 L0,7 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;tp-aa&quot; markerWidth=&quot;9&quot; markerHeight=&quot;7&quot; refX=&quot;8&quot; refY=&quot;3.5&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L8,3.5 L0,7 Z&quot; class=&quot;aha&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;!-- system boundary --&gt;
&lt;rect class=&quot;bnd&quot; x=&quot;52&quot; y=&quot;12&quot; width=&quot;756&quot; height=&quot;372&quot; rx=&quot;10&quot;/&gt;
&lt;text class=&quot;s&quot; x=&quot;60&quot; y=&quot;30&quot;&gt;system&lt;/text&gt;
&lt;!-- blocks --&gt;
&lt;rect class=&quot;bx&quot; x=&quot;76&quot; y=&quot;68&quot; width=&quot;118&quot; height=&quot;56&quot; rx=&quot;7&quot;/&gt;
&lt;text class=&quot;m&quot; x=&quot;135&quot; y=&quot;100&quot; text-anchor=&quot;middle&quot;&gt;uart&lt;/text&gt;
&lt;rect class=&quot;bx&quot; x=&quot;256&quot; y=&quot;68&quot; width=&quot;118&quot; height=&quot;56&quot; rx=&quot;7&quot;/&gt;
&lt;text class=&quot;m&quot; x=&quot;315&quot; y=&quot;100&quot; text-anchor=&quot;middle&quot;&gt;loader&lt;/text&gt;
&lt;rect class=&quot;bx&quot; x=&quot;436&quot; y=&quot;68&quot; width=&quot;118&quot; height=&quot;56&quot; rx=&quot;7&quot;/&gt;
&lt;text class=&quot;m&quot; x=&quot;495&quot; y=&quot;100&quot; text-anchor=&quot;middle&quot;&gt;instrRam&lt;/text&gt;
&lt;rect class=&quot;bxa&quot; x=&quot;628&quot; y=&quot;68&quot; width=&quot;150&quot; height=&quot;56&quot; rx=&quot;7&quot;/&gt;
&lt;text class=&quot;ma&quot; x=&quot;703&quot; y=&quot;92&quot; text-anchor=&quot;middle&quot;&gt;mealy stepM&lt;/text&gt;
&lt;text class=&quot;ma&quot; x=&quot;703&quot; y=&quot;110&quot; text-anchor=&quot;middle&quot;&gt;initState&lt;/text&gt;
&lt;rect class=&quot;bx&quot; x=&quot;256&quot; y=&quot;210&quot; width=&quot;118&quot; height=&quot;56&quot; rx=&quot;7&quot;/&gt;
&lt;text class=&quot;m&quot; x=&quot;315&quot; y=&quot;242&quot; text-anchor=&quot;middle&quot;&gt;ringRam&lt;/text&gt;
&lt;rect class=&quot;bx&quot; x=&quot;628&quot; y=&quot;310&quot; width=&quot;150&quot; height=&quot;56&quot; rx=&quot;7&quot;/&gt;
&lt;text class=&quot;m&quot; x=&quot;703&quot; y=&quot;342&quot; text-anchor=&quot;middle&quot;&gt;ledPattern&lt;/text&gt;
&lt;!-- serial line in and out --&gt;
&lt;text class=&quot;l&quot; x=&quot;8&quot; y=&quot;78&quot;&gt;rxLine&lt;/text&gt;
&lt;line class=&quot;w&quot; x1=&quot;14&quot; y1=&quot;88&quot; x2=&quot;70&quot; y2=&quot;88&quot; marker-end=&quot;url(#tp-a)&quot;/&gt;
&lt;text class=&quot;l&quot; x=&quot;8&quot; y=&quot;128&quot;&gt;txLine&lt;/text&gt;
&lt;line class=&quot;w&quot; x1=&quot;76&quot; y1=&quot;112&quot; x2=&quot;20&quot; y2=&quot;112&quot; marker-end=&quot;url(#tp-a)&quot;/&gt;
&lt;!-- uart and loader --&gt;
&lt;line class=&quot;w&quot; x1=&quot;194&quot; y1=&quot;84&quot; x2=&quot;250&quot; y2=&quot;84&quot; marker-end=&quot;url(#tp-a)&quot;/&gt;
&lt;text class=&quot;l&quot; x=&quot;225&quot; y=&quot;74&quot; text-anchor=&quot;middle&quot;&gt;rxByte&lt;/text&gt;
&lt;line class=&quot;w&quot; x1=&quot;256&quot; y1=&quot;110&quot; x2=&quot;200&quot; y2=&quot;110&quot; marker-end=&quot;url(#tp-a)&quot;/&gt;
&lt;text class=&quot;l&quot; x=&quot;225&quot; y=&quot;128&quot; text-anchor=&quot;middle&quot;&gt;txByte&lt;/text&gt;
&lt;!-- loader to instrRam --&gt;
&lt;line class=&quot;w&quot; x1=&quot;374&quot; y1=&quot;96&quot; x2=&quot;430&quot; y2=&quot;96&quot; marker-end=&quot;url(#tp-a)&quot;/&gt;
&lt;text class=&quot;l&quot; x=&quot;405&quot; y=&quot;86&quot; text-anchor=&quot;middle&quot;&gt;instrWr&lt;/text&gt;
&lt;!-- instrRam to engine --&gt;
&lt;line class=&quot;w&quot; x1=&quot;554&quot; y1=&quot;96&quot; x2=&quot;622&quot; y2=&quot;96&quot; marker-end=&quot;url(#tp-a)&quot;/&gt;
&lt;text class=&quot;l&quot; x=&quot;588&quot; y=&quot;86&quot; text-anchor=&quot;middle&quot;&gt;instrWord&lt;/text&gt;
&lt;!-- pcO feedback, above --&gt;
&lt;path class=&quot;wa&quot; d=&quot;M703,68 L703,32 L495,32 L495,62&quot; marker-end=&quot;url(#tp-aa)&quot;/&gt;
&lt;text class=&quot;la&quot; x=&quot;599&quot; y=&quot;24&quot; text-anchor=&quot;middle&quot;&gt;pcO&lt;/text&gt;
&lt;!-- ringWrite feedback, below --&gt;
&lt;path class=&quot;wa&quot; d=&quot;M703,124 L703,172 L412,172 L412,238 L380,238&quot; marker-end=&quot;url(#tp-aa)&quot;/&gt;
&lt;text class=&quot;la&quot; x=&quot;557&quot; y=&quot;164&quot; text-anchor=&quot;middle&quot;&gt;ringWrite&lt;/text&gt;
&lt;!-- loader and ringRam --&gt;
&lt;line class=&quot;w&quot; x1=&quot;292&quot; y1=&quot;124&quot; x2=&quot;292&quot; y2=&quot;204&quot; marker-end=&quot;url(#tp-a)&quot;/&gt;
&lt;text class=&quot;l&quot; x=&quot;284&quot; y=&quot;172&quot; text-anchor=&quot;end&quot;&gt;ringAddr&lt;/text&gt;
&lt;line class=&quot;wa&quot; x1=&quot;338&quot; y1=&quot;210&quot; x2=&quot;338&quot; y2=&quot;130&quot; marker-end=&quot;url(#tp-aa)&quot;/&gt;
&lt;text class=&quot;la&quot; x=&quot;346&quot; y=&quot;172&quot;&gt;ringData&lt;/text&gt;
&lt;!-- engine to led --&gt;
&lt;line class=&quot;w&quot; x1=&quot;748&quot; y1=&quot;124&quot; x2=&quot;748&quot; y2=&quot;304&quot; marker-end=&quot;url(#tp-a)&quot;/&gt;
&lt;text class=&quot;l&quot; x=&quot;756&quot; y=&quot;228&quot;&gt;halted&lt;/text&gt;
&lt;line class=&quot;w&quot; x1=&quot;778&quot; y1=&quot;338&quot; x2=&quot;826&quot; y2=&quot;338&quot; marker-end=&quot;url(#tp-a)&quot;/&gt;
&lt;text class=&quot;l&quot; x=&quot;832&quot; y=&quot;342&quot;&gt;led&lt;/text&gt;
&lt;!-- pins right --&gt;
&lt;line class=&quot;w&quot; x1=&quot;778&quot; y1=&quot;84&quot; x2=&quot;826&quot; y2=&quot;84&quot; marker-end=&quot;url(#tp-a)&quot;/&gt;
&lt;text class=&quot;l&quot; x=&quot;832&quot; y=&quot;80&quot;&gt;lanesOut&lt;/text&gt;
&lt;text class=&quot;l&quot; x=&quot;832&quot; y=&quot;96&quot;&gt;cs, sck, rst&lt;/text&gt;
&lt;line class=&quot;w&quot; x1=&quot;826&quot; y1=&quot;118&quot; x2=&quot;782&quot; y2=&quot;118&quot; marker-end=&quot;url(#tp-a)&quot;/&gt;
&lt;text class=&quot;l&quot; x=&quot;832&quot; y=&quot;122&quot;&gt;ioIn, alertIn&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The whole design, as &lt;code&gt;system&lt;/code&gt; wires it. The dashed boundary is the signature: everything inside speaks in ordinary unidirectional &lt;code&gt;Signal&lt;/code&gt;s, and the four bidirectional &lt;code&gt;IO&lt;/code&gt; lanes are somebody else&#39;s problem — &lt;code&gt;ioIn&lt;/code&gt; arrives already sampled and &lt;code&gt;lanesOut&lt;/code&gt; leaves as four &lt;code&gt;(value, enable)&lt;/code&gt; pairs. The three accented wires are the feedback loops: &lt;code&gt;pcO&lt;/code&gt; into the instruction memory whose answer comes back a cycle later, &lt;code&gt;ringWrite&lt;/code&gt; into the trace memory, and &lt;code&gt;ringData&lt;/code&gt; back out of it into the loader&#39;s drain. Each of the three is a definition that mentions a name defined further down the &lt;code&gt;where&lt;/code&gt; block, and each is legal because these are nets, not values.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Those loops are the reason this module reads strangely on a first pass. &lt;code&gt;pcO&lt;/code&gt; is
defined from &lt;code&gt;busOut&lt;/code&gt;, which comes from &lt;code&gt;busInS&lt;/code&gt;, which contains &lt;code&gt;instrWord&lt;/code&gt;,
which comes from &lt;code&gt;instrRam pcO&lt;/code&gt; — &lt;code&gt;pcO&lt;/code&gt; appears on both sides. &lt;code&gt;ringData&lt;/code&gt; feeds
the loader, whose &lt;code&gt;lOut&lt;/code&gt; produces &lt;code&gt;ringAddrL&lt;/code&gt;, which the memory needs to produce
&lt;code&gt;ringData&lt;/code&gt;. &lt;code&gt;halted&lt;/code&gt; is read by the loader four lines above the line that
defines it.&lt;/p&gt;
&lt;p&gt;None of that is a problem, and the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;UART top post&lt;/a&gt; already explained
why: Clash &lt;strong&gt;elaborates&lt;/strong&gt;, it does not evaluate. A &lt;code&gt;where&lt;/code&gt;-bound name in a
hardware description is the name of a &lt;em&gt;net&lt;/em&gt;, and a netlist has no notion of
“before”. Writing &lt;code&gt;instrWord = instrRam pcO instrWrL&lt;/code&gt; above the line that defines
&lt;code&gt;pcO&lt;/code&gt; states that the memory’s address port is connected to the engine’s &lt;code&gt;pcOut&lt;/code&gt;
port, which is a fact about wires and is as true read upwards as downwards. What
would be a problem is a loop with no register in it, and there is none: the
instruction memory’s &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;one-cycle read latency&lt;/a&gt; breaks the fetch loop, the
engine’s own state register breaks the trace loop, and the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;&lt;code&gt;Fetch&lt;/code&gt;
phase&lt;/a&gt; exists precisely to spend the cycle that latency costs.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-knot-1&quot;&gt;&lt;a href=&quot;#fn-knot&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id=&quot;one-line-that-keeps-a-promise&quot;&gt;One line that keeps a promise&lt;/h2&gt;
&lt;p&gt;The projection between the engine and the trace memory is a single line, and it
is the smallest thing in the file with an argument behind it:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;ringWrite&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RW&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ringWrite &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; fmap &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;\&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Ring&lt;/span&gt;&lt;span&gt; a d&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; d&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The engine emits &lt;code&gt;Maybe Ring&lt;/code&gt;, a record with named fields &lt;code&gt;rAddr&lt;/code&gt; and &lt;code&gt;rData&lt;/code&gt;.
The memory’s write port takes &lt;code&gt;Maybe (Unsigned RW, BitVector 32)&lt;/code&gt;, a bare tuple.
&lt;code&gt;ringWrite&lt;/code&gt; unwraps one into the other, and &lt;code&gt;fmap&lt;/code&gt; carries it through the &lt;code&gt;Maybe&lt;/code&gt;
so a cycle with nothing to write stays a cycle with nothing to write.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;mem post&lt;/a&gt; spent a section on why the write port is a bare tuple, and
gave the reason as an aphorism: &lt;em&gt;a memory that imports the engine is a memory
that knows what a trace record is&lt;/em&gt;. &lt;code&gt;ringRam&lt;/code&gt; is deliberately ignorant. It stores
thirty-two-bit words at addresses and has never heard of &lt;code&gt;Ring&lt;/code&gt;, or &lt;code&gt;Capture&lt;/code&gt;, or
the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-trace/&quot;&gt;two-bit tag&lt;/a&gt; that tells a host which record shape follows. Keeping it
that way requires somebody, somewhere, to do the unwrapping — and this is the
somebody. The adapter lives in the top, which is the one module that is &lt;em&gt;supposed&lt;/em&gt;
to know about everything, and so the ignorance the memory was designed to have is
paid for in one line by the module that can afford it.&lt;/p&gt;
&lt;p&gt;Its tests are two, and they are exactly the two cases: &lt;code&gt;ringWrite Nothing&lt;/code&gt; is
&lt;code&gt;Nothing&lt;/code&gt;, and &lt;code&gt;ringWrite (Just (Ring a d))&lt;/code&gt; is &lt;code&gt;Just (a, d)&lt;/code&gt; for random &lt;code&gt;a&lt;/code&gt; and
&lt;code&gt;d&lt;/code&gt;. Trivial — and the sort of trivial that catches a swapped pair.&lt;/p&gt;
&lt;h2 id=&quot;a-status-led-made-a-truth-table&quot;&gt;A status LED, made a truth table&lt;/h2&gt;
&lt;p&gt;The last eight lines are the user interface, and they are the only part of the
design a person looks at directly:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RigState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Waiting&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Running&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Done&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rigState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RigState&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;rigState _ &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Done&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;rigState &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Running&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;rigState &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Waiting&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;ledPattern&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RigState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 26&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ledPattern &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Waiting&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; msb c&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ledPattern &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Running&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; msb &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;shiftL&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 3&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ledPattern &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Done&lt;/span&gt;&lt;span&gt; _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; high&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And in &lt;code&gt;system&lt;/code&gt;, the three lines that give them time:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;running &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; register &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;False&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;mux startO &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pure &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;mux halted &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pure &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; running&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ledCnt &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; register &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 26&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ledCnt &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ledOut &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; ledPattern &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;rigState &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; running &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span&gt; halted&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;*&amp;gt;&lt;/span&gt;&lt;span&gt; ledCnt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Split that in half and the split is the point. The &lt;em&gt;stateful&lt;/em&gt; part is two
registers: a one-bit &lt;code&gt;running&lt;/code&gt; latch that sets on the loader’s trigger and clears
when the engine halts, and a free-running twenty-six-bit counter that does
nothing but increment forever. The &lt;em&gt;decision&lt;/em&gt; part is two pure total functions,
exported and tested, that never touch a clock.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rigState&lt;/code&gt; is a truth table with three rows and a wildcard, and the wildcard is a
priority: &lt;code&gt;halted&lt;/code&gt; wins. A rig that has run and stopped shows &lt;code&gt;Done&lt;/code&gt; whether or
not the latch is still set, so the terminal state is genuinely terminal. Its
tests are the truth table, written out — both halted cases, the running case,
the idle case — which is what a three-line function deserves and rarely gets.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ledPattern&lt;/code&gt; is the blink, and it says a great deal in three lines. &lt;code&gt;msb c&lt;/code&gt; is the
top bit of the counter, which at 100 MHz toggles every 2²⁵ cycles: about a third
of a second on and a third off, a slow, patient pulse.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-blink-1&quot;&gt;&lt;a href=&quot;#fn-blink&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; &lt;code&gt;msb (c `shiftL` 3)&lt;/code&gt; reads bit 22 instead — shifting left by three brings a lower bit
into the top position — so &lt;code&gt;Running&lt;/code&gt; blinks &lt;strong&gt;eight times faster&lt;/strong&gt; on exactly
the same counter, no second divider, no second register. And &lt;code&gt;Done&lt;/code&gt; is &lt;code&gt;high&lt;/code&gt; with
the counter ignored: solid on, and the underscore in &lt;code&gt;ledPattern Done _&lt;/code&gt; is the
whole statement that a finished rig does not blink.&lt;/p&gt;
&lt;p&gt;Its tests check the two rates against the same count, which is the sharp way to
do it:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ledPattern &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Running&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x400000&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @?=&lt;/span&gt;&lt;span&gt; high&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ledPattern &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Waiting&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x400000&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @?=&lt;/span&gt;&lt;span&gt; low   &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; same count =&amp;gt; Running is faster&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At count 2²², &lt;code&gt;Running&lt;/code&gt; is on and &lt;code&gt;Waiting&lt;/code&gt; is still off. One assertion, and the
relationship — &lt;em&gt;faster&lt;/em&gt; — is pinned rather than the absolute rates. That is the
property that actually matters to a person squinting at a board: not that the LED
blinks at 1.5 Hz, but that a running rig looks visibly different from a waiting
one.&lt;/p&gt;
&lt;p&gt;There is a general point here worth naming, because it is why these functions are
exported at all. A status indicator is the least testable part of most designs —
it is inherently about what something &lt;em&gt;looks like&lt;/em&gt; over time, and it usually ends
up as three lines of ad-hoc logic buried in a top-level module where nothing can
reach it. Splitting it into a state derivation and a pattern function turns the
question “does the LED do the right thing” into two questions with yes-or-no
answers, and moves the only untestable part — how fast a third of a second feels
— into a constant.&lt;/p&gt;
&lt;h2 id=&quot;the-tests-the-first-time-the-whole-thing-runs&quot;&gt;The tests: the first time the whole thing runs&lt;/h2&gt;
&lt;p&gt;Everything so far has been checked in pieces. &lt;code&gt;Test.Top&lt;/code&gt; is the first time the
pieces run together, and the harness it needs is instructive.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;cyclesPerBit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Int&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cyclesPerBit &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 50&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;100 MHz over 2 Mbaud is fifty cycles a bit — the same arithmetic the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud
generator&lt;/a&gt; did with a fractional accumulator, done here in whole
numbers because the test bench drives the line rather than recovering it. From
that, &lt;code&gt;serialize&lt;/code&gt; turns a list of bytes into a list of &lt;code&gt;Bit&lt;/code&gt; samples: fifty low
for the start bit, fifty per data bit LSB-first, and then &lt;em&gt;two&lt;/em&gt; bit-times high.&lt;/p&gt;
&lt;p&gt;That last detail is an admission, and the comment makes it:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; one idle bit-time between bytes (a realistic transmitter&amp;#39;s inter-byte gap ---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; the RX needs it to resync; truly back-to-back bytes drop on the falling-edge&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; resync).&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A stop bit and one idle bit before the next start. That is what a real UART
transmitter emits and what a real host sends, so the bench is not cheating — but
it is recording that the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver’s&lt;/a&gt; falling-edge resynchronisation wants
the gap, and that a stream with literally none would lose bytes. The kind of
thing you learn when the pieces meet.&lt;/p&gt;
&lt;p&gt;The decoder in the other direction is the nicer trick:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;deserialize samples &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; sampleN &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;L.&lt;/span&gt;&lt;span&gt;length samples&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          (&lt;/span&gt;&lt;span&gt;fst &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;uartRx &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;oversampleTick &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;SNat&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;2_000_000&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;fromList &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;…&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It does not reimplement UART framing. It runs the captured &lt;code&gt;txLine&lt;/code&gt; back through
&lt;strong&gt;the real &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;&lt;code&gt;uartRx&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;, fed by the real &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;&lt;code&gt;oversampleTick&lt;/code&gt;&lt;/a&gt;, and
collects the bytes that strobe out. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver post&lt;/a&gt; closed on a byte-exact
TX-to-RX loopback and called it the keystone; this is that keystone used as
laboratory equipment. The transmitter under test and the receiver reading it are
the two halves the loopback already proved agree, so a decoding failure here is a
system failure and not a bench artefact.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;runSystem&lt;/code&gt; then drives the machine, and its one subtlety is at the front:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;leadN &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; cyclesPerBit&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fifty idle-high cycles before the real stream, so that the domain’s cycle-zero
reset settles while the line is idle rather than in the middle of the first start
bit. It is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;same &lt;code&gt;sampleN&lt;/code&gt; idiom&lt;/a&gt; the memory tests needed, applied to a
whole design.&lt;/p&gt;
&lt;p&gt;And then the payoff. Two test cases, and the first is the smallest complete run
the rig can perform:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testCase &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;cosim: load [HALT], trigger -&amp;gt; drain = REVISION + HALT terminator&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  $&lt;/span&gt;&lt;span&gt; loadRunDrain &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;encode &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Halt&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 20000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  @?=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Right&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0x0001_0000&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0xC000_0000&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Follow what that sentence asks for. &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;&lt;code&gt;encode (Halt 0)&lt;/code&gt;&lt;/a&gt; produces one
thirty-two-bit instruction word. &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-wire/&quot;&gt;&lt;code&gt;encodeControl (LoadProgram …)&lt;/code&gt;&lt;/a&gt; wraps it
in a CRC, COBS-stuffs it and appends a zero delimiter; &lt;code&gt;encodeControl Trigger&lt;/code&gt;
adds a second frame. &lt;code&gt;serialize&lt;/code&gt; lays both out as ten thousand-odd line samples.
Then &lt;code&gt;system&lt;/code&gt; runs: the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;UART&lt;/a&gt; receives the bytes, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader&lt;/a&gt;
peels the frames and writes the word into the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;instruction memory&lt;/a&gt;, the
trigger raises &lt;code&gt;startOut&lt;/code&gt;, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;engine&lt;/a&gt; leaves &lt;code&gt;Idle&lt;/code&gt;, stamps its REVISION
preamble, fetches, executes a &lt;code&gt;HALT&lt;/code&gt;, and pushes a terminator record. The
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader&lt;/a&gt; sees &lt;code&gt;haltedOut&lt;/code&gt;, drains the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;ring&lt;/a&gt;, re-frames the words,
and clocks them out of the transmitter. &lt;code&gt;deserialize&lt;/code&gt; reads them back with the
real receiver and &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-wire/&quot;&gt;&lt;code&gt;decodeResult&lt;/code&gt;&lt;/a&gt; unwraps the frame.&lt;/p&gt;
&lt;p&gt;What comes out is two words. &lt;code&gt;0x0001_0000&lt;/code&gt; is the REVISION preamble the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape
post&lt;/a&gt; said &lt;code&gt;Preamble&lt;/code&gt; stamps. And &lt;code&gt;0xC000_0000&lt;/code&gt; is a &lt;code&gt;HALT&lt;/code&gt; record, which
you can read straight off the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-trace/&quot;&gt;trace post’s&lt;/a&gt; field ruler: the top two bits
are the tag &lt;code&gt;0b11&lt;/code&gt;, and every other field — seventeen reserved zeros, a
three-bit reason, the trap flag, the overflow flag, the status byte — is zero. A
clean stop, no trap, no dropped records, status nought. Exactly what &lt;code&gt;Halt 0&lt;/code&gt;
should leave behind.&lt;/p&gt;
&lt;p&gt;That single assertion exercises the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-wire/&quot;&gt;wire format&lt;/a&gt;, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-wire/&quot;&gt;COBS codec&lt;/a&gt;,
the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt;, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator&lt;/a&gt;, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;transmitter and
receiver&lt;/a&gt;, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader’s&lt;/a&gt; three lives, both &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt;, the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;instruction decoder&lt;/a&gt;, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;engine’s&lt;/a&gt; phase machine, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-trace/&quot;&gt;trace
records&lt;/a&gt; and the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-trace/&quot;&gt;ring discipline&lt;/a&gt;. Fourteen posts, one &lt;code&gt;@?=&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The second case adds the pins:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;let&lt;/span&gt;&lt;span&gt; prog &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;encode &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;CsAssert&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; encode &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;PutByteImm&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0xA5&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; encode &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;CsDeassert&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; encode &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Halt&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;…&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;assertBool &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;cs_n asserts low&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;low &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;L.&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;elem&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span&gt; cs&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;assertBool &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;sck toggles&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;low &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;L.&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;elem&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span&gt; sck &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; high &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;L.&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;elem&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span&gt; sck&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;decodeResult &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;deserialize tx&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @?=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Right&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0x0001_0000&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0xC000_0000&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A real, if minimal, eSPI transaction: assert chip select, put a byte, deassert,
halt. The assertions are deliberately weak — &lt;code&gt;CS#&lt;/code&gt; goes low at some point, &lt;code&gt;SCK&lt;/code&gt;
takes both values at some point — because the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;bus post&lt;/a&gt; already pinned the
five-cycle beat and the rising edge at the 2→3 boundary with sharp unit tests
against &lt;code&gt;step&lt;/code&gt;. What this test adds is not precision but &lt;em&gt;reach&lt;/em&gt;: it shows that a
byte typed into a serial port at one end of the design comes out the other end as
motion on the eSPI wires, and that the trace still drains cleanly afterwards. The
sharp tests prove the waveform; this one proves the waveform is connected to
anything.&lt;/p&gt;
&lt;h2 id=&quot;what-we-read&quot;&gt;What we read&lt;/h2&gt;
&lt;p&gt;A hundred and fifteen lines, and the project stops being a collection of modules.
&lt;code&gt;Tamal.Top&lt;/code&gt; exports one machine and four pure helpers, and imports six modules
that are six posts. &lt;code&gt;stepM&lt;/code&gt; re-associates the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;engine’s&lt;/a&gt; three-element
output into the pair &lt;code&gt;mealy&lt;/code&gt; wants, and that one application — &lt;code&gt;mealy stepM initState&lt;/code&gt; — is where eight posts of pure transition acquire a clock, a register
and a life; a property test holds the adapter to being nothing but parentheses.
&lt;code&gt;system&lt;/code&gt; is the whole design over plain &lt;code&gt;Signal&lt;/code&gt;s, with &lt;code&gt;BiSignal&lt;/code&gt; deliberately
excluded so the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-io/&quot;&gt;pad post’s&lt;/a&gt; simulation knot stays confined to a hundred and
four lines while everything larger stays drivable from a list of bits. Its body is
four stages — &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;UART&lt;/a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader&lt;/a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt;,
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;engine&lt;/a&gt; — and three feedback loops, each written as a definition that
mentions a later name, each legal because &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;these are nets&lt;/a&gt; and each
broken by a register that was placed for other reasons a long time ago.
&lt;code&gt;ringWrite&lt;/code&gt; unwraps a &lt;code&gt;Ring&lt;/code&gt; into a bare tuple so the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memory&lt;/a&gt; can go on not
knowing what a trace record is. And the LED is a latch, a counter, and two total
functions that turn “is it working” into a truth table.&lt;/p&gt;
&lt;p&gt;Then the tests run the whole thing, which nothing before now could. A program is
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-wire/&quot;&gt;framed&lt;/a&gt;, serialised bit by bit onto a wire, received, &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;parsed&lt;/a&gt;,
stored, triggered, &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;executed&lt;/a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-trace/&quot;&gt;traced&lt;/a&gt;, drained, re-framed, and read
back with the project’s own &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt; — and it comes back as a REVISION
word and a &lt;code&gt;HALT&lt;/code&gt; terminator with every field zero. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;introduction&lt;/a&gt;
described a rig that loads a program over a serial link and reports what happened
on the bus. Two hundred lines of test say it does.&lt;/p&gt;
&lt;p&gt;What is still missing is small and absolute. &lt;code&gt;system&lt;/code&gt; is generic in its domain: it
never says how fast its clock runs, never names a pin, and its four &lt;code&gt;IO&lt;/code&gt; lanes are
plain signals that no bonded pad has ever seen. It is a machine with no address.
The next post gives it one — fifty-one lines that tie a real oscillator to a real
domain, wrap &lt;code&gt;system&lt;/code&gt; in &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-io/&quot;&gt;&lt;code&gt;espiPads&lt;/code&gt;&lt;/a&gt;, and name every port on the package so
the place-and-route tool knows which ball of solder is &lt;code&gt;IO[0]&lt;/code&gt;. Below that there
is no more Haskell.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-applicative&quot;&gt;
&lt;p&gt;&lt;code&gt;LoaderIn &amp;lt;$&amp;gt; rxByte &amp;lt;*&amp;gt; txReady &amp;lt;*&amp;gt; halted &amp;lt;*&amp;gt; ringPtrO &amp;lt;*&amp;gt; ringData&lt;/code&gt; looks like effectful code and is nothing of the sort. &lt;code&gt;Signal dom&lt;/code&gt; is an
applicative functor over &lt;em&gt;time&lt;/em&gt;: &lt;code&gt;&amp;lt;$&amp;gt;&lt;/code&gt; maps a pure function across every cycle of
a signal at once, and &lt;code&gt;&amp;lt;*&amp;gt;&lt;/code&gt; applies a signal of functions to a signal of arguments
cycle by cycle. Feed a five-argument constructor into that machinery and what
comes back is &lt;code&gt;Signal dom LoaderIn&lt;/code&gt; — a record assembled fresh every cycle from
whatever its five sources carry at that moment. In hardware it is not an
assembly at all: a record is a bundle of wires, so the expression describes five
groups of wires being routed into one named bundle, and it costs exactly nothing.
The field projections on the other side (&lt;code&gt;txByte &amp;lt;$&amp;gt; lOut&lt;/code&gt; and friends) are the
same move reversed — selecting a sub-bundle out of a wider one. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer’s&lt;/a&gt;
framing of &lt;code&gt;Signal&lt;/code&gt; as &lt;em&gt;a stream you map over, not a value you inspect&lt;/em&gt; is what
makes this style read naturally: you never write down a cycle, so you never have
to say which one. &lt;a href=&quot;#fr-applicative-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-knot&quot;&gt;
&lt;p&gt;A &lt;code&gt;where&lt;/code&gt; block in Clash is a set of simultaneous equations over nets,
not a sequence of assignments, so mutual reference between its bindings is
ordinary. What is &lt;em&gt;not&lt;/em&gt; ordinary — and what the compiler will reject or the
simulator will hang on — is a cycle with no state element in it, since that
describes a combinational loop: a value that must be known in order to compute
itself. All three loops here are broken by registers that were placed for
independent reasons. The fetch loop &lt;code&gt;pcO → instrRam → instrWord → engine → pcO&lt;/code&gt;
crosses the instruction memory’s &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;one-cycle read latency&lt;/a&gt;, which is a
register inside the block RAM primitive and the reason the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;engine&lt;/a&gt; has a
&lt;code&gt;Fetch&lt;/code&gt; phase at all: it spends a cycle doing nothing so the word has time to
arrive. The trace loop &lt;code&gt;maybeRing → ringRam → ringData → loader → ringAddr → ringRam&lt;/code&gt; crosses the ring memory’s read latency and the loader’s own state. And
&lt;code&gt;halted&lt;/code&gt; and &lt;code&gt;startO&lt;/code&gt;, which appear above their definitions, are both projections
of registered outputs — &lt;code&gt;haltedOut&lt;/code&gt; from the engine’s state register, &lt;code&gt;startOut&lt;/code&gt;
from the loader’s. The design was never arranged to make this module typecheck;
it typechecks because a machine that talks to memories has registers in all the
right places anyway. &lt;a href=&quot;#fr-knot-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-blink&quot;&gt;
&lt;p&gt;&lt;code&gt;ledCnt&lt;/code&gt; is an &lt;code&gt;Unsigned 26&lt;/code&gt; incrementing once per clock, so its top bit
is high for 2²⁵ cycles and low for 2²⁵ cycles. At 100 MHz that is 33.5 million
cycles, about 336 ms each way — roughly a 1.5 Hz blink, which is
about as slow as an LED can flash and still read as &lt;em&gt;waiting&lt;/em&gt; rather than &lt;em&gt;broken&lt;/em&gt;.
&lt;code&gt;msb (c `shiftL` 3)&lt;/code&gt; observes bit 22 instead, one eighth of the period: about 12
Hz, fast enough to read as activity and slow enough not to look solid. The whole
thing costs one twenty-six-bit counter, because both rates are taps on the same
count rather than two dividers — which is also why the two patterns stay in phase
with each other, and why the test can compare them at a single value of the
counter. &lt;a href=&quot;#fr-blink-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Tamal: Out to the Pins</title>
    <published>2026-08-13T09:00:00+00:00</published>
    <updated>2026-08-13T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-io/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-io/</id>
    <summary type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-serdes/&quot;&gt;serdes post&lt;/a&gt; ended with a promise and an apology. It had just
finished arguing that a &lt;code&gt;(value, enable)&lt;/code&gt; pair is the whole of tri-state —
three wire states carried in two bits — and then it admitted that none of it
was real yet. Every &lt;code&gt;Lanes&lt;/code&gt; the engine computes is &lt;em&gt;still just numbers&lt;/em&gt;, it
said; a description of a drive, not a drive. Something has to carry those pairs
the last step out.&lt;/p&gt;
&lt;p&gt;This is that something. &lt;code&gt;Tamal.Io&lt;/code&gt; is a hundred and four lines, and it contains
the single line the last eight posts have been walking toward:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;toDrive &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; oe&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; oe &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; then&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; o &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is the crossing. On the left of it, the engine’s arithmetic — two &lt;code&gt;Bit&lt;/code&gt;s
in a tuple, computed by pure functions, checked by property tests, meaning
nothing to any wire. On the right, a &lt;code&gt;Maybe Bit&lt;/code&gt;, which Clash knows how to turn
into an FPGA’s output-enable and a pad that either drives or floats. Everything
the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;introduction&lt;/a&gt; meant when it said &lt;em&gt;the pins are the hard part&lt;/em&gt;
happens in this file, and the file is short enough to read in one sitting.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-serdes/&quot;&gt;serdes post&lt;/a&gt; ended with a promise and an apology. It had just
finished arguing that a &lt;code&gt;(value, enable)&lt;/code&gt; pair is the whole of tri-state —
three wire states carried in two bits — and then it admitted that none of it
was real yet. Every &lt;code&gt;Lanes&lt;/code&gt; the engine computes is &lt;em&gt;still just numbers&lt;/em&gt;, it
said; a description of a drive, not a drive. Something has to carry those pairs
the last step out.&lt;/p&gt;
&lt;p&gt;This is that something. &lt;code&gt;Tamal.Io&lt;/code&gt; is a hundred and four lines, and it contains
the single line the last eight posts have been walking toward:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;toDrive &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; oe&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; oe &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; then&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; o &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is the crossing. On the left of it, the engine’s arithmetic — two &lt;code&gt;Bit&lt;/code&gt;s
in a tuple, computed by pure functions, checked by property tests, meaning
nothing to any wire. On the right, a &lt;code&gt;Maybe Bit&lt;/code&gt;, which Clash knows how to turn
into an FPGA’s output-enable and a pad that either drives or floats. Everything
the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;introduction&lt;/a&gt; meant when it said &lt;em&gt;the pins are the hard part&lt;/em&gt;
happens in this file, and the file is short enough to read in one sitting.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;two-names-on-the-door&quot;&gt;Two names on the door&lt;/h2&gt;
&lt;p&gt;The door is the shortest of the whole series bar one, and the imports beneath it
are shorter still:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Io&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; espiPads&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; alertSync&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Clash.Prelude&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Bus.Serdes&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Lanes&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two functions, no types. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC’s&lt;/a&gt; wall-with-a-door still holds, and this
wall has exactly two openings: the pad boundary itself, and the synchroniser
that guards the one asynchronous wire in the design. Everything else in the file
is a &lt;code&gt;where&lt;/code&gt;-bound helper.&lt;/p&gt;
&lt;p&gt;The import list is the more interesting half. &lt;code&gt;Clash.Prelude&lt;/code&gt;, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;one line
that turns a Haskell file into a hardware description&lt;/a&gt;, and then a single
name from a single sibling: &lt;code&gt;Lanes&lt;/code&gt;, from &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-serdes/&quot;&gt;&lt;code&gt;Tamal.Bus.Serdes&lt;/code&gt;&lt;/a&gt;. That is
the whole dependency graph. &lt;code&gt;Tamal.Io&lt;/code&gt; has never heard of the engine. It does
not import &lt;code&gt;Tamal.Engine&lt;/code&gt;, does not know what a &lt;code&gt;BusOut&lt;/code&gt; is, has no idea a
&lt;code&gt;phase&lt;/code&gt; field exists or that a &lt;code&gt;PUT&lt;/code&gt; is different from a &lt;code&gt;TAR&lt;/code&gt;. It takes a
&lt;code&gt;Signal dom Lanes&lt;/code&gt; — four &lt;code&gt;(value, enable)&lt;/code&gt; pairs per cycle — and asks no
questions about where they came from.&lt;/p&gt;
&lt;p&gt;That is the same discipline the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt; were held to, and for the same
reason. A &lt;code&gt;blockRamPow2&lt;/code&gt; that imported the engine would be a memory that knows
what a trace record is; a pad boundary that imported the engine would be a pad
that knows what an instruction is. Neither needs to. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape post&lt;/a&gt;
observed that the engine’s &lt;code&gt;busOut&lt;/code&gt; is a &lt;em&gt;projection&lt;/em&gt; and not a computation —
the pins are read out of registers that were already settled — and this module
is the consumer that projection was written for. It receives a settled drive and
makes it physical. Nothing more.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A pad that imports the engine is a pad that knows what an instruction is.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;the-line-that-makes-it-physical&quot;&gt;The line that makes it physical&lt;/h2&gt;
&lt;p&gt;Read &lt;code&gt;toDrive&lt;/code&gt; again, because it is doing something more delicate than it looks:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;toDrive &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; oe&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  if&lt;/span&gt;&lt;span&gt; oe &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    then&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; o&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    else&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The type is &lt;code&gt;Lane -&amp;gt; Maybe Bit&lt;/code&gt;, which is &lt;code&gt;(Bit, Bit) -&amp;gt; Maybe Bit&lt;/code&gt;. Two bits in,
one optional bit out. Count the inhabitants and the shape of the move becomes
clear. The input type has four values: &lt;code&gt;(0,0)&lt;/code&gt;, &lt;code&gt;(1,0)&lt;/code&gt;, &lt;code&gt;(0,1)&lt;/code&gt;, &lt;code&gt;(1,1)&lt;/code&gt;. The
output type has three: &lt;code&gt;Nothing&lt;/code&gt;, &lt;code&gt;Just 0&lt;/code&gt;, &lt;code&gt;Just 1&lt;/code&gt;. &lt;code&gt;toDrive&lt;/code&gt; is the function
that collapses four onto three, and it collapses exactly the pair the serdes post
said was redundant — &lt;code&gt;(0,0)&lt;/code&gt; and &lt;code&gt;(1,0)&lt;/code&gt;, released with the value ignored —
onto the single &lt;code&gt;Nothing&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-serdes/&quot;&gt;serdes post&lt;/a&gt; called that redundancy &lt;em&gt;not waste, but the shape of the
thing&lt;/em&gt;, and it was right about the encoding. Two bits are how you carry three
states through a register file, a &lt;code&gt;Vec&lt;/code&gt;, a &lt;code&gt;bitCoerce&lt;/code&gt;d record, all the machinery
that wants fixed widths. But the moment those bits reach a pad, the redundancy is
a liability: a pad has no use for the value of a lane it is not driving. &lt;code&gt;Maybe Bit&lt;/code&gt; is the type with no redundancy in it. Three states, three constructors, and
no way to write down a released lane that also carries a value, because there is
no such thing.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The engine says the enable &lt;em&gt;may&lt;/em&gt; veto the value. The pad type says the veto has
already happened.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So &lt;code&gt;toDrive&lt;/code&gt; is not a conversion so much as a &lt;em&gt;normalisation&lt;/em&gt; — it takes the
engine’s convenient encoding and hands the synthesiser the canonical one. And
&lt;code&gt;Just&lt;/code&gt;/&lt;code&gt;Nothing&lt;/code&gt; is exactly the vocabulary Clash’s bidirectional primitive
expects:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;writeToBiSignal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitPack&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; a&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; NFDataX&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; a&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; …&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  BiSignalIn&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ds&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; d&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitSize&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; a&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; d&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; a&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BiSignalOut&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ds&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; d&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitSize&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; a&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Just x&lt;/code&gt; means &lt;em&gt;drive &lt;code&gt;x&lt;/code&gt; onto this net this cycle&lt;/em&gt;; &lt;code&gt;Nothing&lt;/code&gt; means &lt;em&gt;let go&lt;/em&gt;.
Which is why the body of &lt;code&gt;drive&lt;/code&gt; is one line with nothing clever in it:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;drive&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BiSignalIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;PullUp&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BiSignalOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;PullUp&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;drive i padIn &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; writeToBiSignal padIn &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;toDrive &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;laneSigs &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;!!&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;laneSigs = unbundle lanesOut&lt;/code&gt; splits the one &lt;code&gt;Signal dom (Vec 4 Lane)&lt;/code&gt; the
engine produces into four separate &lt;code&gt;Signal dom Lane&lt;/code&gt;s, one per wire.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-unbundle-1&quot;&gt;&lt;a href=&quot;#fn-unbundle&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;
&lt;code&gt;laneSigs !! i&lt;/code&gt; picks lane &lt;code&gt;i&lt;/code&gt;; &lt;code&gt;toDrive &amp;lt;$&amp;gt;&lt;/code&gt; maps the collapse across time, cycle
by cycle; &lt;code&gt;writeToBiSignal&lt;/code&gt; attaches the result to the net as a driver. Three
combinators and the &lt;code&gt;(value, enable)&lt;/code&gt; pair has become a tri-state buffer.
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;Post 3’s&lt;/a&gt; &lt;code&gt;beatLanes&lt;/code&gt; decided &lt;em&gt;what&lt;/em&gt; to drive; &lt;code&gt;serializeX1&lt;/code&gt; and &lt;code&gt;tarBeat&lt;/code&gt;
and &lt;code&gt;hiZ&lt;/code&gt; &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-serdes/&quot;&gt;built the drive&lt;/a&gt;; this line performs it.&lt;/p&gt;
&lt;h2 id=&quot;a-pad-has-two-ends&quot;&gt;A pad has two ends&lt;/h2&gt;
&lt;p&gt;The signature of &lt;code&gt;espiPads&lt;/code&gt; is the longest thing in the file, and worth reading
as a shape before reading it as a list. Five plain &lt;code&gt;Signal&lt;/code&gt; inputs, four pad
inputs, and a nine-element tuple out:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;espiPads&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  forall&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;HiddenClockResetEnable&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Lanes&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;                 --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; engine lanesOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;                   --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; csOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;                   --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; sckOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;                   --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; rstOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;                   --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; ALERT# (raw, async, active-low)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  BiSignalIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;PullUp&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;         --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; IO0 pad (read side)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  BiSignalIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;PullUp&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;         --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; IO1 pad (read side)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  BiSignalIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;PullUp&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;         --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; IO2 pad (read side)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  BiSignalIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;PullUp&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;         --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; IO3 pad (read side)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BiSignalOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;PullUp&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;         --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; IO0 pad (drive side)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BiSignalOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;PullUp&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;         --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; IO1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BiSignalOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;PullUp&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;         --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; IO2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BiSignalOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;PullUp&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;         --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; IO3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;                    --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; CS#    pin out&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;                    --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; SCK    pin out&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;                    --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; RESET# pin out&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;            --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; ioIn    -&amp;gt; BusIn.ioIn&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;                    --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; alertIn -&amp;gt; BusIn.alertIn&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Four of those inputs and four of those outputs are the same four wires.&lt;/p&gt;
&lt;p&gt;That is the thing to understand about &lt;code&gt;BiSignalIn&lt;/code&gt; and &lt;code&gt;BiSignalOut&lt;/code&gt;: they are
not two ports, they are two &lt;em&gt;views&lt;/em&gt; of one port. A unidirectional signal has a
direction baked into where it appears — an argument is an input, a result is an
output. A bidirectional wire has no such luxury, because at different moments it
is both. Clash’s answer is to split the wire into its two roles and make you name
each: &lt;code&gt;BiSignalIn&lt;/code&gt; is the net as something you may read, &lt;code&gt;BiSignalOut&lt;/code&gt; is the net
as something you may drive. Hand &lt;code&gt;espiPads&lt;/code&gt; the read view of &lt;code&gt;IO0&lt;/code&gt; and it hands
you back the drive view of &lt;code&gt;IO0&lt;/code&gt; — and when Clash lowers the design to Verilog,
it recognises that the result was derived from the argument and fuses the pair
into one &lt;code&gt;inout&lt;/code&gt; port.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-inout-1&quot;&gt;&lt;a href=&quot;#fn-inout&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;&#39;PullUp&lt;/code&gt; in &lt;code&gt;BiSignalIn &#39;PullUp dom 1&lt;/code&gt; is a type-level tag for what the net
does when &lt;em&gt;nobody&lt;/em&gt; drives it, and here that is the physically honest answer for
an eSPI bus: idle high. Every eSPI lane has a pull-up on the board, so a fully
released net floats up rather than drifting undefined — which is why the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-serdes/&quot;&gt;turnaround&lt;/a&gt; drives high before letting go, settling the line to the
level the pull-ups would have chosen anyway.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-pullup-1&quot;&gt;&lt;a href=&quot;#fn-pullup&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; The &lt;code&gt;1&lt;/code&gt; is the width in
bits: each lane is one wire, so each pad is a &lt;code&gt;BiSignal … 1&lt;/code&gt;. And the whole thing
carries &lt;code&gt;HiddenClockResetEnable dom&lt;/code&gt; — not because the pads need a clock, but
because &lt;code&gt;alertSync&lt;/code&gt;, further down, holds two registers.&lt;/p&gt;
&lt;p&gt;The last two results are the return path, and they are the fields the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape
post&lt;/a&gt; introduced without saying where they came from:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; BusIn&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; instrWord&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ioIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Vec&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; alertIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; startIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ioIn&lt;/code&gt; is &lt;em&gt;the four IO lanes sampled off the pads&lt;/em&gt;, that post said, and &lt;code&gt;alertIn&lt;/code&gt;
&lt;em&gt;the synchronised alert pin&lt;/em&gt;. This module is the pads and the synchroniser. The
loop the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;bus post&lt;/a&gt; ran — drive a beat, sample the far end, shift the bit
in — closes here, through eight signal wires and four bidirectional ones.&lt;/p&gt;
&lt;h2 id=&quot;scalar-not-vec&quot;&gt;Scalar, not Vec&lt;/h2&gt;
&lt;p&gt;The file’s longest comment is a warning, and it is the kind of warning that only
gets written after somebody has lost an afternoon:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; Per-lane scalar BiSignals, deliberately not a Vec. […] Clash fuses a scalar&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; BiSignalIn argument with the scalar BiSignalOut result derived from it into&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; one inout port per lane --- but if the lanes are routed through a Vec of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; BiSignals (zipWith/map/:&amp;gt; over BiSignalIn/BiSignalOut), Clash treats the&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; vector as an opaque bundle and drops the drive: the inout ports get a read&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; path but no tri-state driver (the write collapses to a dead net).&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Look at what this forbids, because the forbidden version is the version a Haskell
programmer writes first. Four lanes, four identical operations, a &lt;code&gt;Vec 4&lt;/code&gt; already
in hand from &lt;code&gt;unbundle&lt;/code&gt; — of course you write &lt;code&gt;zipWith writeToBiSignal pads laneSigs&lt;/code&gt; and get a &lt;code&gt;Vec 4 (BiSignalOut …)&lt;/code&gt; back. It is one line instead of four,
it is obviously correct, and every other four-lane operation in the project is
written exactly that way. &lt;code&gt;serializeX1&lt;/code&gt; maps over eight bits. &lt;code&gt;beatLanes&lt;/code&gt; returns
a whole &lt;code&gt;Vec 4&lt;/code&gt;. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;ISA&lt;/a&gt; packs and unpacks by field. Tamal is a project
that trusts &lt;code&gt;Vec&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And the &lt;code&gt;Vec&lt;/code&gt; version compiles, simulates correctly, and passes its tests. It
produces silicon that cannot drive a pin.&lt;/p&gt;
&lt;p&gt;The failure is entirely in the lowering. Clash’s &lt;code&gt;inout&lt;/code&gt; support is a
&lt;em&gt;pattern-matching&lt;/em&gt; feature, not a type-level one: the compiler looks for a
scalar &lt;code&gt;BiSignalIn&lt;/code&gt; argument in the top entity’s signature and a scalar
&lt;code&gt;BiSignalOut&lt;/code&gt; result that was derived from it, and when it finds that pair it
emits one &lt;code&gt;inout&lt;/code&gt; port and wires the tri-state driver to it. Wrap the pair in a
&lt;code&gt;Vec&lt;/code&gt; and the pair is no longer visible — the argument is now one opaque
aggregate and the result another, and the derivation relating them is buried
inside a bundle the fuser does not open. So it does the only safe thing it knows:
it keeps the read path, because reading an input is always valid, and it drops
the write, because it cannot see which port the write belongs to. The result is a
port that can be sampled and never driven. A pad that can only listen.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The type system will not save you here. The &lt;code&gt;Vec&lt;/code&gt; version has the same type as
the scalar version and does something different in Verilog.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Which is why the fix is not a fix but a &lt;em&gt;shape&lt;/em&gt;: the lanes stay scalar end to
end, four arguments in and four results out, spelled once each, and the tuple
that returns them is nine elements long because collapsing it would undo the
whole point.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;espiPads lanesOut csOut sckOut rstOut alert pad0 pad1 pad2 pad3 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt; drive &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt; pad0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span&gt; drive &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;span&gt; pad1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span&gt; drive &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;2&lt;/span&gt;&lt;span&gt; pad2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span&gt; drive &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;3&lt;/span&gt;&lt;span&gt; pad3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; …&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Four calls, hand-written, with the index and the pad matched up by eye. It reads
like a regression rather than a design, and in a sense it is: this is the one
place in Tamal where the natural Haskell abstraction has to be &lt;em&gt;declined&lt;/em&gt; because
the backend cannot see through it. The comment even records how the constraint
was established — &lt;em&gt;verified from the emitted Verilog: each &lt;code&gt;io0&lt;/code&gt;..&lt;code&gt;io3&lt;/code&gt; must
carry its own tri-state driver&lt;/em&gt; — which is the only way such a thing can be
established. No test in the Haskell world distinguishes the two versions. You
have to go read the output.&lt;/p&gt;
&lt;p&gt;This is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;introduction’s&lt;/a&gt; thesis arriving as a concrete scar. The pins
are the hard part not because tri-state logic is conceptually difficult — it is
two bits, and the serdes post spent five thousand words showing how simple —
but because the pins are where your abstraction meets somebody else’s compiler,
and the meeting has rules that live outside your type system. The engine got
eight posts of increasingly confident reasoning. The pad boundary gets a
hand-unrolled loop and a comment saying &lt;em&gt;check the Verilog&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;reading-the-net-back&quot;&gt;Reading the net back&lt;/h2&gt;
&lt;p&gt;The drive side is four scalars. The read side, one line down, is a &lt;code&gt;Vec&lt;/code&gt; again:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ioIn &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  bundle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt; readFromBiSignal pad0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        :&amp;gt;&lt;/span&gt;&lt;span&gt; readFromBiSignal pad1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        :&amp;gt;&lt;/span&gt;&lt;span&gt; readFromBiSignal pad2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        :&amp;gt;&lt;/span&gt;&lt;span&gt; readFromBiSignal pad3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        :&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That looks like it should trip the same wire, and it does not — for a reason
worth being precise about. The &lt;code&gt;Vec&lt;/code&gt; here is not a vector of &lt;code&gt;BiSignal&lt;/code&gt;s. Each
&lt;code&gt;readFromBiSignal padK&lt;/code&gt; has already turned a pad into an ordinary &lt;code&gt;Signal dom Bit&lt;/code&gt;, an everyday unidirectional value, and &lt;em&gt;those&lt;/em&gt; are what get consed together
and &lt;code&gt;bundle&lt;/code&gt;d into a &lt;code&gt;Signal dom (Vec 4 Bit)&lt;/code&gt;. The four pads are still touched
one at a time, by name; only their sampled results are gathered. The rule is
narrower than “no &lt;code&gt;Vec&lt;/code&gt;s near pads” — it is that the fusible pair, the
&lt;code&gt;BiSignalIn&lt;/code&gt; argument and the &lt;code&gt;BiSignalOut&lt;/code&gt; derived from it, must both be scalar
and visible. Once a pad has been read into a plain &lt;code&gt;Signal&lt;/code&gt;, ordinary Haskell
resumes.&lt;/p&gt;
&lt;p&gt;So the asymmetry in the source — four hand-written &lt;code&gt;drive&lt;/code&gt; calls, then a
four-element &lt;code&gt;:&amp;gt;&lt;/code&gt; chain — is not inconsistency. It is the boundary of the
constraint, drawn exactly where the constraint ends.&lt;/p&gt;
&lt;p&gt;What comes out is &lt;code&gt;Signal dom (Vec 4 Bit)&lt;/code&gt;, which is &lt;code&gt;BusIn.ioIn&lt;/code&gt;, which is where
the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;bus post’s&lt;/a&gt; &lt;code&gt;sampleGet&lt;/code&gt; was reading from all along:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;PendGet&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; t&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;shifter&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; shifter t &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;shiftL&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; .|.&lt;/span&gt;&lt;span&gt; zeroExtend &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pack &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ioIn inp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;!!&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Index &lt;code&gt;1&lt;/code&gt;. &lt;code&gt;IO[1]&lt;/code&gt;, the eSPI response lane, sampled at the rising edge, shifted
into the accumulator a bit at a time. That expression has been in the series
since the bus post, and this is the first time we can point at the wire it names:
&lt;code&gt;readFromBiSignal pad1&lt;/code&gt;, the second of four, reading whatever the target is
driving while all four of our own enables are low.&lt;/p&gt;
&lt;p&gt;Note also that the read is &lt;em&gt;combinational&lt;/em&gt;. &lt;code&gt;readFromBiSignal&lt;/code&gt; puts no register
in the path; what &lt;code&gt;ioIn&lt;/code&gt; carries this cycle is the net’s level this cycle. The
engine samples it inside &lt;code&gt;stepBusBeat&lt;/code&gt;, at a phase of its own choosing, and the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;bus post&lt;/a&gt; chose phase 3 of the five-cycle beat — comfortably after the
rising edge at the 2→3 boundary, comfortably before the next. The pad boundary
does not schedule anything. It presents a level; the engine decides when that
level means something.&lt;/p&gt;
&lt;h2 id=&quot;three-wires-that-pass-straight-through&quot;&gt;Three wires that pass straight through&lt;/h2&gt;
&lt;p&gt;Three of the nine results are pure pass-through:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  , csOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  , sckOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  , rstOut&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No transformation at all. The value that arrives is the value that leaves. And
that is correct, because the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape post&lt;/a&gt; made a point of it: &lt;code&gt;csN&lt;/code&gt;, &lt;code&gt;sck&lt;/code&gt;
and &lt;code&gt;rstN&lt;/code&gt; are &lt;em&gt;fields of the engine’s &lt;code&gt;State&lt;/code&gt;&lt;/em&gt;, written by the transition
function and read out by &lt;code&gt;busOut&lt;/code&gt;, which means they emerge from the engine
already registered — settled outputs of flip-flops, not combinational functions
of this cycle’s inputs. There is nothing left to do to them. Adding a register
here would only delay them by a cycle relative to the lanes, which are equally
registered, and skew the bus.&lt;/p&gt;
&lt;p&gt;So why route them through &lt;code&gt;espiPads&lt;/code&gt; at all, if the function does nothing to
them? Because the module’s job is to be &lt;em&gt;the pad boundary&lt;/em&gt;, singular. It is the
one place in the design where the outside world is named. A board shell that
wired &lt;code&gt;CS#&lt;/code&gt; straight from &lt;code&gt;system&lt;/code&gt; and &lt;code&gt;IO[0]&lt;/code&gt; through &lt;code&gt;espiPads&lt;/code&gt; would have the
pin list living in two places, and the next person to add a sideband would have
to guess which. Routing all seven eSPI pins through one function costs three
lines of nothing and buys a single answer to &lt;em&gt;where do the pins leave&lt;/em&gt;. It is the
same argument the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;UART top&lt;/a&gt; made for a module with no behaviour of its
own: the value is in the wiring being in one place.&lt;/p&gt;
&lt;h2 id=&quot;the-one-asynchronous-wire&quot;&gt;The one asynchronous wire&lt;/h2&gt;
&lt;p&gt;Everything else in Tamal is synchronous. The engine, the loader, the memories,
the serialiser: one clock, one domain, every signal generated inside it. There is
exactly one exception, and it is the only wire in the design that arrives from
outside with no relationship to our clock at all:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;alertSync&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;HiddenClockResetEnable&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;alertSync alert &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; alert&amp;#39;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  alert&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; register high alert&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  alert&amp;#39;&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; register high alert&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two registers in series, both initialised &lt;code&gt;high&lt;/code&gt;. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt; built the
same thing for the same reason and spent a long section on why: an asynchronous
input will eventually violate a flip-flop’s setup or hold window, that flop can
go &lt;strong&gt;metastable&lt;/strong&gt; — its output hovering at neither level for an unbounded
time — and the second flop exists to give the first a whole clock period to
settle before anything downstream is allowed to look. It does not make
metastability impossible. It makes it improbable enough to ignore.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-mtbf-1&quot;&gt;&lt;a href=&quot;#fn-mtbf&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Two details are specific to this wire rather than to synchronisers in general.&lt;/p&gt;
&lt;p&gt;The first is &lt;code&gt;high&lt;/code&gt;. Both flops power up at one, and one is &lt;em&gt;deasserted&lt;/em&gt; —
&lt;code&gt;ALERT#&lt;/code&gt; is active-low, as the trailing hash says. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt; made the
same choice for the idle-high UART line, and the argument is identical: a
synchroniser that powers up at zero powers up believing the world is asserting
something. Here that would be worse than a spurious byte. &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;&lt;code&gt;WAIT_ON&lt;/code&gt;&lt;/a&gt; blocks
the engine until the alert asserts, so a synchroniser that came out of reset low
would release a waiting engine on a signal that was never sent. Initialising the
flops to the wire’s resting state is what makes the first two cycles of a run
&lt;em&gt;quiet&lt;/em&gt; instead of a lie.&lt;/p&gt;
&lt;p&gt;The second is that this synchroniser is unconditional. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver’s&lt;/a&gt; flops
were also clocked every cycle — deliberately not gated by the oversample tick,
because a metastable strike does not wait for the tick — and the same holds
here with nothing to gate it against. Two flops, every cycle, output lagging the
raw pin by exactly two. That lag is the cost, and for this signal it is free:
&lt;code&gt;ALERT#&lt;/code&gt; is a request for attention with no timing requirement finer than the
engine’s own beat, so two cycles at 100 MHz is twenty nanoseconds of latency on a
signal that exists to be waited on.&lt;/p&gt;
&lt;p&gt;Worth noticing where the synchroniser &lt;em&gt;is&lt;/em&gt;, too. It is in the pad module, not in
the engine and not in the board shell. That is the same placement argument the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt; made about &lt;code&gt;rxLine&lt;/code&gt;: a wire is asynchronous because it comes from
a pin, so it should be synchronised at the pin, and every consumer downstream can
then treat it as ordinary. &lt;code&gt;BusIn.alertIn&lt;/code&gt; is a clean synchronous bit because
&lt;code&gt;espiPads&lt;/code&gt; cleaned it. The engine never learns that one of its inputs came from
outside the clock domain.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Synchronise a wire where it enters, once, and no one downstream has to know it
was ever dangerous.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;one-lane-drawn&quot;&gt;One lane, drawn&lt;/h2&gt;
&lt;p&gt;Four lanes, and they are four copies of one picture:&lt;/p&gt;
&lt;figure class=&quot;iop-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;iop&quot; viewBox=&quot;0 0 760 344&quot; role=&quot;img&quot; aria-labelledby=&quot;iop-t iop-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;iop-t&quot;&gt;One IO lane crossing from the engine&#39;s value-and-enable pair to a bidirectional pad&lt;/title&gt;
&lt;desc id=&quot;iop-d&quot;&gt;A left-to-right path. A box labelled lanesOut index i, holding the pair o and oe, feeds a box labelled toDrive. From toDrive an accented wire labelled Just o or Nothing runs into a tri-state buffer drawn as a triangle, labelled writeToBiSignal, whose output-enable stub above it is also accented and labelled oe. The buffer output runs right to a junction dot and then to a square port labelled inout io underscore i. A pull-up box labelled PullUp, idle high, drops onto the junction from above. From the same junction a wire runs downward into a box labelled readFromBiSignal, and below that into a box labelled ioIn index i, annotated as feeding BusIn dot ioIn. The accented path is the enable path: it runs from the pair through toDrive to the buffer&#39;s output enable.&lt;/desc&gt;
&lt;style&gt;
.iop{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.iop .bx{fill:var(--bg-dim);stroke:var(--fg-dim);stroke-width:1.6;rx:7}
.iop .bxa{fill:var(--bg-dim);stroke:var(--accent);stroke-width:2.4;rx:7}
.iop .pad{fill:var(--bg-main);stroke:var(--fg-dim);stroke-width:2}
.iop .buf{fill:var(--bg-dim);stroke:var(--accent);stroke-width:2.4}
.iop .w{stroke:var(--fg-dim);stroke-width:1.8;fill:none}
.iop .wa{stroke:var(--accent);stroke-width:2.4;fill:none}
.iop .m{fill:var(--fg-main);font-family:var(--mono);font-size:13.5px}
.iop .d{fill:var(--fg-dim);font-family:var(--mono);font-size:12px}
.iop .a{fill:var(--accent);font-family:var(--mono);font-size:12px}
.iop .s{fill:var(--fg-dim);font-family:var(--sans);font-size:11.5px}
.iop .ah{fill:var(--fg-dim)}
.iop .aha{fill:var(--accent)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;iop-m&quot; markerWidth=&quot;9&quot; markerHeight=&quot;7&quot; refX=&quot;8&quot; refY=&quot;3.5&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L8,3.5 L0,7 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;iop-ma&quot; markerWidth=&quot;9&quot; markerHeight=&quot;7&quot; refX=&quot;8&quot; refY=&quot;3.5&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L8,3.5 L0,7 Z&quot; class=&quot;aha&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;!-- engine side --&gt;
&lt;rect class=&quot;bx&quot; x=&quot;14&quot; y=&quot;62&quot; width=&quot;152&quot; height=&quot;56&quot; rx=&quot;7&quot;/&gt;
&lt;text class=&quot;m&quot; x=&quot;90&quot; y=&quot;86&quot; text-anchor=&quot;middle&quot;&gt;lanesOut !! i&lt;/text&gt;
&lt;text class=&quot;d&quot; x=&quot;90&quot; y=&quot;105&quot; text-anchor=&quot;middle&quot;&gt;(o, oe)&lt;/text&gt;
&lt;line class=&quot;w&quot; x1=&quot;166&quot; y1=&quot;90&quot; x2=&quot;222&quot; y2=&quot;90&quot; marker-end=&quot;url(#iop-m)&quot;/&gt;
&lt;!-- toDrive --&gt;
&lt;rect class=&quot;bxa&quot; x=&quot;232&quot; y=&quot;62&quot; width=&quot;120&quot; height=&quot;56&quot; rx=&quot;7&quot;/&gt;
&lt;text class=&quot;m&quot; x=&quot;292&quot; y=&quot;96&quot; text-anchor=&quot;middle&quot;&gt;toDrive&lt;/text&gt;
&lt;!-- toDrive -&gt; buffer, accented --&gt;
&lt;line class=&quot;wa&quot; x1=&quot;352&quot; y1=&quot;90&quot; x2=&quot;426&quot; y2=&quot;90&quot; marker-end=&quot;url(#iop-ma)&quot;/&gt;
&lt;text class=&quot;a&quot; x=&quot;392&quot; y=&quot;78&quot; text-anchor=&quot;middle&quot;&gt;Maybe Bit&lt;/text&gt;
&lt;!-- tri-state buffer --&gt;
&lt;path class=&quot;buf&quot; d=&quot;M432,58 L432,122 L502,90 Z&quot;/&gt;
&lt;line class=&quot;wa&quot; x1=&quot;466&quot; y1=&quot;42&quot; x2=&quot;466&quot; y2=&quot;73&quot;/&gt;
&lt;text class=&quot;a&quot; x=&quot;466&quot; y=&quot;34&quot; text-anchor=&quot;middle&quot;&gt;oe&lt;/text&gt;
&lt;text class=&quot;s&quot; x=&quot;467&quot; y=&quot;142&quot; text-anchor=&quot;middle&quot;&gt;writeToBiSignal&lt;/text&gt;
&lt;!-- buffer -&gt; pad --&gt;
&lt;line class=&quot;w&quot; x1=&quot;502&quot; y1=&quot;90&quot; x2=&quot;612&quot; y2=&quot;90&quot; marker-end=&quot;url(#iop-m)&quot;/&gt;
&lt;circle cx=&quot;556&quot; cy=&quot;90&quot; r=&quot;4.5&quot; fill=&quot;var(--fg-dim)&quot;/&gt;
&lt;!-- pull-up --&gt;
&lt;rect class=&quot;bx&quot; x=&quot;500&quot; y=&quot;10&quot; width=&quot;112&quot; height=&quot;34&quot; rx=&quot;7&quot;/&gt;
&lt;text class=&quot;d&quot; x=&quot;556&quot; y=&quot;32&quot; text-anchor=&quot;middle&quot;&gt;&#39;PullUp&lt;/text&gt;
&lt;line class=&quot;w&quot; x1=&quot;556&quot; y1=&quot;44&quot; x2=&quot;556&quot; y2=&quot;86&quot;/&gt;
&lt;!-- pad --&gt;
&lt;rect class=&quot;pad&quot; x=&quot;618&quot; y=&quot;64&quot; width=&quot;128&quot; height=&quot;52&quot;/&gt;
&lt;text class=&quot;m&quot; x=&quot;682&quot; y=&quot;95&quot; text-anchor=&quot;middle&quot;&gt;inout io_i&lt;/text&gt;
&lt;!-- read tap --&gt;
&lt;line class=&quot;w&quot; x1=&quot;556&quot; y1=&quot;90&quot; x2=&quot;556&quot; y2=&quot;178&quot; marker-end=&quot;url(#iop-m)&quot;/&gt;
&lt;rect class=&quot;bx&quot; x=&quot;462&quot; y=&quot;186&quot; width=&quot;188&quot; height=&quot;52&quot; rx=&quot;7&quot;/&gt;
&lt;text class=&quot;m&quot; x=&quot;556&quot; y=&quot;217&quot; text-anchor=&quot;middle&quot;&gt;readFromBiSignal&lt;/text&gt;
&lt;line class=&quot;w&quot; x1=&quot;556&quot; y1=&quot;238&quot; x2=&quot;556&quot; y2=&quot;270&quot; marker-end=&quot;url(#iop-m)&quot;/&gt;
&lt;rect class=&quot;bx&quot; x=&quot;462&quot; y=&quot;278&quot; width=&quot;188&quot; height=&quot;52&quot; rx=&quot;7&quot;/&gt;
&lt;text class=&quot;m&quot; x=&quot;556&quot; y=&quot;309&quot; text-anchor=&quot;middle&quot;&gt;ioIn !! i&lt;/text&gt;
&lt;line class=&quot;w&quot; x1=&quot;462&quot; y1=&quot;304&quot; x2=&quot;304&quot; y2=&quot;304&quot; marker-end=&quot;url(#iop-m)&quot;/&gt;
&lt;text class=&quot;s&quot; x=&quot;298&quot; y=&quot;308&quot; text-anchor=&quot;end&quot;&gt;to BusIn.ioIn&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;One of the four &lt;code&gt;IO&lt;/code&gt; lanes, from the engine&#39;s arithmetic to the bonded pin. The accented path is the enable: &lt;code&gt;toDrive&lt;/code&gt; collapses &lt;code&gt;(o, oe)&lt;/code&gt; into a &lt;code&gt;Maybe Bit&lt;/code&gt;, and &lt;code&gt;writeToBiSignal&lt;/code&gt; turns &lt;code&gt;Just o&lt;/code&gt; into a driven level and &lt;code&gt;Nothing&lt;/code&gt; into a released output. When nobody drives, the &lt;code&gt;&#39;PullUp&lt;/code&gt; holds the net at one. The same net is tapped combinationally by &lt;code&gt;readFromBiSignal&lt;/code&gt; and returned as &lt;code&gt;ioIn !! i&lt;/code&gt;, which is where the engine&#39;s &lt;code&gt;sampleGet&lt;/code&gt; reads &lt;code&gt;IO[1]&lt;/code&gt;. Clash fuses the scalar &lt;code&gt;BiSignalIn&lt;/code&gt; argument and the scalar &lt;code&gt;BiSignalOut&lt;/code&gt; result into the single &lt;code&gt;inout&lt;/code&gt; port on the right — which is precisely why the four lanes may not be bundled into a &lt;code&gt;Vec&lt;/code&gt;.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Four copies of this, plus three wires that pass through and one that gets caught
by two flops, and the file is done.&lt;/p&gt;
&lt;h2 id=&quot;the-tests-and-the-knot&quot;&gt;The tests, and the knot&lt;/h2&gt;
&lt;p&gt;The test module is twice the length of the module it tests, and about half of it
is comment explaining why the harnesses are shaped the way they are. That is
unusual for this project, and the explanation is worth the detour, because it is
about a hazard that only exists once signals become bidirectional.&lt;/p&gt;
&lt;p&gt;Here is the problem. The obvious test for a pad boundary is a loopback: hand
&lt;code&gt;espiPads&lt;/code&gt; a net, let it drive that net, read the same net back through the same
&lt;code&gt;espiPads&lt;/code&gt;, and check that what you drove is what you sampled. The comment says
what happens if you try:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; NB: feeding espiPads&amp;#39;s own drive-side &amp;#39;outs&amp;#39; back into the &amp;#39;padsIn&amp;#39; it also&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; reads makes the Clash BiSignal loopback diverge (self-drive + self-read knot).&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It diverges — hangs, rather than failing. And it diverges for a reason that is
not a bug: a bidirectional net’s value is a function of &lt;em&gt;every&lt;/em&gt; driver on it, so
to compute what the net carries, the simulator must first know what each driver
is putting there. If one of those drivers is derived from a read of the same net,
the value depends on itself. When the lane is driven the knot happens to untie,
because &lt;code&gt;Just o&lt;/code&gt; does not consult the net. When the lane is tri-stated it does
not: the net’s level is &lt;em&gt;only&lt;/em&gt; the other drivers, so &lt;code&gt;Nothing&lt;/code&gt; leaves the
simulator resolving a value defined in terms of itself, and Haskell’s laziness
turns that into a loop rather than an error.&lt;/p&gt;
&lt;p&gt;Which is a fair model of the world, if you think about it. A real net whose
driver is a combinational function of its own level is a real oscillator. The
simulator is not being difficult; it is refusing to invent a fixed point that
hardware would not have.&lt;/p&gt;
&lt;p&gt;So the harnesses split the loopback into its two directions, each with a single
driver:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;idleNet&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Vec&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BiSignalIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;PullUp&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Dom100&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;idleNet &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; repeat &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;veryUnsafeToBiSignalIn &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mempty &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BiSignalOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;PullUp&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Dom100&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;idleNet&lt;/code&gt; is a net nobody drives — &lt;code&gt;mempty&lt;/code&gt; is the empty set of drivers — so
it reads as the pull-up’s idle high. It is the stub you hand &lt;code&gt;espiPads&lt;/code&gt; when you
want it to &lt;em&gt;not depend&lt;/em&gt; on its read side. &lt;code&gt;simSample&lt;/code&gt; gives &lt;code&gt;espiPads&lt;/code&gt; a
DUT-driven net and makes it a pure consumer; &lt;code&gt;simDrive&lt;/code&gt; lets &lt;code&gt;espiPads&lt;/code&gt; drive
while handing it &lt;code&gt;idleNet&lt;/code&gt; to read, and routes its drive-side results to a
&lt;em&gt;fresh&lt;/em&gt; reader. Two linear paths instead of one circular one, and the physical
justification is one line of comment: &lt;em&gt;the engine never drives and samples one
lane in the same cycle&lt;/em&gt;. The thing the harness cannot simulate is a thing the
design never does.&lt;/p&gt;
&lt;p&gt;There is a second shim, and it is a small joke at the module’s expense:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;espiPadsVec&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; …&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Vec&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BiSignalIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;PullUp&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BiSignalOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;PullUp&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; …&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;espiPadsVec lanes cs sck rst alert pads &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;d0 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&amp;gt;&lt;/span&gt;&lt;span&gt; d1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&amp;gt;&lt;/span&gt;&lt;span&gt; d2 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&amp;gt;&lt;/span&gt;&lt;span&gt; d3 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nil&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; …&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;d0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; d1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; d2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; d3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; csO&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; sckO&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; rstO&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; ioIn&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; alertO&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    espiPads lanes cs sck rst alert &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pads &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;!!&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;pads &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;!!&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;pads &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;!!&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;pads &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;!!&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 3&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The tests wrap the scalar-per-lane interface back up into the &lt;code&gt;Vec&lt;/code&gt; interface it
was so carefully not given — because, as its comment notes, &lt;em&gt;bundling
&lt;code&gt;BiSignal&lt;/code&gt;s in a &lt;code&gt;Vec&lt;/code&gt; is fine in simulation; only Clash synthesis needs the
scalar-per-lane form for &lt;code&gt;inout&lt;/code&gt; fusion&lt;/em&gt;. The constraint we spent a section on is
a &lt;strong&gt;backend&lt;/strong&gt; constraint, and the test bench, which never goes near the backend,
is free to ignore it. That is worth stating plainly, because it is exactly what
makes the scar dangerous: the version that breaks silicon behaves identically
everywhere a Haskell programmer can see.&lt;/p&gt;
&lt;p&gt;The assertions themselves are short. Two oracles, one per direction:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sampleOracle &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; map &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;maybe &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;driveOracle  &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; map &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;\&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; oe&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; oe &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; then&lt;/span&gt;&lt;span&gt; o &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read them together and they are the pull-up written twice. &lt;code&gt;sampleOracle&lt;/code&gt; says a
lane reads the DUT’s value when the DUT drives and &lt;code&gt;1&lt;/code&gt; when it does not.
&lt;code&gt;driveOracle&lt;/code&gt; says a lane carries &lt;code&gt;o&lt;/code&gt; when our enable is high and &lt;code&gt;1&lt;/code&gt; when it is
not. Both fall back to one, and both times the one is the pull-up. The
&lt;a rel=&quot;external&quot; href=&quot;https://hedgehog.qa&quot;&gt;Hedgehog&lt;/a&gt; properties then sweep random drive patterns against each
model, and two &lt;code&gt;testCase&lt;/code&gt;s pin the interesting corners: drive nothing and
everything reads high; drive lane zero only and read &lt;code&gt;0 :&amp;gt; 1 :&amp;gt; 1 :&amp;gt; 1 :&amp;gt; Nil&lt;/code&gt;,
one driven low and three floating up.&lt;/p&gt;
&lt;p&gt;One case reaches back a post and welds the two modules together:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testCase &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;io: x1 beat0 drives IO[0] only, IO[1..3] hi-Z (independent OE)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  $&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; let&lt;/span&gt;&lt;span&gt; byte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0b0111_1111&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;   --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; MSB (IO[0]) = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        lane0 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; serializeX1 byte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;!!&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;head &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;simDrive &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;lane0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @?=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; :&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; :&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; :&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; :&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nil&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is not a made-up &lt;code&gt;Lanes&lt;/code&gt; value — it is &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-serdes/&quot;&gt;&lt;code&gt;serializeX1&lt;/code&gt;&lt;/a&gt;’s beat zero,
the real drive the engine emits for the first bit of a &lt;code&gt;PUT&lt;/code&gt;. The byte
&lt;code&gt;0b0111_1111&lt;/code&gt; is chosen so the MSB is the &lt;em&gt;only&lt;/em&gt; zero, so beat zero drives a low
onto &lt;code&gt;IO[0]&lt;/code&gt; while the other three lanes are released and pull up to one. The
expected vector, &lt;code&gt;0 :&amp;gt; 1 :&amp;gt; 1 :&amp;gt; 1&lt;/code&gt;, therefore distinguishes a driven zero from a
floating one by position alone. Change any part of the chain — the serialiser’s
bit order, &lt;code&gt;toDrive&lt;/code&gt;’s enable test, the per-lane indexing in &lt;code&gt;drive&lt;/code&gt; — and the
zero lands on the wrong lane or does not land at all. It is the smallest test in
the file and the only one that checks the two modules agree about which wire is
which.&lt;/p&gt;
&lt;p&gt;The synchroniser gets three of its own, and the first is the clearest statement
of what it does:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;simAlert &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @?=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The input drops on sample two; the output drops on sample four. Two cycles,
exactly, and the leading ones are the init-high flops showing through before the
pin’s own value has walked the pipeline. A property test then holds the general
form against a three-line reference model — &lt;code&gt;refAlert xs = take (length xs) (1 : 1 : xs)&lt;/code&gt;, which is &lt;em&gt;the input with two idle-high samples in front of it&lt;/em&gt; — and
a second &lt;code&gt;testCase&lt;/code&gt; checks that a single-cycle assertion survives the trip rather
than being swallowed. That last one matters: a synchroniser is allowed to delay a
pulse but not to eat it, and a one-cycle &lt;code&gt;ALERT#&lt;/code&gt; that vanished on the way in
would strand a &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;&lt;code&gt;WAIT_ON&lt;/code&gt;&lt;/a&gt; forever.&lt;/p&gt;
&lt;p&gt;Even the sideband pass-through is tested, which sounds like testing that &lt;code&gt;id&lt;/code&gt; is
&lt;code&gt;id&lt;/code&gt; — and functionally it is:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;simSide xs &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;H.&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;===&lt;/span&gt;&lt;span&gt; xs&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But the property is not really about the function. It is about the &lt;em&gt;wiring&lt;/em&gt;:
that &lt;code&gt;CS#&lt;/code&gt;, &lt;code&gt;SCK&lt;/code&gt; and &lt;code&gt;RESET#&lt;/code&gt; come out in that order, uncrossed, unregistered
and unmolested. Swap two of the nine result positions in &lt;code&gt;espiPads&lt;/code&gt; and this is
the test that goes red.&lt;/p&gt;
&lt;h2 id=&quot;what-we-read&quot;&gt;What we read&lt;/h2&gt;
&lt;p&gt;A hundred and four lines, and the series has crossed out of arithmetic. &lt;code&gt;Tamal.Io&lt;/code&gt;
imports nothing but &lt;code&gt;Clash.Prelude&lt;/code&gt; and the &lt;code&gt;Lanes&lt;/code&gt; type, and it does four things.
It turns each lane’s &lt;code&gt;(value, enable)&lt;/code&gt; pair into a &lt;code&gt;Maybe Bit&lt;/code&gt; with &lt;code&gt;toDrive&lt;/code&gt; —
four states collapsed onto three, the redundancy of a released-but-valued lane
finally made unwritable — and hands that to &lt;code&gt;writeToBiSignal&lt;/code&gt;, which is the
tri-state driver the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-serdes/&quot;&gt;serdes post&lt;/a&gt; promised the pair would one day meet.
It taps each net back with &lt;code&gt;readFromBiSignal&lt;/code&gt; and bundles the four into
&lt;code&gt;BusIn.ioIn&lt;/code&gt;, the vector &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;&lt;code&gt;sampleGet&lt;/code&gt;&lt;/a&gt; has been indexing since post three.
It passes &lt;code&gt;CS#&lt;/code&gt;, &lt;code&gt;SCK&lt;/code&gt; and &lt;code&gt;RESET#&lt;/code&gt; through untouched, because they left the
engine &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;already registered&lt;/a&gt; and the only value in routing them here is
that the pin list then lives in one place. And it catches &lt;code&gt;ALERT#&lt;/code&gt;, the single
asynchronous wire in the design, with two flops that both power up deasserted, so
a &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;&lt;code&gt;WAIT_ON&lt;/code&gt;&lt;/a&gt; is never released by a signal nobody sent.&lt;/p&gt;
&lt;p&gt;The scar in the middle of it is the one the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;introduction&lt;/a&gt; predicted.
Four lanes that every instinct in the project says should be a &lt;code&gt;Vec 4&lt;/code&gt; have to be
four scalar arguments and four scalar results, because Clash fuses &lt;code&gt;inout&lt;/code&gt; ports
by matching a scalar &lt;code&gt;BiSignalIn&lt;/code&gt; with the scalar &lt;code&gt;BiSignalOut&lt;/code&gt; derived from it,
and a &lt;code&gt;Vec&lt;/code&gt; of &lt;code&gt;BiSignal&lt;/code&gt;s hides the match — silently keeping the read path and
dropping the driver. The types do not distinguish the two versions. The
simulation does not distinguish them; the test bench cheerfully wraps the scalar
form back into a &lt;code&gt;Vec&lt;/code&gt; because in simulation the &lt;code&gt;Vec&lt;/code&gt; is fine. Only the emitted
Verilog distinguishes them, which is why the comment says to go and look. Eight
posts of the engine could be reasoned about from the source alone. The first post
of the shell cannot.&lt;/p&gt;
&lt;p&gt;The pads exist now, and they are wired to nothing. &lt;code&gt;espiPads&lt;/code&gt; takes a
&lt;code&gt;Signal dom Lanes&lt;/code&gt; and produces four &lt;code&gt;inout&lt;/code&gt; ports, but the design that computes
those lanes — the engine, the loader that feeds it a program, the two block RAMs
it reads and writes, the UART that carries frames in and out — is still a pile
of modules that have never been connected to one another. Every piece has been
read; none of them have been joined. The next post joins them: one function that
takes a UART line and the four sampled bits and gives back a serial line, a
drive, three sidebands and a status LED, with the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;engine’s pure &lt;code&gt;step&lt;/code&gt;&lt;/a&gt;
lifted into a clocked signal at the centre of it and every module in the series
hanging off the result. Everything, wired.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-unbundle&quot;&gt;
&lt;p&gt;&lt;code&gt;unbundle :: Signal dom (Vec n a) -&amp;gt; Vec n (Signal dom a)&lt;/code&gt; turns a
signal of vectors into a vector of signals, and &lt;code&gt;bundle&lt;/code&gt; goes the other way.
Both are identities on the hardware — a &lt;code&gt;Vec 4 Lane&lt;/code&gt; was always four separate
pairs of wires, and neither combinator adds a gate — so the pair exists purely
to let you choose which shape is convenient at each point in the source. Here
the choice is forced by what comes next: &lt;code&gt;writeToBiSignal&lt;/code&gt; wants a
&lt;code&gt;Signal dom (Maybe Bit)&lt;/code&gt; per pad, one pad at a time, so &lt;code&gt;unbundle&lt;/code&gt;
splits the engine’s single lane signal into four the moment it arrives.
&lt;code&gt;readFromBiSignal&lt;/code&gt; then produces four separate &lt;code&gt;Signal dom Bit&lt;/code&gt;s, and &lt;code&gt;bundle&lt;/code&gt;
regathers them because &lt;code&gt;BusIn.ioIn&lt;/code&gt; is a &lt;code&gt;Vec 4 Bit&lt;/code&gt;. The module unbundles on
the way out and bundles on the way back, and both moves are free. The
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer’s&lt;/a&gt; point about &lt;code&gt;Signal&lt;/code&gt; being a stream you map over rather than
a value you inspect is what makes this legal: &lt;code&gt;toDrive &amp;lt;$&amp;gt; laneSig&lt;/code&gt; applies a
pure function at every cycle at once, which is exactly what a combinational
circuit is. &lt;a href=&quot;#fr-unbundle-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-inout&quot;&gt;
&lt;p&gt;In Verilog a port is &lt;code&gt;input&lt;/code&gt;, &lt;code&gt;output&lt;/code&gt;, or &lt;code&gt;inout&lt;/code&gt;, and only the third
can be both driven and read by the module that declares it. Clash has no
&lt;code&gt;inout&lt;/code&gt; in its type system — &lt;code&gt;BiSignalIn&lt;/code&gt; and &lt;code&gt;BiSignalOut&lt;/code&gt; are ordinary
Haskell types — so the mapping is done structurally at lowering time: the
compiler looks for a top-entity argument of type &lt;code&gt;BiSignalIn ds dom n&lt;/code&gt; and a
result of type &lt;code&gt;BiSignalOut ds dom n&lt;/code&gt; that was produced from that argument, and
emits a single &lt;code&gt;inout&lt;/code&gt; port carrying both roles, with the &lt;code&gt;Maybe&lt;/code&gt; given to
&lt;code&gt;writeToBiSignal&lt;/code&gt; becoming the value and output-enable of a tri-state driver on
that port. &lt;code&gt;Nothing&lt;/code&gt; lowers to the driver disabled, which in Verilog is an
assignment of &lt;code&gt;1&#39;bz&lt;/code&gt;. The recognition is syntactic in the sense that matters:
it depends on the shape the pair appears in, not on the types alone. Route the
pair through a &lt;code&gt;Vec&lt;/code&gt; — &lt;code&gt;zipWith writeToBiSignal pads lanes&lt;/code&gt;, or even a &lt;code&gt;map&lt;/code&gt;
over the results — and the argument becomes one opaque aggregate and the
result another, with nothing at the port boundary relating lane &lt;code&gt;k&lt;/code&gt; of one to
lane &lt;code&gt;k&lt;/code&gt; of the other. Clash keeps what it can prove, the read, and discards
what it cannot place, the write. The emitted module still has four &lt;code&gt;inout&lt;/code&gt;
ports and still compiles; each one is simply never driven, so the bus sits at
its pull-ups forever and every &lt;code&gt;GET&lt;/code&gt; returns ones. Nothing in Haskell reports
this. The verification step recorded in the source comment — read the Verilog,
confirm each of &lt;code&gt;io0&lt;/code&gt;..&lt;code&gt;io3&lt;/code&gt; carries its own tri-state driver — is the whole
of the available evidence, which is why it is written down. &lt;a href=&quot;#fr-inout-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-pullup&quot;&gt;
&lt;p&gt;&lt;code&gt;BiSignalIn&lt;/code&gt;’s first parameter is a &lt;code&gt;BiSignalDefault&lt;/code&gt;, a type-level
tag saying what the &lt;em&gt;simulator&lt;/em&gt; should believe an undriven net carries:
&lt;code&gt;&#39;PullUp&lt;/code&gt; for one, &lt;code&gt;&#39;PullDown&lt;/code&gt; for zero, &lt;code&gt;&#39;Floating&lt;/code&gt; for undefined. It
generates no hardware — a real pull-up is a resistor on the board or a pad
attribute in the constraints file, not a gate in your netlist — so the tag is
a claim about the physical world that you are responsible for making true
elsewhere. &lt;code&gt;&#39;PullUp&lt;/code&gt; is the correct claim for eSPI: the bus idles high, every
lane has a pull-up, and a released net rises rather than drifting. It is also
what makes the test oracles read the way they do, both falling back to &lt;code&gt;1&lt;/code&gt;
whenever nothing drives. Get the tag wrong and simulation quietly disagrees
with the board about what a floating wire means — and since the whole point of
a tri-state bus is that wires spend most of their time floating, the
disagreement would not be a corner case. &lt;a href=&quot;#fr-pullup-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-mtbf&quot;&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver post&lt;/a&gt; worked this through at length, so only the
shape is worth repeating. A flip-flop sampled while its input is changing can
enter a metastable state, in which its output sits between the two valid levels
for an unbounded — though exponentially improbable — time. You cannot prevent
the first flop from going metastable, because the input is asynchronous and
asynchronous means &lt;em&gt;will eventually violate setup and hold&lt;/em&gt;. What a second flop
buys is a full clock period of settling time before any logic downstream is
allowed to look, which pushes the mean time between failures from seconds to
geological. Two flops is the standard dose; safety-critical designs add a
third. &lt;code&gt;ALERT#&lt;/code&gt; gets two, and the choice to run them unconditionally — no
enable, every cycle — matters for the same reason it mattered in the receiver:
a strike arrives when the outside world decides, not when your logic is ready
for it. The cost is a fixed two-cycle lag, and for a signal whose consumer is a
blocking wait, twenty nanoseconds is not a cost at all. &lt;a href=&quot;#fr-mtbf-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Tamal: The Record and the Ring</title>
    <published>2026-08-12T09:00:00+00:00</published>
    <updated>2026-08-12T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-trace/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-trace/</id>
    <summary type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-wire/&quot;&gt;wire post&lt;/a&gt; left a word-stream sealed. &lt;code&gt;encodeResult&lt;/code&gt; wrapped a
drained ring — a REVISION word, some records, a HALT terminator — behind an
opcode and never looked inside. This post looks inside. It is the smallest file
of the whole descent, sixty-four lines, and it is the pure twin the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;bus
post&lt;/a&gt; kept promising: every time the engine packed a &lt;code&gt;CAPTURE&lt;/code&gt; or a &lt;code&gt;MARK&lt;/code&gt;
or a &lt;code&gt;HALT&lt;/code&gt; inline, a comment said &lt;em&gt;mirrors &lt;code&gt;Trace.encodeRecord&lt;/code&gt;&lt;/em&gt;. &lt;code&gt;Tamal.Trace&lt;/code&gt;
is that mirror.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-wire/&quot;&gt;wire post&lt;/a&gt; left a word-stream sealed. &lt;code&gt;encodeResult&lt;/code&gt; wrapped a
drained ring — a REVISION word, some records, a HALT terminator — behind an
opcode and never looked inside. This post looks inside. It is the smallest file
of the whole descent, sixty-four lines, and it is the pure twin the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;bus
post&lt;/a&gt; kept promising: every time the engine packed a &lt;code&gt;CAPTURE&lt;/code&gt; or a &lt;code&gt;MARK&lt;/code&gt;
or a &lt;code&gt;HALT&lt;/code&gt; inline, a comment said &lt;em&gt;mirrors &lt;code&gt;Trace.encodeRecord&lt;/code&gt;&lt;/em&gt;. &lt;code&gt;Tamal.Trace&lt;/code&gt;
is that mirror.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;
&lt;p&gt;Like &lt;code&gt;Tamal.Wire&lt;/code&gt;, nothing here runs on the fabric. &lt;code&gt;encodeRecord&lt;/code&gt; returns a
list; &lt;code&gt;ringPush&lt;/code&gt; recurses over one. The engine emits at most one word per cycle,
inline, in the fast synchronous path the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;bus post&lt;/a&gt; read. This module
exists so the &lt;em&gt;layout&lt;/em&gt; lives somewhere readable, and a test can prove the fast
path and the readable model agree. Two functions on the door:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Trace&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Record&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; encodeRecord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ringPush&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;three-shapes&quot;&gt;Three shapes&lt;/h2&gt;
&lt;p&gt;A run leaves behind a stream of records, and there are exactly three kinds:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Record&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Capture&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;          --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; nbits (1..8), sampled byte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Mark&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 14&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;           --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; label, payload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Halt&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 3&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;   --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; trap, reason, overflow, status&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are the same three the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;bus post&lt;/a&gt; met from the engine side. &lt;code&gt;Capture&lt;/code&gt;
reports a sampled byte with its valid-bit count; &lt;code&gt;Mark&lt;/code&gt; carries a host↔trace
correlation label and a register payload; &lt;code&gt;Halt&lt;/code&gt; terminates the run, folding in
the sticky trap and overflow flags, a 3-bit reason, and a status byte. What the
engine built with hand-rolled &lt;code&gt;bitCoerce&lt;/code&gt; calls, this type names.&lt;/p&gt;
&lt;h2 id=&quot;one-encoder-laid-in-field-rulers&quot;&gt;One encoder, laid in field rulers&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;encodeRecord&lt;/code&gt; is the whole point of the file — three lines, one per shape:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;encodeRecord&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Record&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;encodeRecord &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; \&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;case&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Capture&lt;/span&gt;&lt;span&gt; n b        &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;bitCoerce &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0b00&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 18&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Mark&lt;/span&gt;&lt;span&gt; lbl pl        &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;bitCoerce &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0b10&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 16&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; lbl&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; pl&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Halt&lt;/span&gt;&lt;span&gt; trp rsn ovf st &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;bitCoerce &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0b11&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 17&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; rsn&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; trp&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; ovf&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; st&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read these against the engine’s inline builders and they are &lt;em&gt;identical&lt;/em&gt; tuples.
&lt;code&gt;captureWord&lt;/code&gt; was &lt;code&gt;bitCoerce (0b00, 0 :: BitVector 18, nbits, byte)&lt;/code&gt;;
&lt;code&gt;markLabelWord&lt;/code&gt; was &lt;code&gt;bitCoerce (0b10, 0 :: BitVector 16, lbl)&lt;/code&gt;; &lt;code&gt;haltWith&lt;/code&gt; built
&lt;code&gt;bitCoerce (0b11, 0 :: BitVector 17, reason, trap, ovf, status)&lt;/code&gt;. Same tags, same
zero padding, same field order. The engine open-codes them for speed; this
gathers them in one place you can actually read. (The REVISION preamble lives in
the engine, not here — this is the &lt;em&gt;record&lt;/em&gt; encoder, nothing more.)&lt;/p&gt;
&lt;p&gt;Every shape sums to a clean 32 bits, and the two-bit tag column is what a host
reads first to know which shape follows:&lt;/p&gt;
&lt;figure&gt;
&lt;svg class=&quot;trec&quot; viewBox=&quot;0 0 720 210&quot; role=&quot;img&quot; aria-labelledby=&quot;trec-t trec-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; style=&quot;width:100%;max-width:720px;height:auto;display:block;margin:0 auto&quot;&gt;
  &lt;title id=&quot;trec-t&quot;&gt;The three record shapes as 32-bit field rulers&lt;/title&gt;
  &lt;desc id=&quot;trec-d&quot;&gt;Three rows of bit-field rulers, each row summing to 32 bits. CAPTURE is one word: a two-bit tag 00, eighteen zero bits, a four-bit nbits field, and an eight-bit byte. MARK is two words: word one is tag 10, sixteen zero bits, and a fourteen-bit label; word two is a thirty-two-bit payload. HALT is one word: tag 11, seventeen zero bits, a three-bit reason, a one-bit trap flag, a one-bit overflow flag, and an eight-bit status. The two-bit tag column on the left of each first word is accented.&lt;/desc&gt;
  &lt;g fill=&quot;none&quot; stroke=&quot;var(--fg-dim,#999)&quot; stroke-width=&quot;1&quot;&gt;
    &lt;rect x=&quot;120&quot; y=&quot;18&quot; width=&quot;28&quot; height=&quot;26&quot; fill=&quot;var(--accent,#c25)&quot; fill-opacity=&quot;0.16&quot; stroke=&quot;var(--accent,#c25)&quot;/&gt;
    &lt;rect x=&quot;148&quot; y=&quot;18&quot; width=&quot;252&quot; height=&quot;26&quot; fill=&quot;var(--fg-dim,#999)&quot; fill-opacity=&quot;0.10&quot;/&gt;
    &lt;rect x=&quot;400&quot; y=&quot;18&quot; width=&quot;56&quot; height=&quot;26&quot;/&gt;
    &lt;rect x=&quot;456&quot; y=&quot;18&quot; width=&quot;112&quot; height=&quot;26&quot;/&gt;
    &lt;rect x=&quot;120&quot; y=&quot;74&quot; width=&quot;28&quot; height=&quot;26&quot; fill=&quot;var(--accent,#c25)&quot; fill-opacity=&quot;0.16&quot; stroke=&quot;var(--accent,#c25)&quot;/&gt;
    &lt;rect x=&quot;148&quot; y=&quot;74&quot; width=&quot;224&quot; height=&quot;26&quot; fill=&quot;var(--fg-dim,#999)&quot; fill-opacity=&quot;0.10&quot;/&gt;
    &lt;rect x=&quot;372&quot; y=&quot;74&quot; width=&quot;196&quot; height=&quot;26&quot;/&gt;
    &lt;rect x=&quot;120&quot; y=&quot;112&quot; width=&quot;448&quot; height=&quot;26&quot;/&gt;
    &lt;rect x=&quot;120&quot; y=&quot;168&quot; width=&quot;28&quot; height=&quot;26&quot; fill=&quot;var(--accent,#c25)&quot; fill-opacity=&quot;0.16&quot; stroke=&quot;var(--accent,#c25)&quot;/&gt;
    &lt;rect x=&quot;148&quot; y=&quot;168&quot; width=&quot;238&quot; height=&quot;26&quot; fill=&quot;var(--fg-dim,#999)&quot; fill-opacity=&quot;0.10&quot;/&gt;
    &lt;rect x=&quot;386&quot; y=&quot;168&quot; width=&quot;42&quot; height=&quot;26&quot;/&gt;
    &lt;rect x=&quot;428&quot; y=&quot;168&quot; width=&quot;14&quot; height=&quot;26&quot;/&gt;
    &lt;rect x=&quot;442&quot; y=&quot;168&quot; width=&quot;14&quot; height=&quot;26&quot;/&gt;
    &lt;rect x=&quot;456&quot; y=&quot;168&quot; width=&quot;112&quot; height=&quot;26&quot;/&gt;
  &lt;/g&gt;
  &lt;g fill=&quot;var(--fg,#222)&quot; font-family=&quot;ui-monospace,SFMono-Regular,Menlo,monospace&quot; font-size=&quot;12&quot; text-anchor=&quot;middle&quot;&gt;
    &lt;text x=&quot;134&quot; y=&quot;35&quot;&gt;00&lt;/text&gt;
    &lt;text x=&quot;274&quot; y=&quot;35&quot;&gt;0 (18)&lt;/text&gt;
    &lt;text x=&quot;428&quot; y=&quot;35&quot;&gt;nbits·4&lt;/text&gt;
    &lt;text x=&quot;512&quot; y=&quot;35&quot;&gt;byte·8&lt;/text&gt;
    &lt;text x=&quot;134&quot; y=&quot;91&quot;&gt;10&lt;/text&gt;
    &lt;text x=&quot;260&quot; y=&quot;91&quot;&gt;0 (16)&lt;/text&gt;
    &lt;text x=&quot;470&quot; y=&quot;91&quot;&gt;label·14&lt;/text&gt;
    &lt;text x=&quot;344&quot; y=&quot;129&quot;&gt;payload·32&lt;/text&gt;
    &lt;text x=&quot;134&quot; y=&quot;185&quot;&gt;11&lt;/text&gt;
    &lt;text x=&quot;267&quot; y=&quot;185&quot;&gt;0 (17)&lt;/text&gt;
    &lt;text x=&quot;407&quot; y=&quot;185&quot;&gt;rsn·3&lt;/text&gt;
    &lt;text x=&quot;435&quot; y=&quot;185&quot;&gt;t&lt;/text&gt;
    &lt;text x=&quot;449&quot; y=&quot;185&quot;&gt;o&lt;/text&gt;
    &lt;text x=&quot;512&quot; y=&quot;185&quot;&gt;st·8&lt;/text&gt;
  &lt;/g&gt;
  &lt;g fill=&quot;var(--fg-dim,#777)&quot; font-family=&quot;system-ui,sans-serif&quot; font-size=&quot;12&quot;&gt;
    &lt;text x=&quot;8&quot; y=&quot;34&quot;&gt;CAPTURE&lt;/text&gt;
    &lt;text x=&quot;8&quot; y=&quot;90&quot;&gt;MARK w1&lt;/text&gt;
    &lt;text x=&quot;8&quot; y=&quot;128&quot;&gt;MARK w2&lt;/text&gt;
    &lt;text x=&quot;8&quot; y=&quot;184&quot;&gt;HALT&lt;/text&gt;
  &lt;/g&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The three record shapes as 32-bit field rulers, each field&#39;s width drawn in proportion to its bit count. Each first word opens with a two-bit tag (accented) the host reads to know which shape follows; every row spans a full 32 bits. In &lt;code&gt;HALT&lt;/code&gt;, &lt;code&gt;t&lt;/code&gt; and &lt;code&gt;o&lt;/code&gt; are the single-bit trap and overflow flags. &lt;code&gt;MARK&lt;/code&gt; is the only two-word record --- its label word then a full 32-bit payload --- which is exactly why the push has to be atomic.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;test&lt;/a&gt;-style oracle is a handful of literal-shift assertions — &lt;code&gt;Capture 8 0xA5&lt;/code&gt; must equal &lt;code&gt;0b00 &amp;lt;&amp;lt; 30 | 8 &amp;lt;&amp;lt; 8 | 0xA5&lt;/code&gt;, &lt;code&gt;Mark 0x1234 0xDEADBEEF&lt;/code&gt; must be
two words with the label in the first and the payload verbatim in the second —
each computed by a different arithmetic than &lt;code&gt;bitCoerce&lt;/code&gt;, so a drift in field
placement would show as a mismatch against a number no &lt;code&gt;bitCoerce&lt;/code&gt; produced.&lt;/p&gt;
&lt;h2 id=&quot;the-push-that-never-tears&quot;&gt;The push that never tears&lt;/h2&gt;
&lt;p&gt;The other function is the ring discipline, made pure:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ringPush ptr limit ovf ws&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; ovf       &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ptr&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; fits      &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ptr &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span&gt; count&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; ws&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ptr&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  count &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; fromIntegral &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;L.&lt;/span&gt;&lt;span&gt;length ws&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  fits  &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;length ws &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ptr &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span&gt; count &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;=&lt;/span&gt;&lt;span&gt; limit&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three cases, and they are the whole &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;drop-on-full contract&lt;/a&gt; the bus post met
a word at a time. Already overflowed: drop the record, keep the sticky flag high.
Fits — the last index this record would occupy, &lt;code&gt;ptr + count - 1&lt;/code&gt;, stays within
&lt;code&gt;limit&lt;/code&gt; — write every word and advance the pointer by the record’s &lt;em&gt;whole&lt;/em&gt; word
count. Otherwise: drop, and latch overflow. The pointer never lands past &lt;code&gt;limit&lt;/code&gt;,
so the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;terminator slot&lt;/a&gt; beyond it is always free for the HALT that ends the
run.&lt;/p&gt;
&lt;p&gt;The load-bearing word is &lt;em&gt;atomically&lt;/em&gt;. &lt;code&gt;ringPush&lt;/code&gt; writes all of a record’s words
or none of them — where the engine’s &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;&lt;code&gt;pushWord&lt;/code&gt;&lt;/a&gt; went one word per cycle,
this goes record at a time. The reason is &lt;code&gt;Mark&lt;/code&gt;: it is two words, and a &lt;code&gt;Mark&lt;/code&gt;
half-written — its label word in the ring, its payload word dropped at the limit
— desyncs the host’s parse, because the host reads the label, expects a payload
word next, and finds the HALT terminator instead. Both words or neither. The test
pushes ten one-word records into a ring with &lt;code&gt;limit = 3&lt;/code&gt; and checks the invariants
the contract promises: the pointer never passes &lt;code&gt;limit + 1&lt;/code&gt;, at most four words
are ever written, and the sticky overflow flag ends &lt;code&gt;True&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-we-read&quot;&gt;What we read&lt;/h2&gt;
&lt;p&gt;Sixty-four lines, and both formats are now closed. &lt;code&gt;Tamal.Trace&lt;/code&gt; is the record
side made pure: three &lt;code&gt;Record&lt;/code&gt; shapes — &lt;code&gt;Capture&lt;/code&gt;, &lt;code&gt;Mark&lt;/code&gt;, &lt;code&gt;Halt&lt;/code&gt; —
&lt;code&gt;encodeRecord&lt;/code&gt; laying each into 32-bit words with the exact &lt;code&gt;bitCoerce&lt;/code&gt; tuples the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;engine builds inline&lt;/a&gt;, and &lt;code&gt;ringPush&lt;/code&gt; enforcing the drop-on-full,
never-past-the-limit, record-atomic push whose atomicity exists to keep a two-word
&lt;code&gt;MARK&lt;/code&gt; from tearing. Not because the fabric calls any of it — the fabric emits a
word a cycle in its own fast path — but because a readable specification is what
lets a test prove the fast path honest. The third time the series has kept a pure
twin alive for exactly this reason, after the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-serdes/&quot;&gt;serdes finale’s&lt;/a&gt;
&lt;code&gt;deserializeX1&lt;/code&gt; and the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-wire/&quot;&gt;wire post’s&lt;/a&gt; whole reference model.&lt;/p&gt;
&lt;p&gt;The wire carries a program in; the trace carries a run out; both are pure and
done. What is left is not a &lt;em&gt;format&lt;/em&gt; at all — it is the impure shell that turns
&lt;code&gt;(value, enable)&lt;/code&gt; pairs into real pins that float and drive. That crossing, out to
the silicon, is the next post.&lt;/p&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Tamal: Frames on the Wire</title>
    <published>2026-08-11T09:00:00+00:00</published>
    <updated>2026-08-11T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-wire/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-wire/</id>
    <summary type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-serdes/&quot;&gt;engine is open entire&lt;/a&gt;. The outside-in descent that began with
the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape post’s&lt;/a&gt; map of eight boxes ended when the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-serdes/&quot;&gt;serdes
finale&lt;/a&gt; opened the last of them, and everything the fabric does —
fetch, decode, compute, drive the pins, record what happened — has now been
read leaf by leaf. But a running engine on a board is only half a system. A
host has to load a program &lt;em&gt;into&lt;/em&gt; it and read a trace back &lt;em&gt;out&lt;/em&gt; of it, and
between the host and the fabric there is a wire. This post is about what
travels on that wire: not the electrical layer, not the UART that carries the
bytes, but the &lt;em&gt;format&lt;/em&gt; — the seams that turn a bare stream of bytes into
messages a host and an FPGA can agree on.&lt;/p&gt;
&lt;p&gt;We have been here before, from the other side. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader&lt;/a&gt; was “the
streaming realization of the pure &lt;code&gt;Tamal.Wire&lt;/code&gt; model,” and the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader-cobs/&quot;&gt;COBS-codec post&lt;/a&gt; realized the streaming byte-stuffing the loader
clocks a byte at a time. Both of those posts implemented something. This post
opens the thing they were implementing &lt;em&gt;against&lt;/em&gt; — the pure reference model
they were proven equal to. It is two files, &lt;code&gt;Tamal.Wire.Cobs&lt;/code&gt; (68 lines) and
&lt;code&gt;Tamal.Wire&lt;/code&gt; (158), and neither of them ever runs on the fabric. They are pure
&lt;code&gt;[BitVector 8]&lt;/code&gt; functions over ordinary lists, the same “honest surplus” the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-serdes/&quot;&gt;serdes post’s&lt;/a&gt; unused &lt;code&gt;deserializeX1&lt;/code&gt; was: a readable specification
that exists so the hardware has something to be checked against.&lt;/p&gt;
&lt;p&gt;Two files, three layers, read bottom-up: the byte-stuffing that removes every
zero, the frame that wraps a payload in a CRC and a delimiter, and the messages
the frames carry. We start at the bottom, with the leaf that owes nothing to
anyone.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-serdes/&quot;&gt;engine is open entire&lt;/a&gt;. The outside-in descent that began with
the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape post’s&lt;/a&gt; map of eight boxes ended when the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-serdes/&quot;&gt;serdes
finale&lt;/a&gt; opened the last of them, and everything the fabric does —
fetch, decode, compute, drive the pins, record what happened — has now been
read leaf by leaf. But a running engine on a board is only half a system. A
host has to load a program &lt;em&gt;into&lt;/em&gt; it and read a trace back &lt;em&gt;out&lt;/em&gt; of it, and
between the host and the fabric there is a wire. This post is about what
travels on that wire: not the electrical layer, not the UART that carries the
bytes, but the &lt;em&gt;format&lt;/em&gt; — the seams that turn a bare stream of bytes into
messages a host and an FPGA can agree on.&lt;/p&gt;
&lt;p&gt;We have been here before, from the other side. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader&lt;/a&gt; was “the
streaming realization of the pure &lt;code&gt;Tamal.Wire&lt;/code&gt; model,” and the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader-cobs/&quot;&gt;COBS-codec post&lt;/a&gt; realized the streaming byte-stuffing the loader
clocks a byte at a time. Both of those posts implemented something. This post
opens the thing they were implementing &lt;em&gt;against&lt;/em&gt; — the pure reference model
they were proven equal to. It is two files, &lt;code&gt;Tamal.Wire.Cobs&lt;/code&gt; (68 lines) and
&lt;code&gt;Tamal.Wire&lt;/code&gt; (158), and neither of them ever runs on the fabric. They are pure
&lt;code&gt;[BitVector 8]&lt;/code&gt; functions over ordinary lists, the same “honest surplus” the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-serdes/&quot;&gt;serdes post’s&lt;/a&gt; unused &lt;code&gt;deserializeX1&lt;/code&gt; was: a readable specification
that exists so the hardware has something to be checked against.&lt;/p&gt;
&lt;p&gt;Two files, three layers, read bottom-up: the byte-stuffing that removes every
zero, the frame that wraps a payload in a CRC and a delimiter, and the messages
the frames carry. We start at the bottom, with the leaf that owes nothing to
anyone.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;the-leaf-that-removes-every-zero&quot;&gt;The leaf that removes every zero&lt;/h2&gt;
&lt;p&gt;A bare stream of bytes has no seams. Send two messages back to back and the
receiver cannot tell where the first ends and the second begins — unless you
reserve a byte to mean &lt;em&gt;stop here&lt;/em&gt;. The obvious candidate is &lt;code&gt;0x00&lt;/code&gt;, but it is
only usable as a delimiter if it never appears inside a message, and real
payloads are full of zeros. &lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing&quot;&gt;&lt;strong&gt;COBS&lt;/strong&gt; — Consistent Overhead Byte
Stuffing&lt;/a&gt; — buys that guarantee cheaply: it removes &lt;em&gt;every&lt;/em&gt; &lt;code&gt;0x00&lt;/code&gt;
from a payload, at a cost of at most one extra byte per 254, so a single &lt;code&gt;0x00&lt;/code&gt;
can be reserved — unambiguously — to mean &lt;em&gt;end of frame&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Tamal.Wire.Cobs&lt;/code&gt; is the pure model of that transform, and its door lists two
functions:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Wire.Cobs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; cobsEncode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; cobsDecode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Clash.Prelude&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; qualified&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Data.List&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; as&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is a &lt;strong&gt;leaf&lt;/strong&gt;: it imports no other &lt;code&gt;Tamal.Wire.*&lt;/code&gt; module, only the prelude
and &lt;code&gt;Data.List&lt;/code&gt;. That isolation is deliberate — COBS knows nothing about
CRCs, opcodes, or frames; it is a pure list-to-list transform, and everything
above it in the stack treats it as a black box that removes zeros and puts them
back.&lt;/p&gt;
&lt;p&gt;The encoder groups the input:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;cobsEncode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cobsEncode &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; go &lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  go grp &lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; emit grp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  go grp &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span&gt; bs&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;length grp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 254&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; emit grp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span&gt; go &lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;]&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span&gt; bs&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; group full: flush, reprocess b&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; emit grp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span&gt; go &lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;]&lt;/span&gt;&lt;span&gt; bs &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; zero terminates the group (implied)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; go &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;grp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; bs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  emit grp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; fromIntegral &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;L.&lt;/span&gt;&lt;span&gt;length grp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; :&lt;/span&gt;&lt;span&gt; grp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read &lt;code&gt;emit&lt;/code&gt; first, because it defines the shape of the output. A &lt;strong&gt;group&lt;/strong&gt; is a
&lt;em&gt;code byte&lt;/em&gt; followed by its data bytes, and the code byte is &lt;code&gt;length + 1&lt;/code&gt; — so
an empty group emits &lt;code&gt;[0x01]&lt;/code&gt;, a group of three data bytes emits &lt;code&gt;[0x04, …]&lt;/code&gt;.
The &lt;code&gt;+ 1&lt;/code&gt; is what keeps zeros out of the code byte itself: the smallest code is
&lt;code&gt;0x01&lt;/code&gt;, never &lt;code&gt;0x00&lt;/code&gt;, so no code byte can ever collide with the delimiter.&lt;/p&gt;
&lt;p&gt;Then &lt;code&gt;go&lt;/code&gt; walks the input, accumulating non-zero bytes into &lt;code&gt;grp&lt;/code&gt;. Three arms,
and the order they are written in is the whole subtlety of the algorithm:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;b == 0&lt;/code&gt;&lt;/strong&gt; terminates the group. The zero is &lt;em&gt;consumed&lt;/em&gt; — it never appears
in the output — and its presence is recorded &lt;em&gt;implicitly&lt;/em&gt;, by the fact that
a new group starts after it. A code byte less than &lt;code&gt;0xFF&lt;/code&gt; means “this group
was closed by a zero the encoder ate,” and the decoder puts that zero back.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;otherwise&lt;/code&gt;&lt;/strong&gt; appends the non-zero byte and keeps going.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;length grp == 254&lt;/code&gt;&lt;/strong&gt; is checked &lt;em&gt;first&lt;/em&gt;, before either of the others, and
that ordering is load-bearing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The load-bearing case is worth slowing down for, because it is the one bug this
whole design is arranged to avoid. A group can hold at most 254 data bytes —
the code byte is &lt;code&gt;length + 1&lt;/code&gt;, and &lt;code&gt;254 + 1 = 255 = 0xFF&lt;/code&gt; is the largest byte.
When a group fills to 254, it must be flushed, and the flush code &lt;code&gt;0xFF&lt;/code&gt; carries
a special meaning: &lt;em&gt;this group was full, not closed by a zero&lt;/em&gt;. There is no
implied zero after a &lt;code&gt;0xFF&lt;/code&gt; group.&lt;/p&gt;
&lt;p&gt;Now imagine a zero arrives exactly when the group is full. If the &lt;code&gt;b == 0&lt;/code&gt; case
ran first, the zero would fold into the full group — which flushes as &lt;code&gt;0xFF&lt;/code&gt;,
which carries &lt;em&gt;no&lt;/em&gt; implied zero — and the zero would silently vanish on
decode. Checking &lt;code&gt;length == 254&lt;/code&gt; first prevents that: the full group flushes as
&lt;code&gt;0xFF&lt;/code&gt; and reprocesses &lt;code&gt;b&lt;/code&gt; in a &lt;em&gt;fresh&lt;/em&gt; empty group, so a zero landing on a full
boundary terminates that fresh group and survives as an implied zero. The
source comment spells it out, and there is a regression test pinned to exactly
this input:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testCase &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;cobsEncode 254 non-zero then 0x00 == FF,&amp;lt;254&amp;gt;,01,01&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  $&lt;/span&gt;&lt;span&gt; cobsEncode &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;run254 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0x00&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  @?=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0xFF&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; :&lt;/span&gt;&lt;span&gt; run254&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0x01&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x01&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;0xFF&lt;/code&gt; flushes the full group, and the trailing &lt;code&gt;[0x01, 0x01]&lt;/code&gt; is a fresh
empty group (code &lt;code&gt;0x01&lt;/code&gt;) closed by the zero (another &lt;code&gt;0x01&lt;/code&gt;). Fold the zero
into the full group and that &lt;code&gt;[0x01, 0x01]&lt;/code&gt; disappears — and so does the zero.
This is the same ordering the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader-cobs/&quot;&gt;streaming COBS post&lt;/a&gt; had to get
right in its &lt;code&gt;s -&amp;gt; i -&amp;gt; (s, o)&lt;/code&gt; form: same algorithm, same trap, two
realizations, and the differential tests weld them together.&lt;/p&gt;
&lt;h2 id=&quot;putting-the-zeros-back&quot;&gt;Putting the zeros back&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;cobsDecode&lt;/code&gt; is the inverse, and it carries the totality discipline the whole
series leans on: it returns &lt;code&gt;Maybe&lt;/code&gt;, &lt;code&gt;Nothing&lt;/code&gt; on anything malformed.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;cobsDecode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cobsDecode bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;null bytes &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; empty is not a valid frame&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;elem &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt; bytes &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; a valid (delimiter-stripped) frame has no 0x00&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; go bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  go &lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  go &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;code &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span&gt; rest&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; n &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; fromIntegral code &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; data bytes in this group&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          (&lt;/span&gt;&lt;span&gt;grp&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; more&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;-&lt;/span&gt;&lt;span&gt; takeExactly n rest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          if&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;null more&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            then&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; grp &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; final group: no trailing zero&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            else&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              decoded &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; go more&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;              Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;grp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;if&lt;/span&gt;&lt;span&gt; code &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 255&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; then&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; else&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;&amp;gt;&lt;/span&gt;&lt;span&gt; decoded&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The two guards reject the two ways a delimiter-stripped frame can be malformed
on its face: it cannot be empty, and it cannot contain a &lt;code&gt;0x00&lt;/code&gt; — because COBS
by construction removed all of them, so a zero in the input means corruption.
Everything else is handled by &lt;code&gt;go&lt;/code&gt;, one group at a time. It reads the code
byte, takes exactly &lt;code&gt;code - 1&lt;/code&gt; data bytes via &lt;code&gt;takeExactly&lt;/code&gt; (which returns
&lt;code&gt;Nothing&lt;/code&gt; if the input is too short — a code byte demanding more bytes than
remain), and then decides whether to reinsert a zero. The final group —
&lt;code&gt;more&lt;/code&gt; is empty — gets no trailing zero, because nothing came after it. An
interior group gets a zero &lt;em&gt;unless&lt;/em&gt; its code was &lt;code&gt;0xFF&lt;/code&gt;, because &lt;code&gt;0xFF&lt;/code&gt; means
“full group, no implied zero,” the exact flag &lt;code&gt;cobsEncode&lt;/code&gt; set.&lt;/p&gt;
&lt;p&gt;That single &lt;code&gt;if code == 255 then [] else [0]&lt;/code&gt; is the decoder’s half of the
load-bearing case. It is why the full-group flush and the zero-terminated group
decode back to different things, and why the regression test round-trips.&lt;/p&gt;
&lt;p&gt;The round-trip law is stated as a property, and it is tested over two
generators for a reason:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;cobsDecode . cobsEncode == Just&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  xs &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Gen.&lt;/span&gt;&lt;span&gt;list &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Range.&lt;/span&gt;&lt;span&gt;linear &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 300&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; genByteZeros&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  cobsDecode &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cobsEncode xs&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ===&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; xs&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;genByteZeros&lt;/code&gt; is zero-dense — it produces many group boundaries by emitting
frequent zeros — but by design it &lt;em&gt;cannot&lt;/em&gt; build a 254-byte non-zero run, so
it never reaches the full-group cap. A second generator, &lt;code&gt;genRuns&lt;/code&gt;,
concatenates non-zero runs up to 260 long, each optionally followed by a single
zero, so it crosses the 254 boundary and exercises the full-group / trailing-zero
interaction directly. Two generators, one law, and the boundary the first one
cannot reach is exactly where the algorithm is subtlest.&lt;/p&gt;
&lt;h2 id=&quot;from-bytes-to-a-word-and-back&quot;&gt;From bytes to a word, and back&lt;/h2&gt;
&lt;p&gt;With the zero-stuffing in hand, we climb to &lt;code&gt;Tamal.Wire&lt;/code&gt;, and its first job is
smaller than COBS: turn a 32-bit instruction word into bytes. The engine speaks
32-bit words; the wire speaks bytes; something has to choose an order.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;wordToBytesLE&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Vec&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;wordToBytesLE &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; reverse &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span&gt; unpack&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;bytesToWordLE&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Vec&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;bytesToWordLE &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; pack &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span&gt; reverse&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;unpack&lt;/code&gt; splits the word into a &lt;code&gt;Vec 4 (BitVector 8)&lt;/code&gt; with the most significant
byte first; &lt;code&gt;reverse&lt;/code&gt; flips it to &lt;strong&gt;little-endian&lt;/strong&gt; — least significant byte
first, so &lt;code&gt;0xAABBCCDD&lt;/code&gt; becomes &lt;code&gt;&amp;lt;0xDD, 0xCC, 0xBB, 0xAA&amp;gt;&lt;/code&gt; (ISA §4). This is a
detail worth naming out loud, because it runs &lt;em&gt;against&lt;/em&gt; the grain of everything
the bus side of the engine did: the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC post&lt;/a&gt; and the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-serdes/&quot;&gt;serdes
post&lt;/a&gt; both unpacked bytes &lt;strong&gt;MSB-first&lt;/strong&gt; onto the eSPI wire, because that
is what the eSPI spec demands. Here, the &lt;em&gt;host transport&lt;/em&gt; is little-endian —
the natural order for a host CPU writing words to a serial port. The two
orderings live in different worlds and never meet: the eSPI wire is MSB-first,
the host word transport is LE, and &lt;code&gt;wordToBytesLE&lt;/code&gt; is the seam that keeps them
straight.&lt;/p&gt;
&lt;p&gt;The inverse is &lt;code&gt;pack . reverse&lt;/code&gt;, and &lt;code&gt;bytesToWordLE (wordToBytesLE w) == w&lt;/code&gt;
falls out by construction — &lt;code&gt;reverse&lt;/code&gt; is its own inverse and &lt;code&gt;pack&lt;/code&gt;/&lt;code&gt;unpack&lt;/code&gt;
are &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;total, zero-cost reinterpretations&lt;/a&gt;. The test states it as a property
and pins the concrete vector:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testCase &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;wordToBytesLE 0xAABBCCDD == [DD,CC,BB,AA]&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  $&lt;/span&gt;&lt;span&gt; toList &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;wordToBytesLE &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0xAABBCCDD&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  @?=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0xDD&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0xCC&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0xBB&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0xAA&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;the-crc-one-layer-up&quot;&gt;The CRC, one layer up&lt;/h2&gt;
&lt;p&gt;A frame needs integrity, and the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC post&lt;/a&gt; already built the primitive:
&lt;code&gt;crc8Update&lt;/code&gt;, one byte folded into a running CRC-8 (polynomial &lt;code&gt;0x07&lt;/code&gt;, initial
&lt;code&gt;0x00&lt;/code&gt;, MSB-first). &lt;code&gt;Tamal.Wire&lt;/code&gt; reuses it, unchanged, one layer up:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;crc8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;crc8 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;foldl&amp;#39; crc8Update &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is the whole function — fold &lt;code&gt;crc8Update&lt;/code&gt; over a byte &lt;em&gt;list&lt;/em&gt;, starting
from &lt;code&gt;0&lt;/code&gt;. The CRC block did the hard per-byte arithmetic; the frame layer just
sequences it over a payload. And because it is the CRC-8/SMBUS parameterization,
it satisfies the standard check vector, which the test uses as an independent
oracle:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testCase &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;crc8 matches CRC-8/SMBUS check vector (0xF4)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  $&lt;/span&gt;&lt;span&gt; crc8 &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;fromIntegral &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fromEnum c&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;123456789&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  @?=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0xF4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The string &lt;code&gt;&quot;123456789&quot;&lt;/code&gt; is the canonical CRC check input, and &lt;code&gt;0xF4&lt;/code&gt; is the
published CRC-8/SMBUS result. If the polynomial or the fold order ever drifted,
this one line would catch it against a number no Tamal code produced.&lt;/p&gt;
&lt;h2 id=&quot;the-frame-crc-then-stuff-then-delimit&quot;&gt;The frame: CRC, then stuff, then delimit&lt;/h2&gt;
&lt;p&gt;Now the three pieces — CRC, COBS, delimiter — assemble into a frame. The
encoder is one line, and the order of operations is the entire format:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;frameEncode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;frameEncode logical &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; cobsEncode &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;logical &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;crc8 logical&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read it inside out. Start with the &lt;em&gt;logical&lt;/em&gt; bytes (an opcode and its payload).
Append the CRC of those bytes. COBS-encode the whole &lt;code&gt;logical ++ [crc]&lt;/code&gt; blob —
which removes every &lt;code&gt;0x00&lt;/code&gt; from it. Then, and only then, append a single &lt;code&gt;0x00&lt;/code&gt;
delimiter. The delimiter is safe precisely because COBS guaranteed there are no
other zeros in front of it: the one &lt;code&gt;0x00&lt;/code&gt; on the wire is unambiguously the end
of the frame.&lt;/p&gt;
&lt;figure&gt;
&lt;svg class=&quot;wirew&quot; viewBox=&quot;0 0 760 200&quot; role=&quot;img&quot; aria-labelledby=&quot;wirew-t wirew-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
  &lt;title id=&quot;wirew-t&quot;&gt;A wire frame built in four layers&lt;/title&gt;
  &lt;desc id=&quot;wirew-d&quot;&gt;A byte pipeline shown as four stacked rows. Row one, the logical frame, is an opcode byte followed by payload bytes. Row two appends a CRC-8 byte, accented, to the end. Row three COBS-encodes the result: a code byte leads each group and every 0x00 has been removed. Row four appends a single 0x00 delimiter byte, accented, at the very end; it is the only zero on the wire.&lt;/desc&gt;
  &lt;style&gt;
    .wirew text { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; fill: var(--fg, #222); }
    .wirew .lbl { font-family: system-ui, sans-serif; font-size: 12px; fill: var(--fg-dim, #777); }
    .wirew .cell { fill: none; stroke: var(--fg-dim, #999); stroke-width: 1; }
    .wirew .accent { fill: var(--accent, #c25); fill-opacity: 0.16; stroke: var(--accent, #c25); }
    .wirew .code { fill: var(--fg-dim, #999); fill-opacity: 0.12; }
  &lt;/style&gt;
  &lt;text class=&quot;lbl&quot; x=&quot;8&quot; y=&quot;26&quot;&gt;logical&lt;/text&gt;
  &lt;rect class=&quot;cell&quot; x=&quot;120&quot; y=&quot;14&quot; width=&quot;70&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;132&quot; y=&quot;31&quot;&gt;opcode&lt;/text&gt;
  &lt;rect class=&quot;cell&quot; x=&quot;190&quot; y=&quot;14&quot; width=&quot;60&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;200&quot; y=&quot;31&quot;&gt;pay&lt;/text&gt;
  &lt;rect class=&quot;cell&quot; x=&quot;250&quot; y=&quot;14&quot; width=&quot;60&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;260&quot; y=&quot;31&quot;&gt;pay&lt;/text&gt;
  &lt;rect class=&quot;cell&quot; x=&quot;310&quot; y=&quot;14&quot; width=&quot;60&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;320&quot; y=&quot;31&quot;&gt;pay&lt;/text&gt;
  &lt;text class=&quot;lbl&quot; x=&quot;8&quot; y=&quot;70&quot;&gt;+ CRC-8&lt;/text&gt;
  &lt;rect class=&quot;cell&quot; x=&quot;120&quot; y=&quot;58&quot; width=&quot;70&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;132&quot; y=&quot;75&quot;&gt;opcode&lt;/text&gt;
  &lt;rect class=&quot;cell&quot; x=&quot;190&quot; y=&quot;58&quot; width=&quot;60&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;200&quot; y=&quot;75&quot;&gt;pay&lt;/text&gt;
  &lt;rect class=&quot;cell&quot; x=&quot;250&quot; y=&quot;58&quot; width=&quot;60&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;260&quot; y=&quot;75&quot;&gt;pay&lt;/text&gt;
  &lt;rect class=&quot;cell&quot; x=&quot;310&quot; y=&quot;58&quot; width=&quot;60&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;320&quot; y=&quot;75&quot;&gt;pay&lt;/text&gt;
  &lt;rect class=&quot;cell accent&quot; x=&quot;370&quot; y=&quot;58&quot; width=&quot;60&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;382&quot; y=&quot;75&quot;&gt;crc&lt;/text&gt;
  &lt;text class=&quot;lbl&quot; x=&quot;8&quot; y=&quot;114&quot;&gt;COBS&lt;/text&gt;
  &lt;rect class=&quot;cell code&quot; x=&quot;120&quot; y=&quot;102&quot; width=&quot;52&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;130&quot; y=&quot;119&quot;&gt;code&lt;/text&gt;
  &lt;rect class=&quot;cell&quot; x=&quot;172&quot; y=&quot;102&quot; width=&quot;70&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;184&quot; y=&quot;119&quot;&gt;opcode&lt;/text&gt;
  &lt;rect class=&quot;cell&quot; x=&quot;242&quot; y=&quot;102&quot; width=&quot;52&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;252&quot; y=&quot;119&quot;&gt;pay&lt;/text&gt;
  &lt;rect class=&quot;cell code&quot; x=&quot;294&quot; y=&quot;102&quot; width=&quot;52&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;304&quot; y=&quot;119&quot;&gt;code&lt;/text&gt;
  &lt;rect class=&quot;cell&quot; x=&quot;346&quot; y=&quot;102&quot; width=&quot;52&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;356&quot; y=&quot;119&quot;&gt;pay&lt;/text&gt;
  &lt;rect class=&quot;cell accent&quot; x=&quot;398&quot; y=&quot;102&quot; width=&quot;52&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;410&quot; y=&quot;119&quot;&gt;crc&lt;/text&gt;
  &lt;text class=&quot;lbl&quot; x=&quot;460&quot; y=&quot;118&quot;&gt;no 0x00 inside&lt;/text&gt;
  &lt;text class=&quot;lbl&quot; x=&quot;8&quot; y=&quot;158&quot;&gt;+ delim&lt;/text&gt;
  &lt;rect class=&quot;cell code&quot; x=&quot;120&quot; y=&quot;146&quot; width=&quot;52&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;130&quot; y=&quot;163&quot;&gt;code&lt;/text&gt;
  &lt;rect class=&quot;cell&quot; x=&quot;172&quot; y=&quot;146&quot; width=&quot;70&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;184&quot; y=&quot;163&quot;&gt;opcode&lt;/text&gt;
  &lt;rect class=&quot;cell&quot; x=&quot;242&quot; y=&quot;146&quot; width=&quot;52&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;252&quot; y=&quot;163&quot;&gt;pay&lt;/text&gt;
  &lt;rect class=&quot;cell code&quot; x=&quot;294&quot; y=&quot;146&quot; width=&quot;52&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;304&quot; y=&quot;163&quot;&gt;code&lt;/text&gt;
  &lt;rect class=&quot;cell&quot; x=&quot;346&quot; y=&quot;146&quot; width=&quot;52&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;356&quot; y=&quot;163&quot;&gt;pay&lt;/text&gt;
  &lt;rect class=&quot;cell accent&quot; x=&quot;398&quot; y=&quot;146&quot; width=&quot;52&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;410&quot; y=&quot;163&quot;&gt;crc&lt;/text&gt;
  &lt;rect class=&quot;cell accent&quot; x=&quot;450&quot; y=&quot;146&quot; width=&quot;44&quot; height=&quot;24&quot;/&gt;&lt;text x=&quot;462&quot; y=&quot;163&quot;&gt;00&lt;/text&gt;
  &lt;text class=&quot;lbl&quot; x=&quot;506&quot; y=&quot;162&quot;&gt;the one zero on the wire&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;A frame is built in four steps. The &lt;em&gt;logical&lt;/em&gt; bytes are an opcode and its payload; &lt;code&gt;frameEncode&lt;/code&gt; appends the CRC-8 (accented), COBS-encodes the whole blob so every interior &lt;code&gt;0x00&lt;/code&gt; is removed and each group gains a leading code byte, then appends a single &lt;code&gt;0x00&lt;/code&gt; delimiter (accented) --- the only zero on the wire, and therefore an unambiguous end-of-frame marker.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The decoder runs the pipeline backwards, and every step that can fail names its
failure:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;frameDecode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Either&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; WireError&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;frameDecode wire &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  stripped &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; stripDelim wire&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  content &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; maybe &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Left&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; BadCobs&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Right&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;cobsDecode stripped&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;logical&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; crc&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;-&lt;/span&gt;&lt;span&gt; splitLastByte content&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  if&lt;/span&gt;&lt;span&gt; crc8 logical &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; crc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    then&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Right&lt;/span&gt;&lt;span&gt; logical&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    else&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Left&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; BadCrc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Strip the trailing &lt;code&gt;0x00&lt;/code&gt; delimiter (&lt;code&gt;stripDelim&lt;/code&gt;, failing &lt;code&gt;ShortFrame&lt;/code&gt; on empty
or &lt;code&gt;BadCobs&lt;/code&gt; if the last byte is not a zero), COBS-decode (lifting the codec’s
&lt;code&gt;Nothing&lt;/code&gt; to &lt;code&gt;BadCobs&lt;/code&gt;), split off the trailing CRC byte, and verify it against
a freshly recomputed CRC. Four steps, each in the &lt;code&gt;Either WireError&lt;/code&gt; monad, so
the first failure short-circuits and names itself.&lt;/p&gt;
&lt;p&gt;That &lt;code&gt;WireError&lt;/code&gt; sum is the same “total, named failures” discipline as the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;ISA post’s&lt;/a&gt; &lt;code&gt;DecodeError&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; WireError&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; BadCrc&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;          --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; CRC byte did not match the recomputed CRC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; BadCobs&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;         --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; malformed COBS or missing delimiter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; UnknownOpcode&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; ShortFrame&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;      --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; decoded frame lacks an opcode and/or CRC byte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; BadPayloadLen&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;   --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; payload length invalid for the opcode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Five ways a frame can be wrong, each a constructor, none of them an exception or
a crash. A decoder that returns &lt;code&gt;Either WireError&lt;/code&gt; cannot silently accept a
corrupt frame — the caller must handle every arm — and the property test
proves the pipeline never lies:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;single-byte corruption is never a silent success&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  xs &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Gen.&lt;/span&gt;&lt;span&gt;list &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Range.&lt;/span&gt;&lt;span&gt;linear &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; genByte&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; f &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; frameEncode xs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Gen.&lt;/span&gt;&lt;span&gt;int &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Range.&lt;/span&gt;&lt;span&gt;linear &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;L.&lt;/span&gt;&lt;span&gt;length f &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; f&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;if&lt;/span&gt;&lt;span&gt; j &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;then&lt;/span&gt;&lt;span&gt; x &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;xor&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; else&lt;/span&gt;&lt;span&gt; x &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;j&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; x&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;-&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;zip &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ..&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; f&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  frameDecode f&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;/==&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Right&lt;/span&gt;&lt;span&gt; xs&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Flip a single bit anywhere in a valid frame and decode must &lt;em&gt;not&lt;/em&gt; return the
original bytes. It might return a &lt;code&gt;Left&lt;/code&gt; of some &lt;code&gt;WireError&lt;/code&gt;, or a &lt;code&gt;Right&lt;/code&gt; of
&lt;em&gt;different&lt;/em&gt; bytes — but never a silent success on corrupted input. That is the
CRC earning its byte, checked at every position in the frame.&lt;/p&gt;
&lt;h2 id=&quot;the-messages-the-frames-carry&quot;&gt;The messages the frames carry&lt;/h2&gt;
&lt;p&gt;The top layer gives the frames meaning. A frame is just an opcode and a payload;
a &lt;em&gt;message&lt;/em&gt; is what those mean. Two directions, host-to-FPGA control and
FPGA-to-host results, and a small type for the control plane:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; ControlMsg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; LoadProgram&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; instruction words to load into the instr BRAM&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Trigger&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;                     --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; start-of-run pulse&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three opcodes name the traffic (§8.1), and &lt;code&gt;0x00&lt;/code&gt; is conspicuously &lt;em&gt;not&lt;/em&gt; among
them, because it is the delimiter and can never be an opcode:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;opLoadProgram &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x01&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;opTrigger     &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x02&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;opTraceDrain  &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x81&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Encoding a control message is opcode-prefix plus payload, run through
&lt;code&gt;frameEncode&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;encodeControl&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; ControlMsg&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;encodeControl &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;LoadProgram&lt;/span&gt;&lt;span&gt; ws&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  frameEncode &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;opLoadProgram &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;concatMap &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;toList &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span&gt; wordToBytesLE&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; ws&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;encodeControl &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Trigger&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; frameEncode &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;opTrigger&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;LoadProgram&lt;/code&gt; splits each 32-bit word into its four little-endian bytes and
concatenates them behind the opcode; &lt;code&gt;Trigger&lt;/code&gt; is a lone opcode with no payload.
Decoding reverses it and enforces the per-opcode payload rules:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;decodeControl&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Either&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; WireError&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; ControlMsg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;decodeControl wire &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  logical &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; frameDecode wire&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  case&lt;/span&gt;&lt;span&gt; logical &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;    [&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Left&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; ShortFrame&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;op &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span&gt; payload&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      |&lt;/span&gt;&lt;span&gt; op &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; opLoadProgram &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; LoadProgram&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; bytesToWords payload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      |&lt;/span&gt;&lt;span&gt; op &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; opTrigger &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          if&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;null payload &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Right&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Trigger&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; else&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Left&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; BadPayloadLen&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Left&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;UnknownOpcode&lt;/span&gt;&lt;span&gt; op&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every branch is total: an empty logical frame is &lt;code&gt;ShortFrame&lt;/code&gt;, a &lt;code&gt;Trigger&lt;/code&gt; with
a non-empty payload is &lt;code&gt;BadPayloadLen&lt;/code&gt;, an opcode outside the set is
&lt;code&gt;UnknownOpcode op&lt;/code&gt; carrying the offending byte. And &lt;code&gt;LoadProgram&lt;/code&gt; defers to
&lt;code&gt;bytesToWords&lt;/code&gt;, which regroups the little-endian byte payload back into words and
refuses anything that is not a clean multiple of four:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;bytesToWords&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Either&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; WireError&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;bytesToWords &lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Right&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;bytesToWords &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;a &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span&gt; d &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span&gt; rest&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;bytesToWordLE &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;a &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&amp;gt;&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&amp;gt;&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&amp;gt;&lt;/span&gt;&lt;span&gt; d &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nil&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; :&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; bytesToWords rest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;bytesToWords _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Left&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; BadPayloadLen&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Four bytes make a word; zero bytes left over is success; one, two, or three
trailing bytes is &lt;code&gt;BadPayloadLen&lt;/code&gt;. A program is a whole number of 32-bit
instructions or it is not a program.&lt;/p&gt;
&lt;p&gt;The result direction is the mirror. &lt;code&gt;encodeResult&lt;/code&gt; wraps a drained ring
word-stream — the REVISION word, the records, the HALT terminator (§8.3) —
behind the &lt;code&gt;opTraceDrain&lt;/code&gt; opcode, and &lt;code&gt;decodeResult&lt;/code&gt; unwraps it:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;encodeResult ws &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; frameEncode &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;opTraceDrain &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;concatMap &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;toList &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span&gt; wordToBytesLE&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; ws&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;decodeResult wire &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  logical &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; frameDecode wire&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  case&lt;/span&gt;&lt;span&gt; logical &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;    [&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Left&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; ShortFrame&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;op &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span&gt; payload&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      |&lt;/span&gt;&lt;span&gt; op &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; opTraceDrain &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; bytesToWords payload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Left&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;UnknownOpcode&lt;/span&gt;&lt;span&gt; op&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same shape as the control path, same LE byte regrouping, same named failures —
and the &lt;em&gt;content&lt;/em&gt; of that word-stream, the records the ring holds, is the
subject of the &lt;a href=&quot;https://balbi.sh/posts/tamal-wire/#what-we-read&quot;&gt;next post&lt;/a&gt;. The two directions round-trip in
tests (&lt;code&gt;decodeControl . encodeControl == Right&lt;/code&gt;, &lt;code&gt;decodeResult . encodeResult == Right&lt;/code&gt;), and each rejects the other’s opcodes: a &lt;code&gt;Trigger&lt;/code&gt; frame fed to
&lt;code&gt;decodeResult&lt;/code&gt; comes back &lt;code&gt;Left (UnknownOpcode 0x02)&lt;/code&gt;, because the result
decoder answers only to &lt;code&gt;0x81&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-reference-model-role-made-explicit&quot;&gt;The reference-model role, made explicit&lt;/h2&gt;
&lt;p&gt;Nothing in either file is synthesizable. &lt;code&gt;cobsEncode&lt;/code&gt; builds an unbounded list
with &lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt;; &lt;code&gt;bytesToWords&lt;/code&gt; recurses over a list of runtime-unknown length;
&lt;code&gt;frameDecode&lt;/code&gt; returns &lt;code&gt;Either&lt;/code&gt; — these are ordinary Haskell over &lt;code&gt;[BitVector 8]&lt;/code&gt;, and no fabric ever runs them. So why do they exist, if the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader&lt;/a&gt;
already implements the streaming form the hardware actually clocks?&lt;/p&gt;
&lt;p&gt;Because the streaming form needs something to be &lt;em&gt;right against&lt;/em&gt;. The
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader&lt;/a&gt; walks the wire a byte per cycle, threading state through a
mealy machine; &lt;code&gt;Tamal.Wire&lt;/code&gt; describes the same result in one pure pass over a
list. The two are welded together by differential property tests — feed both
the same input, assert the same output — so the readable model is the
&lt;em&gt;specification&lt;/em&gt; and the streaming machine is the &lt;em&gt;implementation&lt;/em&gt;, and the tests
prove they agree. This is the third time the series has used this move: the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-serdes/&quot;&gt;serdes post’s&lt;/a&gt; &lt;code&gt;deserializeX1&lt;/code&gt;, the &lt;code&gt;Tamal.Trace&lt;/code&gt; model the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;bus
post&lt;/a&gt; kept pointing at, and now the wire format. Each is a pure twin kept
alive not because the fabric calls it, but because a specification you can read
is what lets you trust the machine you cannot.&lt;/p&gt;
&lt;h2 id=&quot;what-we-read&quot;&gt;What we read&lt;/h2&gt;
&lt;p&gt;The pure wire format, opened whole. &lt;code&gt;Tamal.Wire.Cobs&lt;/code&gt; first — the leaf that
removes every &lt;code&gt;0x00&lt;/code&gt; so one &lt;code&gt;0x00&lt;/code&gt; can delimit frames, its groups a code byte
&lt;code&gt;(len + 1)&lt;/code&gt; and up to 254 non-zero data bytes, its one subtle case the full
group flushed as &lt;code&gt;0xFF&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; the zero case is checked, so a zero on a full
boundary opens a fresh group instead of vanishing. Then &lt;code&gt;Tamal.Wire&lt;/code&gt; above it:
&lt;code&gt;wordToBytesLE&lt;/code&gt; splitting a word into little-endian bytes (the host transport LE,
against the eSPI wire’s MSB-first), &lt;code&gt;crc8&lt;/code&gt; folding the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC post’s&lt;/a&gt;
&lt;code&gt;crc8Update&lt;/code&gt; over a list, &lt;code&gt;frameEncode&lt;/code&gt; appending the CRC then COBS-encoding then
the delimiter, and &lt;code&gt;frameDecode&lt;/code&gt; undoing it through a &lt;code&gt;WireError&lt;/code&gt; sum of named
failures. And the message layer on top: &lt;code&gt;ControlMsg&lt;/code&gt;, the &lt;code&gt;0x01&lt;/code&gt;/&lt;code&gt;0x02&lt;/code&gt;/&lt;code&gt;0x81&lt;/code&gt;
opcodes, the control and result codecs, &lt;code&gt;bytesToWords&lt;/code&gt; insisting on whole words.
All of it pure, all of it a specification the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader&lt;/a&gt; and the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader-cobs/&quot;&gt;streaming
COBS codec&lt;/a&gt; were proven equal to.&lt;/p&gt;
&lt;p&gt;The wire carries a program &lt;em&gt;in&lt;/em&gt; and a trace &lt;em&gt;out&lt;/em&gt;. We have read the program side
— &lt;code&gt;LoadProgram&lt;/code&gt;, the words, the LE bytes. The trace side ended at
&lt;code&gt;encodeResult&lt;/code&gt;, wrapping a word-stream this post never looked inside: the
REVISION word, the records, the HALT terminator. Those records — what a
&lt;code&gt;CAPTURE&lt;/code&gt; packs, what a &lt;code&gt;MARK&lt;/code&gt; carries, how the ring pushes them without ever
tearing a two-word record in half — are the &lt;code&gt;Tamal.Trace&lt;/code&gt; model, the pure twin
the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;bus post&lt;/a&gt; kept promising. That is the next post, and with it both
formats, in and out, are done — and the series turns, at last, to the impure
shell that makes the pins physical.&lt;/p&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Tamal: Value and Enable</title>
    <published>2026-08-10T09:00:00+00:00</published>
    <updated>2026-08-10T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-serdes/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-serdes/</id>
    <summary type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-compute/&quot;&gt;compute post&lt;/a&gt; ended on the one leaf still shut, and it named
that leaf by what it &lt;em&gt;does&lt;/em&gt; rather than what it &lt;em&gt;is&lt;/em&gt;: the drive on the lanes
when a &lt;code&gt;PUT&lt;/code&gt; walked the beats, the turnaround when the bus changed hands, a
byte becoming MSB-first bits on &lt;code&gt;IO[0]&lt;/code&gt; — all of it, it promised, built
out of a single tiny type, a &lt;code&gt;(value, enable)&lt;/code&gt; pair, the whole tri-state
story in two bits. That type and the handful of functions around it are
&lt;code&gt;Tamal.Bus.Serdes&lt;/code&gt;, and they are the &lt;strong&gt;last box&lt;/strong&gt; in the engine. This post
opens it.&lt;/p&gt;
&lt;p&gt;It is the leaf nearest the wire and the smallest of all of them —
fifty-four lines, no clock, no state, every function a pure combinational
map from a byte or an index to the drive on four pins for one bus beat.
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;Post 3’s&lt;/a&gt; &lt;code&gt;beatLanes&lt;/code&gt; reached into it three times without once looking
inside: &lt;code&gt;hiZ&lt;/code&gt; to release the bus for a &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;tarBeat&lt;/code&gt; to turn it around,
&lt;code&gt;serializeX1&lt;/code&gt; to lay a byte out bit by bit for a &lt;code&gt;PUT&lt;/code&gt;. &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;Post 1’s&lt;/a&gt;
&lt;code&gt;State&lt;/code&gt; carried a &lt;code&gt;Lanes&lt;/code&gt; in a field and idled it to &lt;code&gt;hiZ&lt;/code&gt; at reset; &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;post
2’s&lt;/a&gt; &lt;code&gt;startPut&lt;/code&gt; primed the first beat with &lt;code&gt;serializeX1 byte !! 0&lt;/code&gt; as
it armed a transfer. We have been leaning on this module since before we
could name it. Now we read how two bits carry a whole bus.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-compute/&quot;&gt;compute post&lt;/a&gt; ended on the one leaf still shut, and it named
that leaf by what it &lt;em&gt;does&lt;/em&gt; rather than what it &lt;em&gt;is&lt;/em&gt;: the drive on the lanes
when a &lt;code&gt;PUT&lt;/code&gt; walked the beats, the turnaround when the bus changed hands, a
byte becoming MSB-first bits on &lt;code&gt;IO[0]&lt;/code&gt; — all of it, it promised, built
out of a single tiny type, a &lt;code&gt;(value, enable)&lt;/code&gt; pair, the whole tri-state
story in two bits. That type and the handful of functions around it are
&lt;code&gt;Tamal.Bus.Serdes&lt;/code&gt;, and they are the &lt;strong&gt;last box&lt;/strong&gt; in the engine. This post
opens it.&lt;/p&gt;
&lt;p&gt;It is the leaf nearest the wire and the smallest of all of them —
fifty-four lines, no clock, no state, every function a pure combinational
map from a byte or an index to the drive on four pins for one bus beat.
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;Post 3’s&lt;/a&gt; &lt;code&gt;beatLanes&lt;/code&gt; reached into it three times without once looking
inside: &lt;code&gt;hiZ&lt;/code&gt; to release the bus for a &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;tarBeat&lt;/code&gt; to turn it around,
&lt;code&gt;serializeX1&lt;/code&gt; to lay a byte out bit by bit for a &lt;code&gt;PUT&lt;/code&gt;. &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;Post 1’s&lt;/a&gt;
&lt;code&gt;State&lt;/code&gt; carried a &lt;code&gt;Lanes&lt;/code&gt; in a field and idled it to &lt;code&gt;hiZ&lt;/code&gt; at reset; &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;post
2’s&lt;/a&gt; &lt;code&gt;startPut&lt;/code&gt; primed the first beat with &lt;code&gt;serializeX1 byte !! 0&lt;/code&gt; as
it armed a transfer. We have been leaning on this module since before we
could name it. Now we read how two bits carry a whole bus.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;the-last-door&quot;&gt;The last door&lt;/h2&gt;
&lt;p&gt;A Haskell file opens by naming itself and its door, and this one’s door is
short:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Bus.Serdes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Lane&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Lanes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; hiZ&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; driveHigh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; serializeX1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; deserializeX1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; tarBeat&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Clash.Prelude&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Seven names, and the shape of the list is worth a beat before the code
behind it. Two of the seven are &lt;em&gt;types&lt;/em&gt; — &lt;code&gt;Lane&lt;/code&gt; and &lt;code&gt;Lanes&lt;/code&gt; — and the
other five are functions that build or transform them. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC’s&lt;/a&gt;
image of a module as a wall with a door in it holds: the export list is the
complete public surface, and everything the module can do leaves through one
of these seven names. But the more telling line is the one beneath it.
&lt;code&gt;import Clash.Prelude&lt;/code&gt;, alone, with no &lt;code&gt;hiding&lt;/code&gt; clause and nothing
qualified.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-compute/&quot;&gt;compute post&lt;/a&gt; made a small drama of its imports — the ALU
hiding &lt;code&gt;And&lt;/code&gt; and &lt;code&gt;Xor&lt;/code&gt; from the prelude, pulling &lt;code&gt;Isa&lt;/code&gt; in qualified, two
scars from two naming collisions. &lt;code&gt;Serdes&lt;/code&gt; has none. Its vocabulary —
&lt;code&gt;Lane&lt;/code&gt;, &lt;code&gt;Lanes&lt;/code&gt;, &lt;code&gt;hiZ&lt;/code&gt;, &lt;code&gt;serializeX1&lt;/code&gt;, &lt;code&gt;tarBeat&lt;/code&gt; — collides with nothing
in &lt;code&gt;Clash.Prelude&lt;/code&gt; and nothing in the rest of Tamal, so it borrows no
trouble and needs no clause to keep its names clear. The lone &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;prelude
swap&lt;/a&gt; is the one line every block in the project carries, the line that
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;turns a Haskell file into a hardware description&lt;/a&gt;, and here it is the
&lt;em&gt;only&lt;/em&gt; import. This is as plain as a module header gets, and the plainness
is a fair advertisement for what is inside: no cleverness, no ceremony, just
two bits and what you can do with them.&lt;/p&gt;
&lt;h2 id=&quot;a-tuple-on-purpose&quot;&gt;A tuple, on purpose&lt;/h2&gt;
&lt;p&gt;The whole module rests on one line, and it is a &lt;code&gt;type&lt;/code&gt; synonym:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;-- |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; One I/O lane&amp;#39;s per-beat drive state: (output value, output enable).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Lane&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Bit&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;code&gt;Lane&lt;/code&gt; is a pair of bits: an &lt;strong&gt;output value&lt;/strong&gt; and an &lt;strong&gt;output enable&lt;/strong&gt;.
That is the entire type, and the choice to write it as a &lt;code&gt;type&lt;/code&gt; synonym — a
bare alias for &lt;code&gt;(Bit, Bit)&lt;/code&gt; — rather than a &lt;code&gt;newtype&lt;/code&gt; wrapper is the first
decision worth reading. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC post&lt;/a&gt; gave us the image of a module as
a wall with a door; a &lt;code&gt;newtype&lt;/code&gt; is a wall of its own, a distinct type the
compiler forces every caller to construct and unwrap. A &lt;code&gt;type&lt;/code&gt; synonym is
the opposite:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If a module is a wall with a door in it, a &lt;code&gt;type&lt;/code&gt; synonym is a door with
no wall — a nickname.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;Lane&lt;/code&gt; and &lt;code&gt;(Bit, Bit)&lt;/code&gt; are the &lt;em&gt;same&lt;/em&gt; type; &lt;code&gt;Lane&lt;/code&gt; is only a more readable
name for it. Everywhere the engine touches a lane — pattern-matching it,
reading its value with &lt;code&gt;fst&lt;/code&gt; and its enable with &lt;code&gt;snd&lt;/code&gt;, building one with
&lt;code&gt;(v, e)&lt;/code&gt; — it does so with ordinary tuple machinery, no wrapper in the way.
For a type this pervasive — carried in the &lt;code&gt;State&lt;/code&gt;, produced eight at a
time by the serialiser, compared bit-for-bit in the tests — that
transparency is exactly right: a &lt;code&gt;newtype&lt;/code&gt; would buy a little safety against
confusing a lane with some other pair and charge for it in &lt;code&gt;Lane (…)&lt;/code&gt; and
&lt;code&gt;unLane&lt;/code&gt; noise on every use. The author declined the wall because there is
nothing behind it to hide.&lt;/p&gt;
&lt;p&gt;So what do the two bits &lt;em&gt;mean&lt;/em&gt;? This is the sentence the whole eSPI story
compresses into. A wire on a shared, bidirectional bus is not simply high or
low — it has a &lt;strong&gt;third&lt;/strong&gt; state, &lt;strong&gt;released&lt;/strong&gt;, in which this end drives
nothing at all and lets the other end drive instead. That released state —
high-impedance, &lt;em&gt;hi-Z&lt;/em&gt; — is what makes a bus bidirectional: one physical
wire that the controller drives on a &lt;code&gt;PUT&lt;/code&gt; and the target drives on a &lt;code&gt;GET&lt;/code&gt;,
so long as the two never drive at once. One bit cannot express three states.
It takes a second:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The value says what to put on the wire; the enable says whether to put
anything there at all — and on a shared bus, the enable is the bit that
keeps two drivers from fighting.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When &lt;code&gt;oe = 1&lt;/code&gt;, the pad drives &lt;code&gt;value&lt;/code&gt; onto the pin. When &lt;code&gt;oe = 0&lt;/code&gt;, the pad
lets go — the pin floats, the &lt;code&gt;value&lt;/code&gt; bit is a don’t-care, and whatever is
on the other end owns the wire. Two bits, four combinations, but only
&lt;em&gt;three&lt;/em&gt; distinct behaviours: drive-0, drive-1, and release, which is &lt;code&gt;(0, 0)&lt;/code&gt;
and &lt;code&gt;(1, 0)&lt;/code&gt; both, the value ignored once the enable is low. The redundancy
is not waste; it is the shape of the thing. The value is a suggestion the
enable may veto, and on a bus where two simultaneous drivers is not a wrong
answer but a &lt;em&gt;short circuit&lt;/em&gt;, the enable is the load-bearing bit.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-tristate-1&quot;&gt;&lt;a href=&quot;#fn-tristate&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id=&quot;four-lanes-one-beat&quot;&gt;Four lanes, one beat&lt;/h2&gt;
&lt;p&gt;One lane is two bits; the bus is four lanes, so its drive state is four of
those pairs:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;-- |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; The drive state of all four I/O lanes for one SCK beat.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Lanes&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Vec&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Lane&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Lanes&lt;/code&gt; is a &lt;code&gt;Vec 4 Lane&lt;/code&gt; — a fixed-length vector of four &lt;code&gt;(Bit, Bit)&lt;/code&gt;
pairs, one per I/O wire, &lt;code&gt;IO[0]&lt;/code&gt; through &lt;code&gt;IO[3]&lt;/code&gt;, all for a &lt;em&gt;single&lt;/em&gt; bus
beat. This is the type &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;post 1’s&lt;/a&gt; &lt;code&gt;State&lt;/code&gt; held in its &lt;code&gt;lanes&lt;/code&gt; field
and the type &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;post 3’s&lt;/a&gt; &lt;code&gt;beatLanes&lt;/code&gt; returned: the complete instruction
to the pins for one tick of the serial clock, four wires at once. Eight of
these, walked one at a time, drive a byte; &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;post 3&lt;/a&gt; did the walking,
indexing &lt;code&gt;serializeX1 (shifter t) !! bi&lt;/code&gt; a beat per rising edge.&lt;/p&gt;
&lt;p&gt;Four is more than x1 needs. Single-I/O drives one wire and releases the
other three, so three of the four pairs in every &lt;code&gt;PUT&lt;/code&gt; beat are the same
released &lt;code&gt;(0, 0)&lt;/code&gt;. The width is built for a bus this module does not yet
serialise: dual-I/O uses two wires, quad all four, and &lt;code&gt;Lanes&lt;/code&gt; is sized for
the widest of them from the start. It is the same deliberate room to grow
the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;ISA post&lt;/a&gt; found in &lt;code&gt;Reg&lt;/code&gt; — a five-bit selector naming thirty-two
registers when v1 implements sixteen — the type describing the whole design
space while the code fills one corner. We will come back to that corner;
first, the two simplest values of the type.&lt;/p&gt;
&lt;p&gt;Two whole-bus constants sit right under it, and between them they are the bus
at rest and the bus asserted:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;hiZ&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Lanes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;hiZ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; repeat &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;driveHigh&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Lanes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;driveHigh &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; repeat &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;hiZ&lt;/code&gt; is all four lanes released — every enable low, the whole bus handed
off. It is the value &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;post 1’s&lt;/a&gt; &lt;code&gt;initState&lt;/code&gt; powered up into, the state
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;post 3’s&lt;/a&gt; &lt;code&gt;safePins&lt;/code&gt; slammed the bus to before a trap, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/a&gt;
idle where the engine samples rather than drives. Across the project &lt;code&gt;hiZ&lt;/code&gt;
has meant one thing, and now we can see it is not a special state at all,
only four copies of “enable off”:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;hiZ&lt;/code&gt; is hands off the bus.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;driveHigh&lt;/code&gt; is its mirror — all four enables &lt;em&gt;on&lt;/em&gt;, all four values one, the
bus actively pulled to logic high. It has exactly one use, the first clock of
a turnaround, and we will meet it there in a moment. Both are written with
&lt;code&gt;repeat&lt;/code&gt;, which fills a &lt;code&gt;Vec&lt;/code&gt; with copies of one element; neither says &lt;em&gt;how
many&lt;/em&gt;, because neither has to. The count lives in the type — &lt;code&gt;hiZ :: Lanes&lt;/code&gt; is &lt;code&gt;Vec 4&lt;/code&gt;, so &lt;code&gt;repeat&lt;/code&gt; makes four — the same
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;widths-live-in-the-type&lt;/a&gt; discipline that let &lt;code&gt;bitCoerce&lt;/code&gt; pack a word
by its field sizes.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-repeat-1&quot;&gt;&lt;a href=&quot;#fn-repeat&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt; Change &lt;code&gt;Lanes&lt;/code&gt; to &lt;code&gt;Vec 2&lt;/code&gt; for a narrower bus and
these two definitions do not move; the &lt;code&gt;4&lt;/code&gt; they never mention becomes a &lt;code&gt;2&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;a-byte-eight-beats&quot;&gt;A byte, eight beats&lt;/h2&gt;
&lt;p&gt;Now the function the module is named for. A byte goes in; the drive for all
eight of its beats comes out:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;serializeX1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Vec&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Lanes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;serializeX1 b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; map beat &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;unpack b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Vec&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;  beat&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Lanes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  beat bit&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;bit&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; :&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; :&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; :&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; :&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nil&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;type is half the documentation&lt;/a&gt;: a &lt;code&gt;BitVector 8&lt;/code&gt; in, a &lt;code&gt;Vec 8 Lanes&lt;/code&gt; out — one byte, mapped to eight beats, each beat a full four-lane
drive. Not one lane value but the &lt;em&gt;entire schedule&lt;/em&gt; for the byte, computed in
one go, which is what a combinational block does: no loop, no clock, just a
map laying eight beats down side by side. &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;Post 2’s&lt;/a&gt; &lt;code&gt;startPut&lt;/code&gt; took
&lt;code&gt;serializeX1 byte !! 0&lt;/code&gt; to prime the very first beat as it armed a transfer;
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;post 3’s&lt;/a&gt; &lt;code&gt;beatLanes&lt;/code&gt; took &lt;code&gt;serializeX1 (shifter t) !! bi&lt;/code&gt; to fetch
beat &lt;code&gt;bi&lt;/code&gt; as the bus walked forward. Both were indexing into &lt;em&gt;this&lt;/em&gt; vector,
the eight-beat schedule this line builds.&lt;/p&gt;
&lt;p&gt;Two moves make it, and we have read both before. &lt;code&gt;unpack b :: Vec 8 Bit&lt;/code&gt;
takes the opaque eight-wire byte and lays it out as eight individual &lt;code&gt;Bit&lt;/code&gt;s
— the same &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;structural, zero-cost reinterpretation&lt;/a&gt; the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt;
opened its fold with, and with the same convention riding on it: index &lt;code&gt;0&lt;/code&gt; of
the vector is the &lt;strong&gt;most significant bit&lt;/strong&gt;, so the vector runs MSB-first.
That convention is not decorative. eSPI, like SPI and SMBus before it, clocks
the most significant bit onto the wire first, so beat &lt;code&gt;0&lt;/code&gt; must carry bit &lt;code&gt;7&lt;/code&gt;,
and &lt;code&gt;unpack&lt;/code&gt;’s ordering delivers exactly that with no reversal.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-vec-1&quot;&gt;&lt;a href=&quot;#fn-vec&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Then &lt;code&gt;map beat&lt;/code&gt; turns each of those eight bits into a full &lt;code&gt;Lanes&lt;/code&gt;. And
&lt;code&gt;beat&lt;/code&gt; is the two-bit story spelled out one more time: for a data bit &lt;code&gt;bit&#39;&lt;/code&gt;,
it drives &lt;code&gt;IO[0]&lt;/code&gt; with &lt;code&gt;(bit&#39;, 1)&lt;/code&gt; — the value is the bit, the enable is on
— and releases &lt;code&gt;IO[1]&lt;/code&gt;, &lt;code&gt;IO[2]&lt;/code&gt;, &lt;code&gt;IO[3]&lt;/code&gt; with three &lt;code&gt;(0, 0)&lt;/code&gt;s. That is
single-I/O in one line: &lt;strong&gt;one wire driven, three let go&lt;/strong&gt;, the data on
&lt;code&gt;IO[0]&lt;/code&gt; and the rest of the bus floating. Fold the map over the eight bits
and you have the byte’s whole life on the pins — eight beats, each driving
one bit MSB-first on one wire, the other three released the entire time —
which is precisely what the loopback and drive tests will check, and what
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;post 3&lt;/a&gt; spent its beats emitting.&lt;/p&gt;
&lt;h2 id=&quot;the-inverse-the-engine-never-calls&quot;&gt;The inverse the engine never calls&lt;/h2&gt;
&lt;p&gt;The serialiser has an inverse, and it is even shorter:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;deserializeX1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Vec&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;deserializeX1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; pack&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eight sampled bits in, one byte out, and the body is a single word: &lt;code&gt;pack&lt;/code&gt;,
the exact partner of the &lt;code&gt;unpack&lt;/code&gt; a few lines up. Where &lt;code&gt;unpack&lt;/code&gt; scattered a
byte into MSB-first bits, &lt;code&gt;pack&lt;/code&gt; gathers MSB-first bits back into a byte, and
because the two are inverse halves of the same &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;&lt;code&gt;bitCoerce&lt;/code&gt;
reinterpretation&lt;/a&gt;, &lt;code&gt;pack . unpack&lt;/code&gt; is the identity by construction. Read
against &lt;code&gt;serializeX1&lt;/code&gt;, &lt;code&gt;deserializeX1&lt;/code&gt; is the undo: it takes the eight bits a
&lt;code&gt;GET&lt;/code&gt; sampled off the wire, MSB-first, and assembles the byte they spell.&lt;/p&gt;
&lt;p&gt;There is a quiet surprise here, though, and it is worth stopping on: &lt;strong&gt;the
engine never calls this function.&lt;/strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;Post 3’s&lt;/a&gt; &lt;code&gt;sampleGet&lt;/code&gt; did the
receiving, and it did not batch eight bits and &lt;code&gt;pack&lt;/code&gt; them — it shifted each
sampled bit into an accumulator by hand, one per rising edge — &lt;code&gt;shifter `shiftL` 1&lt;/code&gt; to open a slot at the bottom, an &lt;code&gt;.|.&lt;/code&gt; to drop the new bit in —
interleaved with the beat loop it lived inside. The engine assembles a
received byte incrementally because it &lt;em&gt;receives&lt;/em&gt; incrementally, a bit at a
time across eight beats, with nowhere to hold a &lt;code&gt;Vec 8 Bit&lt;/code&gt; in the meanwhile.
So &lt;code&gt;deserializeX1&lt;/code&gt; describes the same result by a different route than the
engine actually takes.&lt;/p&gt;
&lt;p&gt;Why write it, then, if nothing on the fabric runs it? Because it earns its
place as a &lt;em&gt;specification&lt;/em&gt; rather than as code on a path. It names the
inverse, and naming it is what lets the round-trip law — serialise a byte,
sample it back, deserialise, and get the byte — be &lt;em&gt;stated&lt;/em&gt; and a property
test check it, pinning the MSB-first convention down from both ends at once.
It is a move the project has made before with a name it never quite uses: the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;ISA’s&lt;/a&gt; &lt;code&gt;OpcodeUnimplemented&lt;/code&gt;, declared to reserve a vocabulary the
decoders never return; the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;trace module’s&lt;/a&gt; &lt;code&gt;encodeRecord&lt;/code&gt;, which never
runs on the fabric and exists only so the engine’s inline packing has a
readable reference to be proven equal to. &lt;code&gt;deserializeX1&lt;/code&gt; is that kind of
honest surplus — a line that is not on the critical path but makes a promise
the critical path can be checked against.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-deser-1&quot;&gt;&lt;a href=&quot;#fn-deser&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id=&quot;drive-once-then-let-go&quot;&gt;Drive once, then let go&lt;/h2&gt;
&lt;p&gt;The last function is the turnaround, and it is a single conditional:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;tarBeat&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Lanes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;tarBeat i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; then&lt;/span&gt;&lt;span&gt; driveHigh &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;else&lt;/span&gt;&lt;span&gt; hiZ&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;tarBeat&lt;/code&gt; takes a beat index and answers with a drive: on beat &lt;code&gt;0&lt;/code&gt; it returns
&lt;code&gt;driveHigh&lt;/code&gt; — all four lanes pulled high — and on every beat after it
returns &lt;code&gt;hiZ&lt;/code&gt; — all four released. That is the whole of the &lt;strong&gt;turnaround&lt;/strong&gt;,
the &lt;code&gt;TAR&lt;/code&gt; the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;bus post&lt;/a&gt; fetched as &lt;code&gt;tarBeat bi&lt;/code&gt;, and it is the
choreography an eSPI bus performs whenever the wire changes hands. Between a
command the controller drove and a response the target will drive, the two
ends have to swap roles without ever both driving at once; the turnaround is
the handshake in the middle, a beat or two where the outgoing driver gives
the bus a defined push and then lets go, so the line is never left floating
and undefined at the instant of handover.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-tar-1&quot;&gt;&lt;a href=&quot;#fn-tar&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;driveHigh&lt;/code&gt;’s one use is right here: it is that defined push, the first
turnaround clock that drives the bus to a known high before the release. And
after that single beat, every later index falls through to &lt;code&gt;hiZ&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Drive the turnaround clock once, then let go.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The type is &lt;code&gt;Unsigned 4 -&amp;gt; Lanes&lt;/code&gt;, and the argument is the same beat index
&lt;code&gt;beatLanes&lt;/code&gt; threaded in — but &lt;code&gt;tarBeat&lt;/code&gt; reads only whether it is zero. The
function is total over all sixteen values an &lt;code&gt;Unsigned 4&lt;/code&gt; can take, and
fifteen of them give the same answer; only beat &lt;code&gt;0&lt;/code&gt; is special. It is a
small, blunt shape for a small, blunt job: one beat of drive, then the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;&lt;code&gt;hiZ&lt;/code&gt;&lt;/a&gt; the rest of the module already meant by release.&lt;/p&gt;
&lt;h2 id=&quot;three-drives-one-field&quot;&gt;Three drives, one field&lt;/h2&gt;
&lt;p&gt;Step back from the four functions and the module snaps into a single picture
— because &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;post 3’s&lt;/a&gt; &lt;code&gt;beatLanes&lt;/code&gt;, the one place all of this was used,
is a three-way case whose three arms are three of the names we have just
read:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;beatLanes t bi &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; pending t &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  PendGet&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; hiZ&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  PendTar&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;   -&amp;gt;&lt;/span&gt;&lt;span&gt; tarBeat bi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  _         &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; serializeX1 &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;shifter t&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; !!&lt;/span&gt;&lt;span&gt; bi&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three directions off one &lt;code&gt;pending&lt;/code&gt; field. A &lt;code&gt;GET&lt;/code&gt; releases the bus so the
other end can drive it — &lt;code&gt;hiZ&lt;/code&gt;. A &lt;code&gt;TAR&lt;/code&gt; turns it around — &lt;code&gt;tarBeat bi&lt;/code&gt;.
Anything else is a &lt;code&gt;PUT&lt;/code&gt;, and it serialises — &lt;code&gt;serializeX1 (shifter t) !! bi&lt;/code&gt;. The whole of &lt;code&gt;Serdes&lt;/code&gt; exists to answer that case: &lt;code&gt;hiZ&lt;/code&gt; is the release
arm, &lt;code&gt;tarBeat&lt;/code&gt; the turnaround arm, &lt;code&gt;serializeX1&lt;/code&gt; the drive arm, and &lt;code&gt;Lane&lt;/code&gt; is
the two-bit alphabet all three answer in. Every eSPI direction the engine can
point the bus is one of these three drives, and each is this module handing
back four &lt;code&gt;(value, enable)&lt;/code&gt; pairs.&lt;/p&gt;
&lt;figure class=&quot;srd-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;srd&quot; viewBox=&quot;0 0 760 360&quot; role=&quot;img&quot; aria-labelledby=&quot;srd-t srd-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;srd-t&quot;&gt;The three lane drives Serdes provides for one bus beat&lt;/title&gt;
&lt;desc id=&quot;srd-d&quot;&gt;Three drive modes shown as rows of four cells, one cell per I/O lane IO0 through IO3, each cell holding a value-and-enable pair. The PUT row drives the data bit on IO0 with enable one and releases IO1, IO2 and IO3 with zero-zero. The GET row releases all four lanes with zero-zero and samples IO1, marked with a dashed outline and an arrow. The TAR beat-zero row drives all four lanes high with one-one. Driven cells, where the enable is one, are drawn in the accent colour.&lt;/desc&gt;
&lt;style&gt;
.srd{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.srd .cd{fill:var(--bg-dim);stroke:var(--accent);stroke-width:2.5}
.srd .cz{fill:var(--bg-main);stroke:var(--fg-dim);stroke-width:1.6}
.srd .cs{fill:var(--bg-main);stroke:var(--accent);stroke-width:2;stroke-dasharray:5 3}
.srd .hd{fill:var(--fg-dim);font-family:var(--mono);font-size:13px}
.srd .md{fill:var(--fg-main);font-family:var(--mono);font-size:15px}
.srd .sub{fill:var(--fg-dim);font-family:var(--sans);font-size:11.5px}
.srd .vd{fill:var(--accent);font-family:var(--mono);font-size:13px}
.srd .vz{fill:var(--fg-dim);font-family:var(--mono);font-size:13px}
.srd .vs{fill:var(--accent);font-family:var(--mono);font-size:13px}
.srd .sw{stroke:var(--accent);stroke-width:2;fill:none}
.srd .sl{fill:var(--accent);font-family:var(--sans);font-size:11.5px}
.srd .ah{fill:var(--accent)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;srd-a&quot; markerWidth=&quot;9&quot; markerHeight=&quot;7&quot; refX=&quot;7&quot; refY=&quot;3.5&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L7,3.5 L0,7 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;!-- column headers --&gt;
&lt;text class=&quot;hd&quot; x=&quot;230&quot; y=&quot;40&quot; text-anchor=&quot;middle&quot;&gt;IO[0]&lt;/text&gt;
&lt;text class=&quot;hd&quot; x=&quot;360&quot; y=&quot;40&quot; text-anchor=&quot;middle&quot;&gt;IO[1]&lt;/text&gt;
&lt;text class=&quot;hd&quot; x=&quot;490&quot; y=&quot;40&quot; text-anchor=&quot;middle&quot;&gt;IO[2]&lt;/text&gt;
&lt;text class=&quot;hd&quot; x=&quot;620&quot; y=&quot;40&quot; text-anchor=&quot;middle&quot;&gt;IO[3]&lt;/text&gt;
&lt;!-- PUT --&gt;
&lt;text class=&quot;md&quot; x=&quot;22&quot; y=&quot;96&quot;&gt;PUT&lt;/text&gt;
&lt;text class=&quot;sub&quot; x=&quot;22&quot; y=&quot;114&quot;&gt;drive IO[0]&lt;/text&gt;
&lt;rect class=&quot;cd&quot; x=&quot;172&quot; y=&quot;77&quot; width=&quot;116&quot; height=&quot;46&quot; rx=&quot;7&quot;/&gt;
&lt;rect class=&quot;cz&quot; x=&quot;302&quot; y=&quot;77&quot; width=&quot;116&quot; height=&quot;46&quot; rx=&quot;7&quot;/&gt;
&lt;rect class=&quot;cz&quot; x=&quot;432&quot; y=&quot;77&quot; width=&quot;116&quot; height=&quot;46&quot; rx=&quot;7&quot;/&gt;
&lt;rect class=&quot;cz&quot; x=&quot;562&quot; y=&quot;77&quot; width=&quot;116&quot; height=&quot;46&quot; rx=&quot;7&quot;/&gt;
&lt;text class=&quot;vd&quot; x=&quot;230&quot; y=&quot;105&quot; text-anchor=&quot;middle&quot;&gt;(bit, 1)&lt;/text&gt;
&lt;text class=&quot;vz&quot; x=&quot;360&quot; y=&quot;105&quot; text-anchor=&quot;middle&quot;&gt;(0, 0)&lt;/text&gt;
&lt;text class=&quot;vz&quot; x=&quot;490&quot; y=&quot;105&quot; text-anchor=&quot;middle&quot;&gt;(0, 0)&lt;/text&gt;
&lt;text class=&quot;vz&quot; x=&quot;620&quot; y=&quot;105&quot; text-anchor=&quot;middle&quot;&gt;(0, 0)&lt;/text&gt;
&lt;!-- GET --&gt;
&lt;text class=&quot;md&quot; x=&quot;22&quot; y=&quot;201&quot;&gt;GET&lt;/text&gt;
&lt;text class=&quot;sub&quot; x=&quot;22&quot; y=&quot;219&quot;&gt;release all&lt;/text&gt;
&lt;rect class=&quot;cz&quot; x=&quot;172&quot; y=&quot;182&quot; width=&quot;116&quot; height=&quot;46&quot; rx=&quot;7&quot;/&gt;
&lt;rect class=&quot;cs&quot; x=&quot;302&quot; y=&quot;182&quot; width=&quot;116&quot; height=&quot;46&quot; rx=&quot;7&quot;/&gt;
&lt;rect class=&quot;cz&quot; x=&quot;432&quot; y=&quot;182&quot; width=&quot;116&quot; height=&quot;46&quot; rx=&quot;7&quot;/&gt;
&lt;rect class=&quot;cz&quot; x=&quot;562&quot; y=&quot;182&quot; width=&quot;116&quot; height=&quot;46&quot; rx=&quot;7&quot;/&gt;
&lt;text class=&quot;vz&quot; x=&quot;230&quot; y=&quot;210&quot; text-anchor=&quot;middle&quot;&gt;(0, 0)&lt;/text&gt;
&lt;text class=&quot;vs&quot; x=&quot;360&quot; y=&quot;210&quot; text-anchor=&quot;middle&quot;&gt;(0, 0)&lt;/text&gt;
&lt;text class=&quot;vz&quot; x=&quot;490&quot; y=&quot;210&quot; text-anchor=&quot;middle&quot;&gt;(0, 0)&lt;/text&gt;
&lt;text class=&quot;vz&quot; x=&quot;620&quot; y=&quot;210&quot; text-anchor=&quot;middle&quot;&gt;(0, 0)&lt;/text&gt;
&lt;!-- sample arrow into IO1 --&gt;
&lt;line class=&quot;sw&quot; x1=&quot;360&quot; y1=&quot;260&quot; x2=&quot;360&quot; y2=&quot;231&quot; marker-end=&quot;url(#srd-a)&quot;/&gt;
&lt;text class=&quot;sl&quot; x=&quot;360&quot; y=&quot;274&quot; text-anchor=&quot;middle&quot;&gt;sample IO[1]&lt;/text&gt;
&lt;!-- TAR --&gt;
&lt;text class=&quot;md&quot; x=&quot;22&quot; y=&quot;306&quot;&gt;TAR&lt;/text&gt;
&lt;text class=&quot;sub&quot; x=&quot;22&quot; y=&quot;324&quot;&gt;beat 0&lt;/text&gt;
&lt;rect class=&quot;cd&quot; x=&quot;172&quot; y=&quot;287&quot; width=&quot;116&quot; height=&quot;46&quot; rx=&quot;7&quot;/&gt;
&lt;rect class=&quot;cd&quot; x=&quot;302&quot; y=&quot;287&quot; width=&quot;116&quot; height=&quot;46&quot; rx=&quot;7&quot;/&gt;
&lt;rect class=&quot;cd&quot; x=&quot;432&quot; y=&quot;287&quot; width=&quot;116&quot; height=&quot;46&quot; rx=&quot;7&quot;/&gt;
&lt;rect class=&quot;cd&quot; x=&quot;562&quot; y=&quot;287&quot; width=&quot;116&quot; height=&quot;46&quot; rx=&quot;7&quot;/&gt;
&lt;text class=&quot;vd&quot; x=&quot;230&quot; y=&quot;315&quot; text-anchor=&quot;middle&quot;&gt;(1, 1)&lt;/text&gt;
&lt;text class=&quot;vd&quot; x=&quot;360&quot; y=&quot;315&quot; text-anchor=&quot;middle&quot;&gt;(1, 1)&lt;/text&gt;
&lt;text class=&quot;vd&quot; x=&quot;490&quot; y=&quot;315&quot; text-anchor=&quot;middle&quot;&gt;(1, 1)&lt;/text&gt;
&lt;text class=&quot;vd&quot; x=&quot;620&quot; y=&quot;315&quot; text-anchor=&quot;middle&quot;&gt;(1, 1)&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The three drives &lt;code&gt;beatLanes&lt;/code&gt; selects, each a full &lt;code&gt;Lanes&lt;/code&gt; of four &lt;code&gt;(value, enable)&lt;/code&gt; pairs. A &lt;code&gt;PUT&lt;/code&gt; beat drives the data bit on &lt;code&gt;IO[0]&lt;/code&gt; and releases &lt;code&gt;IO[1..3]&lt;/code&gt;; a &lt;code&gt;GET&lt;/code&gt; beat releases all four and samples &lt;code&gt;IO[1]&lt;/code&gt;; a &lt;code&gt;TAR&lt;/code&gt; beat 0 drives all four high before the release that follows. The shaded cells are the driven ones --- enable high --- and everywhere else the enable is low and the value a don&#39;t-care. One byte is eight &lt;code&gt;PUT&lt;/code&gt; beats, MSB first; only &lt;code&gt;IO[0]&lt;/code&gt; is ever driven in x1.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The three modes are the same four lanes read three ways. Look only at the
enables: a &lt;code&gt;PUT&lt;/code&gt; turns exactly one on (&lt;code&gt;IO[0]&lt;/code&gt;), a &lt;code&gt;TAR&lt;/code&gt; beat turns all four
on for its one clock, and a &lt;code&gt;GET&lt;/code&gt; turns them all off and reads &lt;code&gt;IO[1]&lt;/code&gt;
instead of driving it. The values matter only where an enable is high — the
shaded cells — which is the two-bit story made visual: the column that
carries data is the column whose enable let it through.&lt;/p&gt;
&lt;h2 id=&quot;one-corner-of-a-wider-bus&quot;&gt;One corner of a wider bus&lt;/h2&gt;
&lt;p&gt;The module’s doc-comment states the rules the code obeys, and reading them
against what we have seen names the one corner of eSPI this file actually
serialises:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;x1 rules: PUT drives the data bit on IO[0], MSB first, with IO[1..3]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;tri-stated; GET samples IO[1] with all engine drivers tri-stated.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Dual/quad maps land in Phase 3.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every claim in that comment is now a line we have read. &lt;code&gt;PUT&lt;/code&gt; drives &lt;code&gt;IO[0]&lt;/code&gt;
MSB-first — &lt;code&gt;serializeX1&lt;/code&gt;’s &lt;code&gt;(bit&#39;, 1)&lt;/code&gt; on lane zero, fed by &lt;code&gt;unpack&lt;/code&gt;’s
MSB-first order. &lt;code&gt;IO[1..3]&lt;/code&gt; tri-stated — its three &lt;code&gt;(0, 0)&lt;/code&gt;s. &lt;code&gt;GET&lt;/code&gt; samples
&lt;code&gt;IO[1]&lt;/code&gt; with all drivers released — &lt;code&gt;hiZ&lt;/code&gt;, and &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;post 3’s&lt;/a&gt; &lt;code&gt;sampleGet&lt;/code&gt;
reading lane one. The &lt;code&gt;x1&lt;/code&gt; in the function’s name is the promise the comment
keeps: this is &lt;strong&gt;single-I/O&lt;/strong&gt;, one data wire in each direction, the simplest
of the eSPI width modes.&lt;/p&gt;
&lt;p&gt;The other modes are named and deferred in a single clause — &lt;em&gt;dual/quad maps
land in Phase 3&lt;/em&gt; — and that deferral is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;ISA post’s&lt;/a&gt; honesty turned
on the serialiser. &lt;code&gt;Lanes&lt;/code&gt; is a &lt;code&gt;Vec 4&lt;/code&gt; because the bus has four data wires
and dual-I/O drives two of them, quad all four; the type is sized for the
widest mode from the first line. But &lt;code&gt;serializeX1&lt;/code&gt; fills only the x1 corner,
driving &lt;code&gt;IO[0]&lt;/code&gt; and no other, exactly as &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;&lt;code&gt;decodeConfig&lt;/code&gt;&lt;/a&gt; accepted only
the one controller-x1-20MHz configuration and named every wider mode an error
rather than pretending to implement it. The space is drawn in full — four
lanes, the modes that would use them — and one corner of it is wired,
honestly, the rest marked reserved. A &lt;code&gt;serializeX2&lt;/code&gt; and &lt;code&gt;serializeX4&lt;/code&gt; are the
shape of the Phase 3 work; when they come they will produce the same &lt;code&gt;Vec 8 Lanes&lt;/code&gt;, only with two or four enables high per beat instead of one.&lt;/p&gt;
&lt;h2 id=&quot;the-tests&quot;&gt;The tests&lt;/h2&gt;
&lt;p&gt;Three tests, and between them they hold down every claim the module makes. A
small helper sets them up:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;io0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Lanes&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;io0 lanes &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; fst &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lanes &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;!!&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;io0&lt;/code&gt; reads one thing off a beat: the &lt;em&gt;value&lt;/em&gt; bit of lane zero, &lt;code&gt;fst&lt;/code&gt; of
&lt;code&gt;IO[0]&lt;/code&gt;’s pair — the bit &lt;code&gt;serializeX1&lt;/code&gt; drives. It is the sampler the tests
use to look at what a &lt;code&gt;PUT&lt;/code&gt; put on the wire.&lt;/p&gt;
&lt;p&gt;The first test is the round trip, a &lt;a rel=&quot;external&quot; href=&quot;https://hedgehog.qa&quot;&gt;Hedgehog&lt;/a&gt; property:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;x1 serialize/deserialize round-trips (loopback)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genByte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  deserializeX1 &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;map io0 &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;serializeX1 b&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ===&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read it inside out. &lt;code&gt;serializeX1 b&lt;/code&gt; drives the byte into eight beats; &lt;code&gt;map io0&lt;/code&gt; reads &lt;code&gt;IO[0]&lt;/code&gt;’s value back out of each beat, recovering the eight driven
bits as a &lt;code&gt;Vec 8 Bit&lt;/code&gt;; &lt;code&gt;deserializeX1&lt;/code&gt; packs them into a byte. The law is
that this returns &lt;code&gt;b&lt;/code&gt; unchanged, for every byte &lt;a rel=&quot;external&quot; href=&quot;https://hedgehog.qa&quot;&gt;Hedgehog&lt;/a&gt; can
draw. The comment calls it &lt;em&gt;loopback&lt;/em&gt;, and the name is the picture: on a real
loopback the driven &lt;code&gt;IO[0]&lt;/code&gt; is wired round to the sampled &lt;code&gt;IO[1]&lt;/code&gt;, so reading
&lt;code&gt;IO[0]&lt;/code&gt;’s value stands in for sampling what came back. It is
serialise-then-sample-then-deserialise closing to the identity — the
MSB-first convention proven consistent from the drive end to the receive end,
because a single reversed bit anywhere in that chain would make some byte come
back wrong and the property would shrink to it.&lt;/p&gt;
&lt;p&gt;The second test drives one specific, well-chosen byte and checks the pins
directly:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;let&lt;/span&gt;&lt;span&gt; beats &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; serializeX1 &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0b1000_0000&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;     --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; MSB set only&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;io0 &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;head beats&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @?=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;                   --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; first beat carries the MSB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;io0 &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;last beats&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @?=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;map &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;\&lt;/span&gt;&lt;span&gt;l &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; snd &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;l &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;!!&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; beats &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;@?=&lt;/span&gt;&lt;span&gt; repeat &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;0b1000_0000&lt;/code&gt; is the sharpest possible probe of bit order: only the most
significant bit is set. If serialisation is MSB-first, the &lt;em&gt;first&lt;/em&gt; beat must
drive a one and the &lt;em&gt;last&lt;/em&gt; must drive a zero — which is exactly what &lt;code&gt;io0 (head beats) @?= 1&lt;/code&gt; and &lt;code&gt;io0 (last beats) @?= 0&lt;/code&gt; assert. Flip the convention
to LSB-first and the two swap, and the case goes red. The third line checks
the other half of the x1 rule: &lt;code&gt;snd (l !! 1)&lt;/code&gt; is &lt;code&gt;IO[1]&lt;/code&gt;’s &lt;em&gt;enable&lt;/em&gt;, and
mapping it over every beat must give &lt;code&gt;repeat 0&lt;/code&gt; — lane one released on all
eight beats, never driven. One byte proves both that the bits leave in the
right order and that the idle lanes stay out of the way.&lt;/p&gt;
&lt;p&gt;The last test pins the turnaround:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;tarBeat &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @?=&lt;/span&gt;&lt;span&gt; repeat &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; first clock drives all high&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;tarBeat &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @?=&lt;/span&gt;&lt;span&gt; repeat &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; then releases&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;tarBeat &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @?=&lt;/span&gt;&lt;span&gt; repeat &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Beat &lt;code&gt;0&lt;/code&gt; is &lt;code&gt;driveHigh&lt;/code&gt;, beats &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;5&lt;/code&gt; are &lt;code&gt;hiZ&lt;/code&gt; — the “drive once,
then let go” shape asserted on both sides of the only boundary the function
has. &lt;code&gt;tarBeat 5&lt;/code&gt; is there to make the point that &lt;em&gt;every&lt;/em&gt; beat past the first
releases, not merely the second; the function has one special case and
fifteen identical ones, and the test samples both kinds. Three small
assertions, and the two-bit vocabulary is checked in all three of the
directions the bus can face.&lt;/p&gt;
&lt;h2 id=&quot;what-we-read&quot;&gt;What we read&lt;/h2&gt;
&lt;p&gt;The last box, opened — and the smallest. &lt;code&gt;Tamal.Bus.Serdes&lt;/code&gt; is one
transparent type and a handful of functions over it. &lt;code&gt;Lane&lt;/code&gt; is a &lt;code&gt;(value, enable)&lt;/code&gt; pair, a &lt;code&gt;type&lt;/code&gt; synonym with no wall around it because the engine
handles it as the bare tuple it is; the value says what to drive and the
enable says whether to drive at all, and on a shared bidirectional bus that
second bit — the one that chooses between driving and releasing — is the
whole of tri-state, three wire states carried in two bits. &lt;code&gt;Lanes&lt;/code&gt; is four of
them, one per I/O wire, sized for a four-wide bus the module does not yet
fill. &lt;code&gt;hiZ&lt;/code&gt; and &lt;code&gt;driveHigh&lt;/code&gt; are the bus released and the bus asserted,
&lt;code&gt;repeat&lt;/code&gt; taking their width from the type. &lt;code&gt;serializeX1&lt;/code&gt; lays a byte out
MSB-first into eight beats, driving &lt;code&gt;IO[0]&lt;/code&gt; and releasing the rest —
single-I/O in one &lt;code&gt;map&lt;/code&gt; — and &lt;code&gt;deserializeX1&lt;/code&gt; is its &lt;code&gt;pack&lt;/code&gt; inverse, kept
for the round-trip law even though the engine, receiving a bit at a time,
never calls it. &lt;code&gt;tarBeat&lt;/code&gt; drives one turnaround clock high and then lets go.
And &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;post 3’s&lt;/a&gt; &lt;code&gt;beatLanes&lt;/code&gt; was these three drives all along — a &lt;code&gt;GET&lt;/code&gt;’s
release, a &lt;code&gt;TAR&lt;/code&gt;’s turnaround, a &lt;code&gt;PUT&lt;/code&gt;’s serialise — three eSPI directions
off one field, each a fistful of &lt;code&gt;(value, enable)&lt;/code&gt; pairs. The tests close the
loop from drive to sample, pin the MSB-first order to a single-bit probe, and
stake down the turnaround.&lt;/p&gt;
&lt;p&gt;And with that the descent is done. We read the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;engine’s map&lt;/a&gt; first,
then watched it &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch an instruction&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;run its wire&lt;/a&gt;,
holding every leaf shut — and then we opened them, each in the order the
engine first reached for it. &lt;code&gt;decode&lt;/code&gt; and the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;instruction set&lt;/a&gt;; the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-regfile/&quot;&gt;sixteen registers&lt;/a&gt; its operands named; the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-compute/&quot;&gt;compute&lt;/a&gt; those
registers fed, &lt;code&gt;dataResult&lt;/code&gt; and &lt;code&gt;branchTaken&lt;/code&gt;; and now the serialiser nearest
the wire, &lt;code&gt;serializeX1&lt;/code&gt; and &lt;code&gt;tarBeat&lt;/code&gt; and the two-bit &lt;code&gt;Lane&lt;/code&gt; they speak. The
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; had its own post before this arc began, and &lt;code&gt;AW&lt;/code&gt; and &lt;code&gt;RW&lt;/code&gt; were read
inline with the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt;; every other black box the three outside-in
posts named has now been opened and read whole. The engine is open entire —
map and every piece.&lt;/p&gt;
&lt;p&gt;What is left is not inside the engine but around it. &lt;code&gt;step&lt;/code&gt; is pure: it takes
a &lt;code&gt;State&lt;/code&gt; and an input and returns a &lt;code&gt;State&lt;/code&gt;, a &lt;code&gt;BusOut&lt;/code&gt;, and maybe a trace
word, and every &lt;code&gt;Lanes&lt;/code&gt; it computes is still just numbers — four &lt;code&gt;(value, enable)&lt;/code&gt; pairs, a &lt;em&gt;description&lt;/em&gt; of a drive, not a drive. Something has to
carry those pairs the last step out: take each lane’s enable and its value and
turn them into a real bidirectional pad, a wire that is driven when the enable
is high, floats when it is low, and is sampled back into the engine’s inputs
when the other end drives. That is the impure shell — &lt;code&gt;Tamal.Io&lt;/code&gt; and its
tri-state buffers, &lt;code&gt;Tamal.Top&lt;/code&gt; wiring the engine to the clock and the pins,
the &lt;code&gt;Tamal.Board.*&lt;/code&gt; layers that pin it to real silicon — and it is where the
&lt;code&gt;(value, enable)&lt;/code&gt; pair we have just called the whole story meets the FPGA
primitive that makes it physical. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;introduction&lt;/a&gt; warned that the
pins are the hard part; the engine has spent seven posts being precise about
&lt;em&gt;what&lt;/em&gt; to drive and &lt;em&gt;when&lt;/em&gt;, so that the shell can be left the narrower,
sharper job of driving it. That shell is a story for another day — a later
batch, out to the pins.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-tristate&quot;&gt;
&lt;p&gt;The three states a shared wire can take — driven high, driven
low, and released — are electrical, not logical, and the third is the one a
single logic bit cannot name. A released output is &lt;em&gt;high-impedance&lt;/em&gt;: the
driver disconnects, presenting so large a resistance that it neither pulls the
line high nor low but lets another driver anywhere on the net decide the
level. A bidirectional bus is exactly a set of wires more than one device may
drive &lt;em&gt;at different times&lt;/em&gt;, and its one unbreakable rule is that no two drive
at once — two enabled outputs fighting over a line, one high and one low, is
a near short from supply to ground, the contention that overheats pads and
corrupts every bit. The enable bit is what enforces the rule: it is the wire
that goes to a pad’s output-enable, &lt;code&gt;1&lt;/code&gt; to drive and &lt;code&gt;0&lt;/code&gt; to float, and a
protocol like eSPI is in large part a discipline for ensuring exactly one end
has its enable high on any given lane at any given beat. The &lt;code&gt;(value, enable)&lt;/code&gt;
pair is that discipline in its smallest form — and it is also, not by
coincidence, the interface of the FPGA primitive that will implement it: a
tri-state buffer takes a data input and an enable, and does precisely what
&lt;code&gt;Lane&lt;/code&gt; describes. The later shell batch is where the pair meets that
primitive. &lt;a href=&quot;#fr-tristate-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-repeat&quot;&gt;
&lt;p&gt;&lt;code&gt;repeat :: KnownNat n =&amp;gt; a -&amp;gt; Vec n a&lt;/code&gt; fills a vector with copies
of one element, and the length &lt;code&gt;n&lt;/code&gt; is a type-level number the compiler
supplies from context — here from the signatures &lt;code&gt;hiZ :: Lanes&lt;/code&gt; and
&lt;code&gt;driveHigh :: Lanes&lt;/code&gt;, which fix &lt;code&gt;n = 4&lt;/code&gt; because &lt;code&gt;Lanes = Vec 4 Lane&lt;/code&gt;. There is
no count in the source because there is none to write: the type carries it,
the same way &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;&lt;code&gt;bitCoerce&lt;/code&gt;&lt;/a&gt; took its field widths from the types it
coerced between and &lt;code&gt;unpack b :: Vec 8 Bit&lt;/code&gt; took its &lt;code&gt;8&lt;/code&gt; from the annotation.
It is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer’s&lt;/a&gt; widths-live-in-the-type discipline in miniature
— a definition that stays correct when the width changes because it never
mentioned the width in the first place. &lt;a href=&quot;#fr-repeat-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-vec&quot;&gt;
&lt;p&gt;&lt;code&gt;beat&lt;/code&gt; builds its four-lane vector by hand with &lt;code&gt;:&amp;gt;&lt;/code&gt; and &lt;code&gt;Nil&lt;/code&gt;:
&lt;code&gt;(bit&#39;, 1) :&amp;gt; (0, 0) :&amp;gt; (0, 0) :&amp;gt; (0, 0) :&amp;gt; Nil&lt;/code&gt;. &lt;code&gt;:&amp;gt;&lt;/code&gt; is &lt;code&gt;Vec&lt;/code&gt;’s cons —
prepend an element to a shorter vector — and &lt;code&gt;Nil&lt;/code&gt; is the empty one, so the
expression conses four &lt;code&gt;Lane&lt;/code&gt;s onto &lt;code&gt;Nil&lt;/code&gt; and has type &lt;code&gt;Vec 4 Lane&lt;/code&gt;, which is
&lt;code&gt;Lanes&lt;/code&gt;. The length is not asserted anywhere; it is &lt;em&gt;counted&lt;/em&gt; by the compiler
from the four &lt;code&gt;:&amp;gt;&lt;/code&gt;s and checked against the &lt;code&gt;Lanes&lt;/code&gt; the signature demands, so
a &lt;code&gt;beat&lt;/code&gt; that built three lanes or five would not typecheck. It is the same
structural guarantee &lt;code&gt;repeat&lt;/code&gt; gave from the other direction — there the &lt;code&gt;4&lt;/code&gt;
came from the type, here it comes from the literal — and both meet in the
middle at &lt;code&gt;Vec 4&lt;/code&gt;. &lt;a href=&quot;#fr-vec-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-deser&quot;&gt;
&lt;p&gt;A function the fabric never runs might look like waste, but
&lt;code&gt;deserializeX1&lt;/code&gt; is the third instance of a pattern the project uses on
purpose. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;ISA’s&lt;/a&gt; &lt;code&gt;OpcodeUnimplemented&lt;/code&gt; is a &lt;code&gt;DecodeError&lt;/code&gt; the
decoders never return, declared so a future partial implementation has the
word ready. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;trace module’s&lt;/a&gt; &lt;code&gt;encodeRecord&lt;/code&gt; never executes on the
engine — &lt;code&gt;step&lt;/code&gt; packs its records inline with &lt;code&gt;bitCoerce&lt;/code&gt; — and exists only
as the readable reference that inline packing is proven equal to.
&lt;code&gt;deserializeX1&lt;/code&gt; is the same kind of surplus: it is the named inverse of
&lt;code&gt;serializeX1&lt;/code&gt;, and naming it is what lets the round-trip property be
&lt;em&gt;written&lt;/em&gt;, so the MSB-first convention has a law guarding it from both ends.
The engine’s own &lt;code&gt;sampleGet&lt;/code&gt; shifts bits in one at a time because it receives
them one at a time, across eight separate rising edges, with no &lt;code&gt;Vec 8 Bit&lt;/code&gt;
ever in hand; &lt;code&gt;deserializeX1&lt;/code&gt; is the batch statement of the same result, kept
for the test and for the reader, not for the synthesiser. &lt;a href=&quot;#fr-deser-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-tar&quot;&gt;
&lt;p&gt;The turnaround is spec §5’s answer to a hazard the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;SCK waveform&lt;/a&gt;
created. Command and response travel on the same lanes in opposite
directions, so at the seam between them the controller must stop driving and
the target must start, and for a beat or two &lt;em&gt;neither&lt;/em&gt; may — or &lt;em&gt;both&lt;/em&gt;
might, the contention a shared bus must never allow. &lt;code&gt;TAR&lt;/code&gt; fills that seam:
&lt;code&gt;driveHigh&lt;/code&gt; gives the bus one defined, driven-high clock so the line is never
left floating at the instant of handover, and then &lt;code&gt;hiZ&lt;/code&gt; releases it for the
other end to take. Driving high rather than low for that single beat matches
the bus’s idle-high convention, so the release settles to the level the
pull-ups would hold anyway. It is a small courtesy with a real payoff: a
receiver watching for the first response edge sees a clean transition rather
than the ambiguous drift of a wire that changed owners mid-float. &lt;a href=&quot;#fr-tar-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>rambo v0.1.2: the boring release is the point</title>
    <published>2026-08-07T09:00:00+00:00</published>
    <updated>2026-08-07T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/rambo-hands-off-releases/" type="text/html"/>
    <id>https://balbi.sh/posts/rambo-hands-off-releases/</id>
    <summary type="html">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/rambo&quot;&gt;rambo&lt;/a&gt; — the CLI that maps which of a Cortex-M’s SRAM a boot ROM
clobbers before your firmware’s first instruction runs — is at v0.1.2.
If you’re looking for a new feature, there isn’t one:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;### Other&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;- (deps) bump dependencies&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;- add dependabot configuration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;- chain release binaries from release-plz workflow&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;- allow manual dispatch of release workflow&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every flag you might reach for — the &lt;code&gt;--json&lt;/code&gt; reports, the
&lt;code&gt;--expectations&lt;/code&gt; RAM contracts, custom chip descriptions — shipped
back in v0.1.1, which I &lt;a href=&quot;/posts/rambo-rom-collateral-damage/&quot;&gt;already wrote up&lt;/a&gt;. v0.1.2 is pure
plumbing, and that’s the point.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/rambo&quot;&gt;rambo&lt;/a&gt; — the CLI that maps which of a Cortex-M’s SRAM a boot ROM
clobbers before your firmware’s first instruction runs — is at v0.1.2.
If you’re looking for a new feature, there isn’t one:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;### Other&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;- (deps) bump dependencies&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;- add dependabot configuration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;- chain release binaries from release-plz workflow&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;- allow manual dispatch of release workflow&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every flag you might reach for — the &lt;code&gt;--json&lt;/code&gt; reports, the
&lt;code&gt;--expectations&lt;/code&gt; RAM contracts, custom chip descriptions — shipped
back in v0.1.1, which I &lt;a href=&quot;/posts/rambo-rom-collateral-damage/&quot;&gt;already wrote up&lt;/a&gt;. v0.1.2 is pure
plumbing, and that’s the point.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;
&lt;p&gt;The one change you’ll actually notice: &lt;strong&gt;every tagged release now ships
pre-built binaries&lt;/strong&gt; for Linux, macOS (Intel and Apple Silicon), and
Windows, built and attached automatically. No Rust toolchain required to
run rambo — download, unzip, go.&lt;/p&gt;
&lt;p&gt;Under the hood that’s the whole release pipeline earning its keep. I
write &lt;a rel=&quot;external&quot; href=&quot;https://www.conventionalcommits.org/&quot;&gt;conventional commits&lt;/a&gt;; &lt;a rel=&quot;external&quot; href=&quot;https://release-plz.dev&quot;&gt;release-plz&lt;/a&gt; keeps a “Release X.Y.Z”
pull request open with the version bump and changelog already written for
me. Merging it publishes to crates.io, tags the release, and — new in
v0.1.2 — kicks off the per-OS binary build and uploads the archives to
that release. A manual trigger lets me rebuild those binaries for an
existing tag if a run ever fails, without cutting a new version.&lt;/p&gt;
&lt;p&gt;The other half of “boring” is a dependabot config that files one
grouped, conventional-commit pull request a week for Rust and Actions
updates, so it folds into a clean patch bump instead of a pile of noise.
Most of what changed between v0.1.1 and v0.1.2 is exactly that:
&lt;code&gt;actions/checkout&lt;/code&gt; and &lt;code&gt;action-gh-release&lt;/code&gt; bumped themselves and I just
merged.&lt;/p&gt;
&lt;p&gt;None of this is novel. It matters because a one-person tool doesn’t die
from bad code — it dies when every release costs twenty minutes of
bookkeeping and one day you don’t have twenty minutes. v0.1.2 drops that
cost to roughly zero, so the next release, the one with actual features
in it, is one I’ll actually ship.&lt;/p&gt;
&lt;p&gt;rambo is on &lt;a rel=&quot;external&quot; href=&quot;https://crates.io/crates/rambo&quot;&gt;crates.io&lt;/a&gt; (&lt;code&gt;cargo install rambo&lt;/code&gt;) and
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/rambo&quot;&gt;GitHub&lt;/a&gt;; grab a binary from &lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/rambo/releases&quot;&gt;the releases page&lt;/a&gt;,
including this boring one.&lt;/p&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Tamal: Add, And, and the Taken Branch</title>
    <published>2026-08-07T09:00:00+00:00</published>
    <updated>2026-08-07T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-compute/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-compute/</id>
    <summary type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-regfile/&quot;&gt;register file&lt;/a&gt; handed the datapath two values, and we
followed them to the door of the next box. &lt;code&gt;rs1v&lt;/code&gt; and &lt;code&gt;rs2v&lt;/code&gt; — read
through one register port, bound for the other — do not travel straight
from the read to the write. Between the two ports sits a layer that turns
them into something: the value that comes back to &lt;code&gt;writeReg&lt;/code&gt;, or the single
bit that decides whether to jump. That layer is this post. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch
post&lt;/a&gt; held two boxes shut over exactly these two acts — &lt;code&gt;dataResult&lt;/code&gt;,
the value its &lt;code&gt;dataWb&lt;/code&gt; wrote, and &lt;code&gt;branchTaken&lt;/code&gt;, the decision its &lt;code&gt;branch&lt;/code&gt;
turned on — and we open both here, in the two small modules that hold
them: &lt;code&gt;Tamal.Alu&lt;/code&gt; and &lt;code&gt;Tamal.Branch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;These are leaves, read whole — &lt;code&gt;Alu&lt;/code&gt; is eighty-six lines, &lt;code&gt;Branch&lt;/code&gt; is
thirty-four — and together they are the engine’s &lt;strong&gt;compute layer&lt;/strong&gt;: pure,
combinational, single-cycle functions of operand &lt;em&gt;values&lt;/em&gt;. Neither knows
there is a register file above it or an engine around it; each takes
thirty-two-bit values and answers. This is where the machine finally does
arithmetic — and where, in two import lines, it collides with the rest of
the language over what to call it.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-regfile/&quot;&gt;register file&lt;/a&gt; handed the datapath two values, and we
followed them to the door of the next box. &lt;code&gt;rs1v&lt;/code&gt; and &lt;code&gt;rs2v&lt;/code&gt; — read
through one register port, bound for the other — do not travel straight
from the read to the write. Between the two ports sits a layer that turns
them into something: the value that comes back to &lt;code&gt;writeReg&lt;/code&gt;, or the single
bit that decides whether to jump. That layer is this post. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch
post&lt;/a&gt; held two boxes shut over exactly these two acts — &lt;code&gt;dataResult&lt;/code&gt;,
the value its &lt;code&gt;dataWb&lt;/code&gt; wrote, and &lt;code&gt;branchTaken&lt;/code&gt;, the decision its &lt;code&gt;branch&lt;/code&gt;
turned on — and we open both here, in the two small modules that hold
them: &lt;code&gt;Tamal.Alu&lt;/code&gt; and &lt;code&gt;Tamal.Branch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;These are leaves, read whole — &lt;code&gt;Alu&lt;/code&gt; is eighty-six lines, &lt;code&gt;Branch&lt;/code&gt; is
thirty-four — and together they are the engine’s &lt;strong&gt;compute layer&lt;/strong&gt;: pure,
combinational, single-cycle functions of operand &lt;em&gt;values&lt;/em&gt;. Neither knows
there is a register file above it or an engine around it; each takes
thirty-two-bit values and answers. This is where the machine finally does
arithmetic — and where, in two import lines, it collides with the rest of
the language over what to call it.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;two-boxes-one-kind-of-thing&quot;&gt;Two boxes, one kind of thing&lt;/h2&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch post&lt;/a&gt; left seven leaves shut, and two of them sat over
the only places &lt;code&gt;Exec&lt;/code&gt; actually &lt;em&gt;computed&lt;/em&gt;. One was &lt;code&gt;dataResult&lt;/code&gt;, called
from &lt;code&gt;dataWb&lt;/code&gt; — the handler that &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;thirteen DATA instructions&lt;/a&gt; all
funnelled through, each supplying a destination register and letting the
box work out the value. The other was &lt;code&gt;branchTaken&lt;/code&gt;, called from &lt;code&gt;branch&lt;/code&gt;
— the helper the four conditional branches deferred their condition to
while the engine kept the program-counter arithmetic for itself. We open
them together because they are the same kind of thing: a pure, total,
single-cycle function of two register values, no state, no clock, no wire.
The difference is only in what they return — one a thirty-two-bit value,
the other a single &lt;code&gt;Bool&lt;/code&gt; — and that difference is exactly the line
between the DATA group’s compute and the CTRL group’s branches.&lt;/p&gt;
&lt;p&gt;They come as two modules, and the split is deliberate: one concern each,
the way the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;ISA&lt;/a&gt;, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt;, and the rest are one concern each.&lt;/p&gt;
&lt;p&gt;That has been the shape of this whole descent: the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;engine’s map&lt;/a&gt;
named eight boxes and opened one, and every post since has opened the next
in the order the engine reaches for it — &lt;code&gt;decode&lt;/code&gt; first, then the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-regfile/&quot;&gt;registers&lt;/a&gt; its operands named, and now the compute those
registers feed. Two boxes fall in one post here because they are siblings:
both are handed the same pair of register reads, both answer in a single
cycle, and only the shape of the answer — a value, or a verdict — tells
them apart.&lt;/p&gt;
&lt;p&gt;The ALU is the larger story, so we read it first, all the way down — and
its very first two lines are a puzzle worth stopping on.&lt;/p&gt;
&lt;h2 id=&quot;the-two-layer-plan&quot;&gt;The two-layer plan&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Tamal.Alu&lt;/code&gt;’s door lists one type and two functions:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Alu&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; AluOp&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; alu&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dataResult&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;AluOp&lt;/code&gt; is exported with &lt;code&gt;(..)&lt;/code&gt; — all its constructors, because callers
name them — and then &lt;code&gt;alu&lt;/code&gt; and &lt;code&gt;dataResult&lt;/code&gt;. Two functions for one
module is the shape to notice: they are two &lt;em&gt;layers&lt;/em&gt;, and the layering is
the whole design. &lt;code&gt;alu&lt;/code&gt; is the thin core — give it an operation and two
values, it computes. &lt;code&gt;dataResult&lt;/code&gt; is the wrapper — give it a decoded
&lt;code&gt;Instr&lt;/code&gt; and two register values, and it works out &lt;em&gt;which&lt;/em&gt; operation, pulls
any immediate out of the instruction, and calls &lt;code&gt;alu&lt;/code&gt;. The core knows
arithmetic; the wrapper knows the instruction set. Keeping them apart is
what let the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch post’s&lt;/a&gt; &lt;code&gt;dataWb&lt;/code&gt; hand the whole instruction to
&lt;code&gt;dataResult&lt;/code&gt; and collapse thirteen arms into one: the wrapper reads the
operation off the instruction, so no caller has to.&lt;/p&gt;
&lt;h2 id=&quot;two-import-lines-two-collisions&quot;&gt;Two import lines, two collisions&lt;/h2&gt;
&lt;p&gt;Under the header sit three imports, and two of them carry scars:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Clash.Prelude&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; hiding&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;And&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Xor&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Isa&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Instr&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; qualified&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Isa&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; as&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Isa&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;prelude swap&lt;/a&gt; is the line every block carries — but here it
comes with a &lt;code&gt;hiding (And, Xor)&lt;/code&gt; clause, and that clause is the first
symptom of a collision this module walks straight into on purpose. The
ALU wants to name its operations the obvious way: &lt;code&gt;Add&lt;/code&gt;, &lt;code&gt;Sub&lt;/code&gt;, &lt;code&gt;And&lt;/code&gt;,
&lt;code&gt;Or&lt;/code&gt;, &lt;code&gt;Xor&lt;/code&gt;. That vocabulary is the plainest in all of computing — and it
is already spoken for, twice.&lt;/p&gt;
&lt;p&gt;The first claimant is the standard library. Modern &lt;code&gt;Data.Bits&lt;/code&gt; ships
&lt;code&gt;newtype&lt;/code&gt; wrappers called &lt;code&gt;And&lt;/code&gt; and &lt;code&gt;Xor&lt;/code&gt; — little monoid boxes whose
&lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt; is bitwise-and and bitwise-xor, handy for folding a pile of bits down
with &lt;code&gt;mconcat&lt;/code&gt; — and &lt;code&gt;Clash.Prelude&lt;/code&gt; re-exports them.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-databits-1&quot;&gt;&lt;a href=&quot;#fn-databits&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; So the
moment &lt;code&gt;AluOp&lt;/code&gt; declares a constructor &lt;code&gt;And&lt;/code&gt;, it clashes with the &lt;code&gt;And&lt;/code&gt; the
prelude just brought in. The module’s answer is to slam that door:
&lt;code&gt;hiding (And, Xor)&lt;/code&gt; drops those two names from the import, leaving &lt;code&gt;AluOp&lt;/code&gt;’s
constructors the only &lt;code&gt;And&lt;/code&gt; and &lt;code&gt;Xor&lt;/code&gt; in scope. The core never needed the
wrappers anyway — it does bitwise work with the &lt;em&gt;operators&lt;/em&gt; &lt;code&gt;.&amp;amp;.&lt;/code&gt; and
&lt;code&gt;xor&lt;/code&gt;, not the monoids. And &lt;code&gt;Or&lt;/code&gt; escapes the whole affair, because
&lt;code&gt;Data.Bits&lt;/code&gt; spells its or-wrapper &lt;code&gt;Ior&lt;/code&gt;, not &lt;code&gt;Or&lt;/code&gt;, so there is nothing to
hide.&lt;/p&gt;
&lt;p&gt;The second claimant is closer to home: the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;instruction set&lt;/a&gt; itself.
&lt;code&gt;Tamal.Isa&lt;/code&gt;’s &lt;code&gt;Instr&lt;/code&gt; has constructors &lt;code&gt;Add&lt;/code&gt; and &lt;code&gt;Sub&lt;/code&gt; — the DATA
opcodes — and &lt;code&gt;AluOp&lt;/code&gt; wants those exact names for its operations. Two
types in scope, each with an &lt;code&gt;Add&lt;/code&gt;: an unresolvable ambiguity. So &lt;code&gt;Instr&lt;/code&gt;
comes in &lt;em&gt;qualified&lt;/em&gt;, &lt;code&gt;import qualified Tamal.Isa as Isa&lt;/code&gt;, and every
instruction constructor must be written &lt;code&gt;Isa.Add&lt;/code&gt;, &lt;code&gt;Isa.Sub&lt;/code&gt;, and so on,
while the bare &lt;code&gt;Add&lt;/code&gt; and &lt;code&gt;Sub&lt;/code&gt; belong to &lt;code&gt;AluOp&lt;/code&gt;. (The unqualified
&lt;code&gt;import Tamal.Isa (Instr)&lt;/code&gt; on the line between is just so the &lt;em&gt;type&lt;/em&gt;
&lt;code&gt;Instr&lt;/code&gt; can be named without a prefix in the signatures.)&lt;/p&gt;
&lt;p&gt;Read together, the two lines are not clumsiness to apologise for — they
are the two-layer split showing through the namespace. &lt;code&gt;AluOp&lt;/code&gt; and &lt;code&gt;Instr&lt;/code&gt;
both need a word for “add,” because they live at two different levels: one
is a &lt;em&gt;hardware operation&lt;/em&gt;, the other an &lt;em&gt;instruction&lt;/em&gt;. The whole job of
&lt;code&gt;dataResult&lt;/code&gt; is to translate the second into the first — &lt;code&gt;Isa.Add&lt;/code&gt; into
&lt;code&gt;alu Add&lt;/code&gt; — and a translator between two vocabularies only exists because
the two vocabularies are, deliberately, distinct. The collision is the
design, made visible at the import list.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When two layers both need the word “add,” the import list is where you
can see the seam between them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;the-core-eight-total-operations&quot;&gt;The core: eight total operations&lt;/h2&gt;
&lt;p&gt;With the names sorted out, the enum is plain:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; AluOp&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Add&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Sub&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; And&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Or&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Xor&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Sll&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Srl&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Sra&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Enum&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bounded&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eight operations: add, subtract, the three bitwise ops, and three shifts
— shift-left-logical, shift-right-logical, shift-right-arithmetic. The
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;deriving refrain&lt;/a&gt; is the usual four, with two additions worth
naming: &lt;code&gt;Enum&lt;/code&gt; and &lt;code&gt;Bounded&lt;/code&gt;. Those two let a caller write
&lt;code&gt;[minBound .. maxBound]&lt;/code&gt; and get &lt;em&gt;every&lt;/em&gt; &lt;code&gt;AluOp&lt;/code&gt; in order — which is
precisely how the tests will enumerate the operations later, and a small
example of a derived instance bought for a specific downstream use.&lt;/p&gt;
&lt;p&gt;The core itself is one total &lt;code&gt;case&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;alu&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; AluOp&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;alu op r1 r2 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; op &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Add&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; r1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span&gt; r2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Sub&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; r1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&lt;/span&gt;&lt;span&gt; r2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  And&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; r1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&amp;amp;.&lt;/span&gt;&lt;span&gt; r2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Or&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; r1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.|.&lt;/span&gt;&lt;span&gt; r2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Xor&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; r1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;xor&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span&gt; r2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Sll&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; r1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;shiftL&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span&gt; sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Srl&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; r1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;shiftR&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span&gt; sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Sra&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; pack &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;shiftR &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;unpack r1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signed&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; sh&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;  sh&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Int&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  sh &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; fromIntegral &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;unpack &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;truncateB r2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 5&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;type is half the documentation&lt;/a&gt;: an operation and two
thirty-two-bit values in, one out, always — no &lt;code&gt;Maybe&lt;/code&gt;, no failure. The
first five arms are the ones that need no comment: &lt;code&gt;Add&lt;/code&gt; and &lt;code&gt;Sub&lt;/code&gt; are
&lt;code&gt;BitVector&lt;/code&gt;’s &lt;code&gt;Num&lt;/code&gt;, wrapping modulo $2^{32}$; &lt;code&gt;And&lt;/code&gt;, &lt;code&gt;Or&lt;/code&gt;, &lt;code&gt;Xor&lt;/code&gt; are the
operators &lt;code&gt;.&amp;amp;.&lt;/code&gt;, &lt;code&gt;.|.&lt;/code&gt;, &lt;code&gt;`xor`&lt;/code&gt; the module hid the wrappers to keep
clear. That &lt;code&gt;Sub&lt;/code&gt; is &lt;code&gt;r1 - r2&lt;/code&gt; and nothing fancier is the same
two’s-complement fact the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;branch offset&lt;/a&gt; leaned on and the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt;
met in its own algebra: subtraction is addition of the negation, one
operation on the hardware, which is why the tests can assert
&lt;code&gt;alu Sub a b === alu Add a (complement b + 1)&lt;/code&gt; and have it hold by
construction.&lt;/p&gt;
&lt;p&gt;The shifts are where the close reading earns its keep. All three take their
distance from &lt;code&gt;sh&lt;/code&gt;, and &lt;code&gt;sh&lt;/code&gt; is defined once in the &lt;code&gt;where&lt;/code&gt;: the &lt;strong&gt;low five
bits of operand B&lt;/strong&gt;, &lt;code&gt;truncateB r2&lt;/code&gt; narrowed to a &lt;code&gt;BitVector 5&lt;/code&gt;, unpacked to
an &lt;code&gt;Unsigned 5&lt;/code&gt;, and read as an &lt;code&gt;Int&lt;/code&gt; in &lt;code&gt;0..31&lt;/code&gt;. Masking the shift amount
to five bits is a RISC-V habit with a real payoff — a five-bit count can
only ask for a shift of zero through thirty-one, so a shift by thirty-two or
more is &lt;em&gt;impossible to express&lt;/em&gt;, and the “shift wider than the word”
undefined behaviour that haunts C simply has no encoding here. A shift by 32
masks to a shift by 0; the operation stays total because its argument was
range-limited before it arrived.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-mask-1&quot;&gt;&lt;a href=&quot;#fn-mask&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Then the split between &lt;code&gt;Srl&lt;/code&gt; and &lt;code&gt;Sra&lt;/code&gt;, which is the subtlest line in the
module. &lt;code&gt;Srl&lt;/code&gt; is &lt;code&gt;r1 `shiftR` sh&lt;/code&gt; — and &lt;code&gt;BitVector&lt;/code&gt;’s &lt;code&gt;shiftR&lt;/code&gt; is
&lt;strong&gt;logical&lt;/strong&gt;, filling from the top with zeros. &lt;code&gt;Sra&lt;/code&gt; wants the &lt;em&gt;arithmetic&lt;/em&gt;
shift, the one that fills with copies of the sign bit so that shifting a
negative number right divides it. &lt;code&gt;BitVector&lt;/code&gt; has no sign to preserve, so
&lt;code&gt;Sra&lt;/code&gt; borrows one: &lt;code&gt;unpack r1 :: Signed 32&lt;/code&gt; reinterprets the same
thirty-two bits as a &lt;em&gt;signed&lt;/em&gt; number, &lt;code&gt;shiftR&lt;/code&gt; on &lt;code&gt;Signed&lt;/code&gt; sign-fills, and
&lt;code&gt;pack&lt;/code&gt; puts the bits back as a &lt;code&gt;BitVector&lt;/code&gt;. Nothing moved but the &lt;em&gt;type&lt;/em&gt;.
The bits are identical going in; reading them as &lt;code&gt;Signed&lt;/code&gt; is what makes the
shift arithmetic, and reading them back as &lt;code&gt;BitVector&lt;/code&gt; is what returns them
to the untyped wires the rest of the datapath speaks.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-sra-1&quot;&gt;&lt;a href=&quot;#fn-sra&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; It is the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC’s&lt;/a&gt; “the kind of value is part of the contract” turned into a tool:
when the operation needs a sign, the code manufactures one by changing how
the bits are read, for exactly one expression, and changes it back.&lt;/p&gt;
&lt;p&gt;And notice what is &lt;em&gt;not&lt;/em&gt; in the &lt;code&gt;case&lt;/code&gt;: a ninth arm, an error, a reserved
op. &lt;code&gt;AluOp&lt;/code&gt; has eight constructors and &lt;code&gt;alu&lt;/code&gt; handles eight; it is total
with nothing left over. That is only possible because the one reserved shift
encoding — the &lt;code&gt;0b11&lt;/code&gt; op field — never reaches here: the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;instruction
set&lt;/a&gt; traps it at decode, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;&lt;code&gt;SHIFT&lt;/code&gt; op &lt;code&gt;0b11&lt;/code&gt; that the isa post’s
test pinned&lt;/a&gt;. The reserved case is handled one layer out, at the
decoder, so the core can be a clean eight-way truth table — which in
hardware is exactly what it becomes, a bank of adders and shifters and gate
arrays with &lt;code&gt;op&lt;/code&gt; selecting one.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-lion-1&quot;&gt;&lt;a href=&quot;#fn-lion&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;And a truth table computes &lt;em&gt;everything at once&lt;/em&gt;. There is no clock inside
&lt;code&gt;alu&lt;/code&gt; and no sequencing — the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer’s&lt;/a&gt; combinational logic — so
every arm’s gates exist and run every cycle: the adder adds, the shifters
shift, the gate arrays and-or-xor in parallel, and the &lt;code&gt;case&lt;/code&gt; is a
multiplexer at the &lt;em&gt;output&lt;/em&gt; that keeps the one result &lt;code&gt;op&lt;/code&gt; asked for and
discards the rest. The apparent waste is the shape of hardware, and it is
also why the whole of &lt;code&gt;alu&lt;/code&gt; resolves inside the single &lt;code&gt;Exec&lt;/code&gt; cycle the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch post&lt;/a&gt; spent on a DATA instruction: one operation chosen from
eight computed, no time borrowed.&lt;/p&gt;
&lt;h2 id=&quot;the-wrapper-resolving-operand-b&quot;&gt;The wrapper: resolving operand B&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;alu&lt;/code&gt; computes; it does not know an instruction from a hole in the ground.
&lt;code&gt;dataResult&lt;/code&gt; is the layer that does, and it is where the DATA group’s
compute opcodes fold onto the core’s eight operations:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;dataResult&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Instr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;dataResult instr rs1v rs2v &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; instr &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Isa.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;LoadImm&lt;/span&gt;&lt;span&gt; _ imm &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; signExtend imm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Isa.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Lui&lt;/span&gt;&lt;span&gt; _ imm21 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;zeroExtend imm21 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; `&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;shiftL&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 11&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Isa.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Mov&lt;/span&gt;&lt;span&gt; _ _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; rs1v&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Isa.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Add&lt;/span&gt;&lt;span&gt; _ _ _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; alu &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Add&lt;/span&gt;&lt;span&gt; rs1v rs2v&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Isa.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Addi&lt;/span&gt;&lt;span&gt; _ _ imm &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; alu &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Add&lt;/span&gt;&lt;span&gt; rs1v &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;signExtend imm&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; … Sub, And_/Andi, Or_/Ori, Xor_/Xori, all the same shape …&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Isa.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Shift&lt;/span&gt;&lt;span&gt; _ _ shOp amt &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; alu &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;toAluShift shOp&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; rs1v &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;zeroExtend amt&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; BUS / CTRL / RDSR: never routed here by the Engine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;  toAluShift&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; AluOp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  toAluShift &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; \&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;case&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;    0b00&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Sll&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;    0b01&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Srl&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Sra&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; 0b10; 0b11 is unreachable (decode traps it)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read the arms in three groups. The first three &lt;em&gt;bypass&lt;/em&gt; &lt;code&gt;alu&lt;/code&gt; entirely,
because none of them is arithmetic — they are ways to get a &lt;em&gt;constant&lt;/em&gt;, or
a copy, into a register. &lt;code&gt;LoadImm&lt;/code&gt; sign-extends its twenty-one-bit
immediate, so one instruction reaches the whole signed-twenty-one range
(&lt;code&gt;li rd, -1&lt;/code&gt; in a single word); &lt;code&gt;Lui&lt;/code&gt; places the upper immediate for the
two-instruction constants; and &lt;code&gt;Mov&lt;/code&gt; is the degenerate case, an operation
with nothing to compute — it copies &lt;code&gt;rs1v&lt;/code&gt;, ignores &lt;code&gt;rs2v&lt;/code&gt;, the identity
function wearing an opcode. The last real arm, &lt;code&gt;Shift&lt;/code&gt;, dispatches to one of
the three shift ops via &lt;code&gt;toAluShift&lt;/code&gt;. And everything between — the arithmetic and
logic — is the group that reveals &lt;em&gt;why the wrapper exists&lt;/em&gt;: look at &lt;code&gt;Add&lt;/code&gt;
and &lt;code&gt;Addi&lt;/code&gt; sitting one line apart.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Isa.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Add&lt;/span&gt;&lt;span&gt; _ _ _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; alu &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Add&lt;/span&gt;&lt;span&gt; rs1v rs2v&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Isa.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Addi&lt;/span&gt;&lt;span&gt; _ _ imm &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; alu &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Add&lt;/span&gt;&lt;span&gt; rs1v &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;signExtend imm&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both call &lt;code&gt;alu Add rs1v&lt;/code&gt;. The only difference is the second operand: &lt;code&gt;Add&lt;/code&gt;
uses &lt;code&gt;rs2v&lt;/code&gt;, the value of the second register; &lt;code&gt;Addi&lt;/code&gt; uses &lt;code&gt;signExtend imm&lt;/code&gt;,
the instruction’s immediate widened to thirty-two bits. That is the entire
distinction between a register-register op and its immediate form, and it
repeats for &lt;code&gt;And&lt;/code&gt;/&lt;code&gt;Andi&lt;/code&gt;, &lt;code&gt;Or&lt;/code&gt;/&lt;code&gt;Ori&lt;/code&gt;, &lt;code&gt;Xor&lt;/code&gt;/&lt;code&gt;Xori&lt;/code&gt;. So the arithmetic and
logic opcodes collapse onto eight operations not by coincidence but because
each register/immediate pair is &lt;em&gt;one&lt;/em&gt; operation over &lt;em&gt;one&lt;/em&gt; choice of operand B:
a register value, or an immediate. &lt;code&gt;dataResult&lt;/code&gt;’s job, boiled down, is to
&lt;strong&gt;resolve operand B&lt;/strong&gt; and hand off. This is the other side of the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch post’s&lt;/a&gt; collapse — there, thirteen arms became one
&lt;code&gt;dataWb&lt;/code&gt; because “&lt;code&gt;dataResult&lt;/code&gt; reads the operation off the instruction”;
here is the reading, and the eight-fold fan-out it feeds.&lt;/p&gt;
&lt;p&gt;Step back and the operand path is complete across three posts. The
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch post’s&lt;/a&gt; &lt;code&gt;operandRs1&lt;/code&gt; and &lt;code&gt;operandRs2&lt;/code&gt; tables said &lt;em&gt;which&lt;/em&gt;
registers an instruction reads; the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-regfile/&quot;&gt;register file&lt;/a&gt; turned those
names into the values &lt;code&gt;rs1v&lt;/code&gt; and &lt;code&gt;rs2v&lt;/code&gt;; and &lt;code&gt;dataResult&lt;/code&gt; says &lt;em&gt;which
operation&lt;/em&gt;, and where operand B comes from. Four pieces in a line — the
selector tables, &lt;code&gt;readReg&lt;/code&gt;, &lt;code&gt;dataResult&lt;/code&gt;, &lt;code&gt;writeReg&lt;/code&gt; — are a DATA
instruction’s entire datapath, and every one of them was built and tested
alone before the engine wired them in a row. That was the wager of reading
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;outside in&lt;/a&gt;, paid back a leaf at a time.&lt;/p&gt;
&lt;p&gt;The immediates are widened by one rule, almost: every I-form immediate
&lt;em&gt;sign-extends&lt;/em&gt; — &lt;code&gt;signExtend imm&lt;/code&gt;, so an immediate with its top bit set is
negative and &lt;code&gt;addi rd, rd, -1&lt;/code&gt; subtracts one — and the single exception is
the shift amount, &lt;code&gt;zeroExtend amt&lt;/code&gt;, because a shift distance is a count, not
a signed value.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-li-1&quot;&gt;&lt;a href=&quot;#fn-li&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt; &lt;code&gt;Lui&lt;/code&gt; is the one placement worth a second look:
&lt;code&gt;(zeroExtend imm21 :: BitVector 32) `shiftL` 11&lt;/code&gt; takes the twenty-one-bit
immediate the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;ISA post&lt;/a&gt; watched get carved out of the &lt;code&gt;rs1 ++ rs2 ++ imm&lt;/code&gt; fields — the widest immediate in the set, from the fields its opcodes
had spare — and lays it into bits &lt;code&gt;[31:11]&lt;/code&gt;, low eleven zero. That shift
of eleven is chosen so &lt;code&gt;Lui&lt;/code&gt;’s bottom edge meets &lt;code&gt;Addi&lt;/code&gt;’s sign-extended top,
which is what lets the assembler build any thirty-two-bit constant in two
instructions.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-li-2&quot;&gt;&lt;a href=&quot;#fn-li&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Two things the wrapper does &lt;em&gt;not&lt;/em&gt; do, both by design. It takes register
&lt;em&gt;values&lt;/em&gt; &lt;code&gt;rs1v&lt;/code&gt; and &lt;code&gt;rs2v&lt;/code&gt;, never a &lt;code&gt;Regs&lt;/code&gt; — so it has no idea the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-regfile/&quot;&gt;register file&lt;/a&gt; exists, and the &lt;code&gt;x0&lt;/code&gt;-hardwiring and the
discarded-write-to-&lt;code&gt;x0&lt;/code&gt; we read last post are &lt;em&gt;not&lt;/em&gt; its concern; the engine
reads &lt;code&gt;x0&lt;/code&gt; as zero on the way in and drops writes to &lt;code&gt;x0&lt;/code&gt; on the way out,
and &lt;code&gt;dataResult&lt;/code&gt; computes on whatever values it is handed. The compute
layer sits &lt;em&gt;between&lt;/em&gt; the two register ports and touches neither: the engine
wires &lt;code&gt;readReg&lt;/code&gt;’s outputs into &lt;code&gt;dataResult&lt;/code&gt; and &lt;code&gt;dataResult&lt;/code&gt;’s output into
&lt;code&gt;writeReg&lt;/code&gt;. Each leaf minds one concern — the register file was engine-
ignorant, the ALU is register-file-ignorant — and the engine is the only
place that knows them all.&lt;/p&gt;
&lt;p&gt;And the &lt;code&gt;_ -&amp;gt; 0&lt;/code&gt; at the bottom is the totality tax. &lt;code&gt;dataResult&lt;/code&gt; matches on
the whole &lt;code&gt;Instr&lt;/code&gt; type, so Clash demands every constructor have an answer
— but the BUS, CTRL, and &lt;code&gt;RDSR&lt;/code&gt; instructions never arrive here, because
the engine routes only DATA-compute through &lt;code&gt;dataResult&lt;/code&gt; and handles the
rest itself. The default is unreachable &lt;em&gt;in the engine&lt;/em&gt;; &lt;code&gt;0&lt;/code&gt; is a safe,
deterministic value for a case the wiring guarantees will never come up.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-default-1&quot;&gt;&lt;a href=&quot;#fn-default&quot;&gt;[6]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;figure class=&quot;cmp-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;cmp&quot; viewBox=&quot;0 0 760 350&quot; role=&quot;img&quot; aria-labelledby=&quot;cmp-t cmp-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;cmp-t&quot;&gt;The tamal compute layer: the two-layer ALU and the branch comparator&lt;/title&gt;
&lt;desc id=&quot;cmp-d&quot;&gt;A dataflow diagram. On the left, two register values rs1v and rs2v and an immediate enter. Operand A of the ALU is rs1v; operand B is chosen by a multiplexer between rs2v, used by the register-register forms, and the sign-extended immediate, used by the immediate forms. The ALU core, drawn in the accent colour, applies one of eight operations selected by AluOp: Add, Sub, And, Or, Xor, Sll, Srl, Sra. A final multiplexer selects between the ALU result and the constant placers LoadImm, Lui and Mov to produce dataResult, which flows to writeReg rd. Below, a separate branchTaken comparator takes the same two register values and one of Beq, Bne, Bltu, Bgeu and returns taken as a Bool, while the engine does the PC math.&lt;/desc&gt;
&lt;style&gt;
.cmp{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.cmp .box{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.cmp .boxA{fill:var(--bg-dim);stroke:var(--accent);stroke-width:2.5}
.cmp .mux{fill:var(--bg-main);stroke:var(--fg-main);stroke-width:1.8}
.cmp .muxA{fill:var(--bg-main);stroke:var(--accent);stroke-width:2}
.cmp .t{fill:var(--fg-main);font-family:var(--mono);font-size:13px}
.cmp .tA{fill:var(--accent);font-family:var(--mono);font-size:14px}
.cmp .s{fill:var(--fg-dim);font-family:var(--mono);font-size:11px}
.cmp .io{fill:var(--fg-main);font-family:var(--mono);font-size:12px}
.cmp .w{stroke:var(--fg-main);stroke-width:2;fill:none}
.cmp .wa{stroke:var(--accent);stroke-width:2.5;fill:none}
.cmp .nd{fill:var(--fg-main)}
.cmp .ah{fill:var(--fg-main)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;cmp-a&quot; markerWidth=&quot;9&quot; markerHeight=&quot;7&quot; refX=&quot;7&quot; refY=&quot;3.5&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L7,3.5 L0,7 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;!-- inputs --&gt;
&lt;text class=&quot;io&quot; x=&quot;14&quot; y=&quot;100&quot; text-anchor=&quot;start&quot;&gt;rs1v&lt;/text&gt;
&lt;text class=&quot;io&quot; x=&quot;14&quot; y=&quot;150&quot; text-anchor=&quot;start&quot;&gt;rs2v&lt;/text&gt;
&lt;text class=&quot;io&quot; x=&quot;14&quot; y=&quot;176&quot; text-anchor=&quot;start&quot;&gt;imm / amt&lt;/text&gt;
&lt;!-- operand A: rs1v -&gt; alu --&gt;
&lt;path class=&quot;w&quot; d=&quot;M56,96 H300&quot; marker-end=&quot;url(#cmp-a)&quot;/&gt;
&lt;!-- rs2v -&gt; operand-B mux (reg) --&gt;
&lt;path class=&quot;w&quot; d=&quot;M56,146 H214&quot;/&gt;
&lt;!-- imm -&gt; operand-B mux (sign-extended) --&gt;
&lt;path class=&quot;w&quot; d=&quot;M82,172 H214&quot;/&gt;
&lt;text class=&quot;s&quot; x=&quot;150&quot; y=&quot;190&quot; text-anchor=&quot;middle&quot;&gt;sext imm&lt;/text&gt;
&lt;!-- operand-B mux --&gt;
&lt;path class=&quot;muxA&quot; d=&quot;M214,140 L248,150 L248,168 L214,178 Z&quot;/&gt;
&lt;text class=&quot;s&quot; x=&quot;227&quot; y=&quot;163&quot; text-anchor=&quot;middle&quot;&gt;B&lt;/text&gt;
&lt;path class=&quot;w&quot; d=&quot;M248,159 H300&quot; marker-end=&quot;url(#cmp-a)&quot;/&gt;
&lt;!-- alu core --&gt;
&lt;rect class=&quot;boxA&quot; x=&quot;300&quot; y=&quot;66&quot; width=&quot;180&quot; height=&quot;104&quot; rx=&quot;8&quot;/&gt;
&lt;text class=&quot;tA&quot; x=&quot;390&quot; y=&quot;94&quot; text-anchor=&quot;middle&quot;&gt;alu&lt;/text&gt;
&lt;text class=&quot;s&quot; x=&quot;390&quot; y=&quot;122&quot; text-anchor=&quot;middle&quot;&gt;Add  Sub  And  Or  Xor&lt;/text&gt;
&lt;text class=&quot;s&quot; x=&quot;390&quot; y=&quot;142&quot; text-anchor=&quot;middle&quot;&gt;Sll  Srl  Sra&lt;/text&gt;
&lt;text class=&quot;s&quot; x=&quot;390&quot; y=&quot;162&quot; text-anchor=&quot;middle&quot;&gt;total · dispatched by AluOp&lt;/text&gt;
&lt;!-- alu -&gt; final mux --&gt;
&lt;path class=&quot;wa&quot; d=&quot;M480,118 H540&quot;/&gt;
&lt;!-- constant placers --&gt;
&lt;rect class=&quot;box&quot; x=&quot;300&quot; y=&quot;200&quot; width=&quot;180&quot; height=&quot;48&quot; rx=&quot;6&quot;/&gt;
&lt;text class=&quot;t&quot; x=&quot;390&quot; y=&quot;222&quot; text-anchor=&quot;middle&quot;&gt;LoadImm · Lui · Mov&lt;/text&gt;
&lt;text class=&quot;s&quot; x=&quot;390&quot; y=&quot;240&quot; text-anchor=&quot;middle&quot;&gt;constant / pass-through&lt;/text&gt;
&lt;path class=&quot;w&quot; d=&quot;M480,224 H540&quot;/&gt;
&lt;!-- final select mux --&gt;
&lt;path class=&quot;mux&quot; d=&quot;M540,104 L572,124 L572,218 L540,238 Z&quot;/&gt;
&lt;path class=&quot;w&quot; d=&quot;M572,171 H612&quot; marker-end=&quot;url(#cmp-a)&quot;/&gt;
&lt;text class=&quot;t&quot; x=&quot;618&quot; y=&quot;167&quot; text-anchor=&quot;start&quot;&gt;dataResult&lt;/text&gt;
&lt;text class=&quot;s&quot; x=&quot;618&quot; y=&quot;185&quot; text-anchor=&quot;start&quot;&gt;→ writeReg rd&lt;/text&gt;
&lt;!-- branch taps off rs1v / rs2v --&gt;
&lt;circle class=&quot;nd&quot; cx=&quot;86&quot; cy=&quot;96&quot; r=&quot;3.5&quot;/&gt;
&lt;path class=&quot;w&quot; d=&quot;M86,96 V300 H300&quot; marker-end=&quot;url(#cmp-a)&quot;/&gt;
&lt;circle class=&quot;nd&quot; cx=&quot;110&quot; cy=&quot;146&quot; r=&quot;3.5&quot;/&gt;
&lt;path class=&quot;w&quot; d=&quot;M110,146 V316 H300&quot; marker-end=&quot;url(#cmp-a)&quot;/&gt;
&lt;!-- branch comparator --&gt;
&lt;rect class=&quot;box&quot; x=&quot;300&quot; y=&quot;284&quot; width=&quot;180&quot; height=&quot;46&quot; rx=&quot;6&quot;/&gt;
&lt;text class=&quot;t&quot; x=&quot;390&quot; y=&quot;306&quot; text-anchor=&quot;middle&quot;&gt;branchTaken&lt;/text&gt;
&lt;text class=&quot;s&quot; x=&quot;390&quot; y=&quot;324&quot; text-anchor=&quot;middle&quot;&gt;Beq · Bne · Bltu · Bgeu&lt;/text&gt;
&lt;path class=&quot;w&quot; d=&quot;M480,307 H512&quot; marker-end=&quot;url(#cmp-a)&quot;/&gt;
&lt;text class=&quot;t&quot; x=&quot;518&quot; y=&quot;303&quot; text-anchor=&quot;start&quot;&gt;taken : Bool&lt;/text&gt;
&lt;text class=&quot;s&quot; x=&quot;518&quot; y=&quot;321&quot; text-anchor=&quot;start&quot;&gt;engine does PC math&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The compute layer as two functions. &lt;code&gt;dataResult&lt;/code&gt; (top) resolves operand B --- a register value &lt;code&gt;rs2v&lt;/code&gt; or a sign-extended immediate --- and feeds the accent &lt;code&gt;alu&lt;/code&gt; core, the eight &lt;code&gt;AluOp&lt;/code&gt;s the DATA compute collapses onto; &lt;code&gt;Lui&lt;/code&gt;, &lt;code&gt;Mov&lt;/code&gt; and &lt;code&gt;LoadImm&lt;/code&gt; bypass the core as constants, and a final select yields the value written back. &lt;code&gt;branchTaken&lt;/code&gt; (bottom) is the sibling: the same two register values, one of four comparisons, a single &lt;em&gt;taken&lt;/em&gt; bit --- the decision, with the PC arithmetic kept in the engine. Both take &lt;em&gt;values&lt;/em&gt;, not registers; the compute layer sits between the two register ports and touches neither. (The shift amount uses &lt;code&gt;zeroExtend amt&lt;/code&gt;, not sign-extension.)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;the-comparator&quot;&gt;The comparator&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Tamal.Branch&lt;/code&gt; is the sidecar in that diagram, and it is small enough to
read whole:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Branch&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BranchOp&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; branchTaken&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Clash.Prelude&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BranchOp&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Beq&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Bne&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Bltu&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Bgeu&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Enum&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bounded&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;branchTaken&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BranchOp&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;branchTaken op r1 r2 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; op &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Beq&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; r1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; r2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Bne&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; r1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;/=&lt;/span&gt;&lt;span&gt; r2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Bltu&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; r1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; r2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Bgeu&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; r1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;gt;=&lt;/span&gt;&lt;span&gt; r2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the import: a plain &lt;code&gt;import Clash.Prelude&lt;/code&gt;, no &lt;code&gt;hiding&lt;/code&gt; clause. The
ALU had to fight for its names; the branch comparator does not, because
&lt;code&gt;Beq&lt;/code&gt;, &lt;code&gt;Bne&lt;/code&gt;, &lt;code&gt;Bltu&lt;/code&gt;, &lt;code&gt;Bgeu&lt;/code&gt; collide with nothing — they are the module’s
own coinage, not words the standard library or the instruction set already
owns. The absence of a scar is itself a small tell: this module reuses no
vocabulary, so it borrows no trouble.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;branchTaken&lt;/code&gt; returns a &lt;code&gt;Bool&lt;/code&gt; and nothing else — &lt;em&gt;taken, or not&lt;/em&gt;. That is
the entire point of the split the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch post&lt;/a&gt; set up: the
comparator decides, and the engine jumps. When the engine ran &lt;code&gt;branch&lt;/code&gt;, it
called &lt;code&gt;Br.branchTaken&lt;/code&gt; for the yes/no and then did the program-counter
arithmetic &lt;em&gt;itself&lt;/em&gt; — the eleven-bit signed offset truncated to ten bits
and added modulo the counter, backwards jumps falling out of the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;two’s-complement wraparound&lt;/a&gt;. None of that is here. &lt;code&gt;branchTaken&lt;/code&gt;
never sees the offset, never sees the PC; it is a pure predicate on two
values, and the offset math belongs to the only thing that knows where the
program counter is. That is why &lt;code&gt;Branch&lt;/code&gt; needs no &lt;code&gt;Instr&lt;/code&gt; wrapper the way
&lt;code&gt;Alu&lt;/code&gt; did: there is no immediate to resolve, no operand B to mux — both
operands are registers — so mapping the four branch constructors to
&lt;code&gt;BranchOp&lt;/code&gt; is a one-line remap the engine does inline, and a &lt;code&gt;branchResult&lt;/code&gt;-
on-&lt;code&gt;Instr&lt;/code&gt; layer would earn nothing. &lt;code&gt;Alu&lt;/code&gt; grew a second layer because it
had immediates to fold in; &lt;code&gt;Branch&lt;/code&gt; stayed one because it did not.&lt;/p&gt;
&lt;p&gt;The four comparisons hold one detail that is easy to miss and important to
get right. &lt;code&gt;Beq&lt;/code&gt; and &lt;code&gt;Bne&lt;/code&gt; are &lt;code&gt;==&lt;/code&gt; and &lt;code&gt;/=&lt;/code&gt;, unremarkable. But &lt;code&gt;Bltu&lt;/code&gt; and
&lt;code&gt;Bgeu&lt;/code&gt; — the “u” is for &lt;em&gt;unsigned&lt;/em&gt; — are just &lt;code&gt;&amp;lt;&lt;/code&gt; and &lt;code&gt;&amp;gt;=&lt;/code&gt;, and they are
unsigned &lt;em&gt;for free&lt;/em&gt;, because they operate on &lt;code&gt;BitVector&lt;/code&gt;, and &lt;code&gt;BitVector&lt;/code&gt;’s
&lt;code&gt;Ord&lt;/code&gt; is unsigned. A &lt;code&gt;BitVector 32&lt;/code&gt; has no sign bit to interpret; ordered,
it compares as a plain thirty-two-bit magnitude, &lt;code&gt;0xFFFFFFFF&lt;/code&gt; the largest
value rather than $-1$. That is &lt;em&gt;exactly&lt;/em&gt; the &lt;code&gt;BLTU&lt;/code&gt;/&lt;code&gt;BGEU&lt;/code&gt; semantics, so
the comparator gets them by writing &lt;code&gt;&amp;lt;&lt;/code&gt; and doing nothing else.&lt;/p&gt;
&lt;p&gt;This is the mirror image of the ALU’s &lt;code&gt;Sra&lt;/code&gt;, and the two together are the
whole story of how this layer handles number types. &lt;code&gt;Sra&lt;/code&gt; &lt;em&gt;reached for&lt;/em&gt;
&lt;code&gt;Signed 32&lt;/code&gt;, reinterpreting its bits to borrow a sign it needed for an
arithmetic shift. &lt;code&gt;branchTaken&lt;/code&gt; &lt;em&gt;stays on&lt;/em&gt; &lt;code&gt;BitVector&lt;/code&gt; to keep the sign
away, because an unsigned compare is what it wants. Same thirty-two bits,
opposite choices — and each is made by choosing the type the bits are read
through, for exactly the operation that needs it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The bits never change. The type you read them through is the operation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Signed branches — &lt;code&gt;BLT&lt;/code&gt;, &lt;code&gt;BGE&lt;/code&gt;, the ones that &lt;em&gt;would&lt;/em&gt; need &lt;code&gt;Signed&lt;/code&gt;’s
&lt;code&gt;Ord&lt;/code&gt; — are simply not in v1, reserved for a later phase; the four here
are the unsigned pair and the two equalities, and every one of them is a
single operator on the untyped word.&lt;/p&gt;
&lt;h2 id=&quot;the-tests&quot;&gt;The tests&lt;/h2&gt;
&lt;p&gt;Two test files, and they pose a question the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; and the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt; did not have to: when your reference model is nearly
identical to your implementation, what does a test &lt;em&gt;buy&lt;/em&gt;? &lt;code&gt;alu Add a b === a + b&lt;/code&gt; checks that add is add — a near-tautology. The answer is that the
boring arms are there to catch a &lt;em&gt;wiring&lt;/em&gt; slip (an &lt;code&gt;Add&lt;/code&gt; that quietly does
&lt;code&gt;.|.&lt;/code&gt;), and the real value lives in the arms where the behaviour is subtle.
Those get pinned twice, by property and by hand:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;Sub == Add of two&amp;#39;s complement&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  a &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genWord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genWord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  alu &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Sub&lt;/span&gt;&lt;span&gt; a b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;===&lt;/span&gt;&lt;span&gt; alu &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Add&lt;/span&gt;&lt;span&gt; a &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;complement b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testCase &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;Sra 0x80000000 by 1 = 0xC0000000 (sign-fill)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  $&lt;/span&gt;&lt;span&gt; alu &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Sra&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x80000000&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  @?=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0xC0000000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testCase &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;Srl 0x80000000 by 1 = 0x40000000 (zero-fill)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  $&lt;/span&gt;&lt;span&gt; alu &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Srl&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x80000000&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  @?=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x40000000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first states the two’s-complement identity as a law. The two concrete
vectors are the sharpest lines in the suite: the &lt;em&gt;same&lt;/em&gt; input,
&lt;code&gt;0x80000000&lt;/code&gt;, shifted right by one, gives &lt;code&gt;0xC0000000&lt;/code&gt; under &lt;code&gt;Sra&lt;/code&gt; and
&lt;code&gt;0x40000000&lt;/code&gt; under &lt;code&gt;Srl&lt;/code&gt; — the arithmetic shift copying the sign bit down,
the logical shift feeding a zero. If the &lt;code&gt;unpack … :: Signed 32&lt;/code&gt;
reinterpretation in &lt;code&gt;Sra&lt;/code&gt; were ever dropped, the first case goes red
instantly. Around them sit the property that the shift amount is masked to
five bits (&lt;code&gt;alu Sll a b === alu Sll a (b .&amp;amp;. 0x1F)&lt;/code&gt;), that a shift by zero is
identity, and that &lt;code&gt;Sra&lt;/code&gt; preserves the sign bit — the whole subtle corner,
staked down.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;dataResult&lt;/code&gt; tests check the &lt;em&gt;other&lt;/em&gt; layer: not the arithmetic, but the
wiring that routes each opcode to it. &lt;code&gt;Mov&lt;/code&gt; returns &lt;code&gt;rs1v&lt;/code&gt;; &lt;code&gt;LoadImm&lt;/code&gt;
sign-extends; &lt;code&gt;Lui&lt;/code&gt; lands its immediate at &lt;code&gt;[31:11]&lt;/code&gt; with the low eleven
zero (&lt;code&gt;(r .&amp;amp;. 0x7FF) === 0&lt;/code&gt;); each immediate form agrees with &lt;code&gt;alu&lt;/code&gt; on the
sign-extended operand, each register form agrees with &lt;code&gt;alu&lt;/code&gt; on &lt;code&gt;rs2v&lt;/code&gt;. They
prove the wrapper dispatches correctly, independently of whether the core
computes correctly — two layers, two sets of tests.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Test.Branch&lt;/code&gt; gets to do something the others cannot, and it is the
&lt;code&gt;Enum&lt;/code&gt;/&lt;code&gt;Bounded&lt;/code&gt; deriving paying off:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;branchTaken matches reference (all ops)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  op &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Gen.&lt;/span&gt;&lt;span&gt;element &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;minBound &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;..&lt;/span&gt;&lt;span&gt; maxBound&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  a &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genWord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genWord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  branchTaken op a b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;===&lt;/span&gt;&lt;span&gt; ref op a b&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;[minBound .. maxBound]&lt;/code&gt; is &lt;em&gt;every&lt;/em&gt; &lt;code&gt;BranchOp&lt;/code&gt;, enumerable because the type
derives &lt;code&gt;Enum&lt;/code&gt; and &lt;code&gt;Bounded&lt;/code&gt;, so “for all ops” is literal rather than a
hand-kept list that could fall out of date. Beside it, the complementarity
properties — &lt;code&gt;Beq&lt;/code&gt; is exactly &lt;code&gt;not Bne&lt;/code&gt;, &lt;code&gt;Bltu&lt;/code&gt; exactly &lt;code&gt;not Bgeu&lt;/code&gt; — fall
free out of the comparator’s structure, and three concrete vectors stand
guard over the unsigned reading:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testCase &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;Bltu is unsigned: 0x7FFFFFFF &amp;lt; 0x80000000&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  $&lt;/span&gt;&lt;span&gt; branchTaken &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Bltu&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x7FFFFFFF&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x80000000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  @?=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read as &lt;em&gt;signed&lt;/em&gt;, &lt;code&gt;0x80000000&lt;/code&gt; is the most negative number and
&lt;code&gt;0x7FFFFFFF&lt;/code&gt; the most positive, so a signed &lt;code&gt;&amp;lt;&lt;/code&gt; would call this &lt;code&gt;False&lt;/code&gt;.
Read as &lt;em&gt;unsigned&lt;/em&gt; — which is what &lt;code&gt;BitVector&lt;/code&gt;’s &lt;code&gt;Ord&lt;/code&gt; does — &lt;code&gt;0x80000000&lt;/code&gt;
is the larger, and the answer is &lt;code&gt;True&lt;/code&gt;. This one case is the tripwire: the
day someone reinterprets the operands as &lt;code&gt;Signed&lt;/code&gt; by mistake, it goes red
and names the bug. It is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-regfile/&quot;&gt;regfile tests’&lt;/a&gt; discipline in a
different key — the smallest input that distinguishes the behaviour you
want from the one you might slip into.&lt;/p&gt;
&lt;h2 id=&quot;what-we-read&quot;&gt;What we read&lt;/h2&gt;
&lt;p&gt;The two computing boxes of the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch post&lt;/a&gt;, opened. &lt;code&gt;Tamal.Alu&lt;/code&gt;
in two layers: a thin, total &lt;code&gt;alu&lt;/code&gt; core dispatched by an eight-constructor
&lt;code&gt;AluOp&lt;/code&gt; over register values — its &lt;code&gt;Sub&lt;/code&gt; the two’s-complement add, its
shifts masked to five bits so a shift past the word is well-defined, its
&lt;code&gt;Sra&lt;/code&gt; borrowing a &lt;code&gt;Signed&lt;/code&gt; reading for one expression while &lt;code&gt;Srl&lt;/code&gt; stays
logical — and above it &lt;code&gt;dataResult&lt;/code&gt;, the wrapper that places the &lt;code&gt;Lui&lt;/code&gt;,
&lt;code&gt;Mov&lt;/code&gt;, and &lt;code&gt;LoadImm&lt;/code&gt; constants and resolves operand B, so the arithmetic
and logic opcodes collapse onto eight operations because each register/immediate pair
is one operation over one choice of second operand. It takes &lt;em&gt;values&lt;/em&gt;, so
it never meets the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-regfile/&quot;&gt;register file&lt;/a&gt;; it sits between the two ports
and touches neither. Two import lines that were both naming collisions —
&lt;code&gt;And&lt;/code&gt; and &lt;code&gt;Xor&lt;/code&gt; hidden from the prelude, &lt;code&gt;Isa&lt;/code&gt; qualified — because a
compute layer and an instruction set both need the word “add,” and the
translator between them exists precisely because they are kept apart. And
&lt;code&gt;Tamal.Branch&lt;/code&gt;, four lines returning only taken-or-not, its &lt;code&gt;Bltu&lt;/code&gt;/&lt;code&gt;Bgeu&lt;/code&gt;
unsigned for free from &lt;code&gt;BitVector&lt;/code&gt;’s own &lt;code&gt;Ord&lt;/code&gt;, the PC arithmetic left to
the engine — the clean inverse of &lt;code&gt;Sra&lt;/code&gt;’s reach for a sign. The bits never
changed; the type read through them was the operation, every time. And
tests that pin the subtle corners against near-identical references, and
enumerate every op because the enums derive &lt;code&gt;Bounded&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dataResult&lt;/code&gt;’s value now flows back to &lt;code&gt;writeReg&lt;/code&gt;, and &lt;code&gt;branchTaken&lt;/code&gt;’s bit
to the program counter — and with that, the compute is accounted for. Of
the leaves the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch post&lt;/a&gt; named and held shut, only one is still
closed, and it is the one nearest the wire. When a &lt;code&gt;PUT&lt;/code&gt; loaded its shifter
and the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;bus post&lt;/a&gt; walked the beats, the drive on the lanes came from
&lt;code&gt;serializeX1&lt;/code&gt;, and a turnaround from &lt;code&gt;tarBeat&lt;/code&gt; — names for a byte becoming
MSB-first bits on &lt;code&gt;IO[0]&lt;/code&gt;, and for the handover clock, all of it built out
of a single tiny type: a lane, a &lt;code&gt;(value, enable)&lt;/code&gt; pair, the whole tri-state
story in two bits. That serialiser is the last box. We open it next — and
when it is open, the engine is open entire, map and every piece — before
the series turns, in a later batch, to the impure shell that carries those
&lt;code&gt;(value, enable)&lt;/code&gt; pairs the last step out, to the pins.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-databits&quot;&gt;
&lt;p&gt;Since &lt;code&gt;base&lt;/code&gt; 4.16 &lt;code&gt;Data.Bits&lt;/code&gt; provides the &lt;code&gt;newtype&lt;/code&gt; wrappers
&lt;code&gt;And&lt;/code&gt;, &lt;code&gt;Ior&lt;/code&gt;, &lt;code&gt;Xor&lt;/code&gt;, and &lt;code&gt;Iff&lt;/code&gt;, whose &lt;code&gt;Semigroup&lt;/code&gt;/&lt;code&gt;Monoid&lt;/code&gt; instances fold a
collection under bitwise-and, -or, -xor, and -xnor — so &lt;code&gt;getAnd (foldMap And xs)&lt;/code&gt; ands a list together. &lt;code&gt;Clash.Prelude&lt;/code&gt; re-exports &lt;code&gt;Data.Bits&lt;/code&gt;, &lt;code&gt;And&lt;/code&gt;
and &lt;code&gt;Xor&lt;/code&gt; among them, so the moment &lt;code&gt;AluOp&lt;/code&gt; declares constructors of those
names they clash; &lt;code&gt;hiding (And, Xor)&lt;/code&gt; drops the imports, and the core does
its bitwise work with the operators &lt;code&gt;.&amp;amp;.&lt;/code&gt; and &lt;code&gt;`xor`&lt;/code&gt; anyway. &lt;code&gt;Or&lt;/code&gt;
survives only by luck of spelling: the or-wrapper is &lt;code&gt;Ior&lt;/code&gt; (“inclusive
or”), not &lt;code&gt;Or&lt;/code&gt;, so nothing shadows &lt;code&gt;AluOp&lt;/code&gt;’s &lt;code&gt;Or&lt;/code&gt;. &lt;a href=&quot;#fr-databits-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-mask&quot;&gt;
&lt;p&gt;&lt;code&gt;sh&lt;/code&gt; is &lt;code&gt;fromIntegral (unpack (truncateB r2) :: Unsigned 5)&lt;/code&gt;.
&lt;code&gt;truncateB r2&lt;/code&gt; keeps the low five bits as a &lt;code&gt;BitVector 5&lt;/code&gt;; &lt;code&gt;unpack&lt;/code&gt; reads
them as an &lt;code&gt;Unsigned 5&lt;/code&gt; (0..31); &lt;code&gt;fromIntegral&lt;/code&gt; makes the &lt;code&gt;Int&lt;/code&gt; that
&lt;code&gt;shiftL&lt;/code&gt;/&lt;code&gt;shiftR&lt;/code&gt; want. The detour through &lt;code&gt;Unsigned 5&lt;/code&gt; is not optional:
&lt;code&gt;BitVector&lt;/code&gt; has no &lt;code&gt;Integral&lt;/code&gt; instance — you cannot &lt;code&gt;fromIntegral&lt;/code&gt; it
directly — because a bare bag of bits has no agreed numeric reading, the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC’s&lt;/a&gt; point that a value’s &lt;em&gt;kind&lt;/em&gt; is part of its contract. Masking to
five bits before shifting is what makes a shift-by-≥-32 well-defined: the
count physically cannot exceed thirty-one, so the C-style “shift wider than
the type is undefined” has no representable input. &lt;a href=&quot;#fr-mask-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-sra&quot;&gt;
&lt;p&gt;&lt;code&gt;unpack&lt;/code&gt; and &lt;code&gt;pack&lt;/code&gt; are the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;&lt;code&gt;bitCoerce&lt;/code&gt; family&lt;/a&gt; — total,
zero-cost reinterpretations between types of the same bit-width, lowering to
no gates, only a relabelling of wires. &lt;code&gt;unpack r1 :: Signed 32&lt;/code&gt; reads the
thirty-two bits as a two’s-complement number; &lt;code&gt;shiftR&lt;/code&gt; on &lt;code&gt;Signed&lt;/code&gt; is
arithmetic, replicating the sign bit; &lt;code&gt;pack&lt;/code&gt; reads the result back as a
&lt;code&gt;BitVector&lt;/code&gt;. That sandwich is the whole mechanism: &lt;code&gt;BitVector&lt;/code&gt;’s own
&lt;code&gt;shiftR&lt;/code&gt; is logical, and the only difference between &lt;code&gt;Srl&lt;/code&gt; and &lt;code&gt;Sra&lt;/code&gt; is
which type does the shifting. Nothing is added or dropped — the bits match
on both sides of the &lt;code&gt;unpack&lt;/code&gt;/&lt;code&gt;pack&lt;/code&gt; — only their reading differs, signed
for the length of one shift. &lt;a href=&quot;#fr-sra-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-lion&quot;&gt;
&lt;p&gt;The “small total &lt;code&gt;alu&lt;/code&gt; core plus a &lt;code&gt;decode&lt;/code&gt; that returns &lt;code&gt;Either&lt;/code&gt;”
division is borrowed from &lt;a rel=&quot;external&quot; href=&quot;https://github.com/standardsemiconductor/lion&quot;&gt;Lion&lt;/a&gt;,
an RV32I core in Clash: keep the arithmetic core total by pushing every
reserved or illegal encoding out to the decoder, so the datapath never has
to represent “no answer.” tamal diverges from RV32I in the particulars —
its own opcode groups, eleven-bit immediates, unsigned-only branches in v1
— so it does not inherit RV32I’s formal-verification harness; hedgehog
property tests are the verification baseline instead. &lt;a href=&quot;#fr-lion-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-li&quot;&gt;
&lt;p&gt;The eleven-bit shift on &lt;code&gt;Lui&lt;/code&gt; is not arbitrary. &lt;code&gt;Lui&lt;/code&gt; carries a
twenty-one-bit immediate (the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;ISA post’s&lt;/a&gt; &lt;code&gt;rs1 ++ rs2 ++ imm&lt;/code&gt;,
$5 + 5 + 11 = 21$) and places it at bits &lt;code&gt;[31:11]&lt;/code&gt;; &lt;code&gt;Addi&lt;/code&gt; sign-extends its
eleven-bit immediate across &lt;code&gt;[10:0]&lt;/code&gt; and up. Because &lt;code&gt;Lui&lt;/code&gt;’s bottom bit
meets &lt;code&gt;Addi&lt;/code&gt;’s sign-extended top, &lt;code&gt;li = LUI + ADDI&lt;/code&gt; builds &lt;em&gt;any&lt;/em&gt; thirty-two
-bit constant in at most two instructions, with sign-extension used
uniformly (the shift amount, a count, is the exception, &lt;code&gt;zeroExtend amt&lt;/code&gt;).
An earlier draft shifted &lt;code&gt;Lui&lt;/code&gt; by twelve to mirror RISC-V and left a
reachability gap needing a third instruction; widening the immediate to
twenty-one and realigning the shift to eleven closed it. The primitive here
fixes only &lt;code&gt;Lui = imm21 &amp;lt;&amp;lt; 11&lt;/code&gt;; the constant-tiling lives in the assembler. &lt;a href=&quot;#fr-li-1&quot;&gt;↩&lt;/a&gt; &lt;a href=&quot;#fr-li-2&quot;&gt;↩2&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-default&quot;&gt;
&lt;p&gt;&lt;code&gt;dataResult&lt;/code&gt; matches the entire &lt;code&gt;Instr&lt;/code&gt; type, so Clash requires
a total function — every constructor needs a right-hand side. The BUS,
CTRL, and &lt;code&gt;RDSR&lt;/code&gt; instructions have one only as a formality: the engine
dispatches on the decoded &lt;em&gt;group&lt;/em&gt; and routes just DATA-compute through
&lt;code&gt;dataResult&lt;/code&gt;, handling &lt;code&gt;RDSR&lt;/code&gt; (which reads engine state, not operands) and
the branches and bus ops on their own paths. So &lt;code&gt;_ -&amp;gt; 0&lt;/code&gt; is unreachable by
construction, and &lt;code&gt;0&lt;/code&gt; is a deterministic placeholder, not a computed value.
A &lt;code&gt;Maybe&lt;/code&gt;-returning variant was considered and rejected: every input the
engine actually delivers yields a real value, so the &lt;code&gt;Nothing&lt;/code&gt; would never
fire and would only burden every caller with an unwrapping. &lt;a href=&quot;#fr-default-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Tamal: Sixteen Registers and a Hardwired Zero</title>
    <published>2026-08-06T09:00:00+00:00</published>
    <updated>2026-08-06T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-regfile/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-regfile/</id>
    <summary type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;instruction set&lt;/a&gt; closed on a name it declined to open. &lt;code&gt;decode&lt;/code&gt;
had turned a word into an &lt;code&gt;Instr&lt;/code&gt;, pulled the operands out as five-bit
&lt;code&gt;Reg&lt;/code&gt; fields, and handed them on — and the very next thing the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch post&lt;/a&gt; watched &lt;code&gt;execInstr&lt;/code&gt; do was read the registers those
fields named: &lt;code&gt;readReg (regs s) (operandRs1 i)&lt;/code&gt;. But a &lt;code&gt;Reg&lt;/code&gt; was only ever
a &lt;em&gt;name&lt;/em&gt;, five bits wide, not yet an index into anything. The thing it
names is this post: &lt;code&gt;Tamal.RegFile&lt;/code&gt;, the sixteen registers, the register
hardwired to zero, and the two ports the datapath read and wrote through
the whole arc while we held them shut.&lt;/p&gt;
&lt;p&gt;This is a leaf again, read whole the way the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; and the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt; were — small enough to hold entire, fifty-one lines with
nothing left closed when the post ends. And it is a leaf the series has
already promised. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt; kept their write port ignorant of
the engine — a block RAM that will not import &lt;code&gt;Tamal.Engine&lt;/code&gt;, because “a
memory that imports the engine is a memory that knows what a trace record
is” — and pointed here as its sibling: &lt;em&gt;the same discipline that
produced &lt;code&gt;Tamal.RegFile&lt;/code&gt;, built before &lt;code&gt;Engine.step&lt;/code&gt; existed to use it and
never importing the engine whose registers it holds.&lt;/em&gt; By the time we
opened it, that post said, the shape would be familiar. Here it is.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;instruction set&lt;/a&gt; closed on a name it declined to open. &lt;code&gt;decode&lt;/code&gt;
had turned a word into an &lt;code&gt;Instr&lt;/code&gt;, pulled the operands out as five-bit
&lt;code&gt;Reg&lt;/code&gt; fields, and handed them on — and the very next thing the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch post&lt;/a&gt; watched &lt;code&gt;execInstr&lt;/code&gt; do was read the registers those
fields named: &lt;code&gt;readReg (regs s) (operandRs1 i)&lt;/code&gt;. But a &lt;code&gt;Reg&lt;/code&gt; was only ever
a &lt;em&gt;name&lt;/em&gt;, five bits wide, not yet an index into anything. The thing it
names is this post: &lt;code&gt;Tamal.RegFile&lt;/code&gt;, the sixteen registers, the register
hardwired to zero, and the two ports the datapath read and wrote through
the whole arc while we held them shut.&lt;/p&gt;
&lt;p&gt;This is a leaf again, read whole the way the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; and the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt; were — small enough to hold entire, fifty-one lines with
nothing left closed when the post ends. And it is a leaf the series has
already promised. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt; kept their write port ignorant of
the engine — a block RAM that will not import &lt;code&gt;Tamal.Engine&lt;/code&gt;, because “a
memory that imports the engine is a memory that knows what a trace record
is” — and pointed here as its sibling: &lt;em&gt;the same discipline that
produced &lt;code&gt;Tamal.RegFile&lt;/code&gt;, built before &lt;code&gt;Engine.step&lt;/code&gt; existed to use it and
never importing the engine whose registers it holds.&lt;/em&gt; By the time we
opened it, that post said, the shape would be familiar. Here it is.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;the-door-again&quot;&gt;The door, again&lt;/h2&gt;
&lt;p&gt;A &lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/tamal&quot;&gt;Tamal&lt;/a&gt; module opens by naming itself and listing what leaves through
the wall, and after the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;engine’s&lt;/a&gt; eleven-name sprawl this one is a
return to the narrow doors of the leaves:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.RegFile&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Regs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; initRegs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; readReg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; writeReg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Four names. We have read this shape before, and the aphorism the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt;
gave it still holds:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A module is a wall with a door in it, and the export list is the door.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But look at &lt;em&gt;what&lt;/em&gt; the four names are, because the split is the whole
design in miniature. One is a noun — &lt;code&gt;Regs&lt;/code&gt;, the type of the register
bank — and three are verbs: &lt;code&gt;initRegs&lt;/code&gt; makes one, &lt;code&gt;readReg&lt;/code&gt; inspects one,
&lt;code&gt;writeReg&lt;/code&gt; evolves one. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;engine&lt;/a&gt; had to export six types
because something one level up assembles it and must name its plugs; a leaf
like the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; exported only a verb and hid its types entirely.
&lt;code&gt;Tamal.RegFile&lt;/code&gt; sits between: it publishes &lt;em&gt;one&lt;/em&gt; type, because its callers
must be able to hold a &lt;code&gt;Regs&lt;/code&gt; and pass it around, but it publishes that
type &lt;strong&gt;opaquely&lt;/strong&gt; — the name &lt;code&gt;Regs&lt;/code&gt; crosses the wall, and its constructor
does not. You will not find &lt;code&gt;Regs (..)&lt;/code&gt; on that list, the way the engine
wrote &lt;code&gt;State (..)&lt;/code&gt; to export a record and all its fields. Just &lt;code&gt;Regs&lt;/code&gt;, the
bare name, a handle to a thing whose insides stay home.&lt;/p&gt;
&lt;p&gt;That is a deliberate choice, and it is the same choice that kept &lt;code&gt;step&lt;/code&gt;
private inside the CRC: the export list is a place to say less than you
could. Everything a caller is allowed to do to a register bank is on those
four lines, and the shape of the bank behind them is not. Hold that; it is
about to matter twice.&lt;/p&gt;
&lt;h2 id=&quot;the-prelude-and-the-only-import&quot;&gt;The prelude, and the only import&lt;/h2&gt;
&lt;p&gt;Two lines sit under the header, and the first is the one every block in
this series has carried:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Clash.Prelude&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Isa&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Reg&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;prelude swap&lt;/a&gt; is old news by now — the line that throws out
ordinary Haskell’s furniture and moves in the &lt;code&gt;Bit&lt;/code&gt;, &lt;code&gt;BitVector&lt;/code&gt;, &lt;code&gt;Vec&lt;/code&gt;,
and &lt;code&gt;Signal&lt;/code&gt; that lower to gates, the line that says &lt;em&gt;compile me to
hardware&lt;/em&gt;. I will not re-derive it a seventh time. It is the &lt;em&gt;second&lt;/em&gt;
import that repays a look, and it repays it precisely because of how
little it says.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;import Tamal.Isa (Reg)&lt;/code&gt; reaches across the wall for exactly one name, and
that name is a type synonym: &lt;code&gt;Reg&lt;/code&gt;, which &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;we met&lt;/a&gt; as &lt;code&gt;BitVector 5&lt;/code&gt;,
the five-bit register selector &lt;code&gt;decode&lt;/code&gt; carves out of an instruction word.
The register file imports the &lt;em&gt;name of a selector&lt;/em&gt; — and nothing else.
It does not import &lt;code&gt;Tamal.Engine&lt;/code&gt;. It does not import the &lt;code&gt;State&lt;/code&gt; that will
hold it, or the &lt;code&gt;step&lt;/code&gt; that will call it, or the ALU whose results it will
store. It imports one width-carrying synonym from the instruction set and
stops.&lt;/p&gt;
&lt;p&gt;This is the discipline the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt; named when they refused to let
their write port learn what a trace record was, and it is worth stating as
its own rule now that we are standing inside the leaf it promised:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A leaf that holds the engine’s registers has never heard of the engine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The dependency arrow runs one way only. &lt;code&gt;Tamal.Isa&lt;/code&gt; knows nothing of
&lt;code&gt;Tamal.RegFile&lt;/code&gt;; &lt;code&gt;Tamal.RegFile&lt;/code&gt; knows only the name &lt;code&gt;Reg&lt;/code&gt;; and
&lt;code&gt;Tamal.Engine&lt;/code&gt;, one level up, imports &lt;em&gt;both&lt;/em&gt; and wires them together. The
register file was, as the mem post said, “built before &lt;code&gt;Engine.step&lt;/code&gt;
existed to use it” — a small, &lt;a rel=&quot;external&quot; href=&quot;https://hedgehog.qa&quot;&gt;hedgehog&lt;/a&gt;-tested warm-up written
so that when the keystone &lt;code&gt;step&lt;/code&gt; finally landed, its operand-fetch and
writeback path would be &lt;em&gt;wiring&lt;/em&gt;, not &lt;em&gt;invention&lt;/em&gt;. And importing &lt;code&gt;Reg&lt;/code&gt;
rather than re-declaring a bare &lt;code&gt;BitVector 5&lt;/code&gt; is the small courtesy that
makes that wiring seamless: &lt;code&gt;readReg&lt;/code&gt; and &lt;code&gt;writeReg&lt;/code&gt; take the &lt;em&gt;exact&lt;/em&gt; type
&lt;code&gt;decode&lt;/code&gt; produces, so the engine can pass &lt;code&gt;rs1&lt;/code&gt;, &lt;code&gt;rs2&lt;/code&gt;, and &lt;code&gt;rd&lt;/code&gt; straight
through with no conversion at the seam.&lt;/p&gt;
&lt;h2 id=&quot;the-bank-sealed&quot;&gt;The bank, sealed&lt;/h2&gt;
&lt;p&gt;Here is the type the whole module exists to serve, and the reason its
constructor stayed off the door:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;newtype&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Regs&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Regs&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 16&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;code&gt;Regs&lt;/code&gt; is a &lt;code&gt;Vec 16 (BitVector 32)&lt;/code&gt; — sixteen thirty-two-bit words, a
fixed-length vector, five hundred and twelve bits of architectural state
— wrapped in a &lt;code&gt;newtype&lt;/code&gt; whose constructor is also called &lt;code&gt;Regs&lt;/code&gt;. The
wrapper is the wall. Because the constructor does not leave the module,
nothing outside can write &lt;code&gt;Regs someVector&lt;/code&gt; to forge a bank, or pattern-
match &lt;code&gt;Regs v&lt;/code&gt; to reach the raw vector inside; the &lt;code&gt;Vec 16&lt;/code&gt; never leaks.
The outside world holds a &lt;code&gt;Regs&lt;/code&gt; as a sealed handle and touches it only
through &lt;code&gt;initRegs&lt;/code&gt;, &lt;code&gt;readReg&lt;/code&gt;, and &lt;code&gt;writeReg&lt;/code&gt;, exactly the three verbs on
the door. Should a later version want a different representation — a
packed &lt;code&gt;BitVector 512&lt;/code&gt;, a pair of banked halves, whatever a timing closure
demands — it can change this line and the three functions and break not a
single caller, because no caller was ever allowed to see the shape it is
changing.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-newtype-1&quot;&gt;&lt;a href=&quot;#fn-newtype&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;deriving&lt;/code&gt; block is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;four-class refrain&lt;/a&gt; every stateful
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/tamal&quot;&gt;Tamal&lt;/a&gt; block wears, and I will be quick with it: &lt;code&gt;Generic&lt;/code&gt; lets Clash work
structurally, &lt;code&gt;Show&lt;/code&gt; and &lt;code&gt;Eq&lt;/code&gt; are for the tests — and &lt;code&gt;Eq&lt;/code&gt; earns a
specific keep here, since one property will assert &lt;code&gt;writeReg rs 0 v === rs&lt;/code&gt;
and needs to compare two banks — and &lt;code&gt;NFDataX&lt;/code&gt; is the one that means
“this value can be the contents of a register.” Everywhere else in the
series that phrase was a near-metaphor. Here it is &lt;em&gt;literal&lt;/em&gt;: the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;engine’s &lt;code&gt;State&lt;/code&gt;&lt;/a&gt; has a field &lt;code&gt;regs :: Regs&lt;/code&gt;, the whole &lt;code&gt;State&lt;/code&gt; is
what the &lt;code&gt;mealy&lt;/code&gt; wrapper clocks, and so a &lt;code&gt;Regs&lt;/code&gt; is not &lt;em&gt;like&lt;/em&gt; a register’s
contents — it &lt;em&gt;is&lt;/em&gt; sixteen registers’ contents, five hundred and twelve
flip-flops on the fabric. &lt;code&gt;NFDataX&lt;/code&gt; is the class that lets those flip-flops
have a defined power-up value; without it, &lt;code&gt;Regs&lt;/code&gt; could not sit in the
state at all.&lt;/p&gt;
&lt;h2 id=&quot;power-up-is-sixteen-zeros&quot;&gt;Power-up is sixteen zeros&lt;/h2&gt;
&lt;p&gt;One line says what the bank holds before anything happens:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;initRegs&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Regs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;initRegs &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Regs&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;repeat &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;repeat 0&lt;/code&gt; is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;&lt;code&gt;Vec&lt;/code&gt; &lt;code&gt;repeat&lt;/code&gt;&lt;/a&gt; that fills a whole fixed-length
vector with one value; here it lays down sixteen copies of &lt;code&gt;0&lt;/code&gt;, and &lt;code&gt;Regs&lt;/code&gt;
wraps them. Every register powers up holding zero. This is the same move
the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt; made with &lt;code&gt;repeat 0&lt;/code&gt; for their &lt;code&gt;INIT&lt;/code&gt; contents, and it
does the same double duty: on hardware it becomes the flip-flops’ reset
value, and in simulation it means every slot is &lt;em&gt;defined&lt;/em&gt; from the first
cycle, so a randomised test that reads a register nobody has written gets a
zero rather than an exception.&lt;/p&gt;
&lt;p&gt;And &lt;code&gt;initRegs&lt;/code&gt; is not merely &lt;em&gt;a&lt;/em&gt; starting value; it is &lt;em&gt;the&lt;/em&gt; one. The
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape post&lt;/a&gt; read &lt;code&gt;initState&lt;/code&gt; and found &lt;code&gt;regs = initRegs&lt;/code&gt; sitting in
it, and read the larger fact folded into &lt;code&gt;initState&lt;/code&gt; existing at all:
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/tamal&quot;&gt;Tamal&lt;/a&gt; has &lt;strong&gt;no reset port.&lt;/strong&gt; The top ties reset permanently deasserted and
leans on power-up &lt;code&gt;init&lt;/code&gt;, so &lt;code&gt;initRegs&lt;/code&gt; is not what a reset line loads —
there is no reset line — it is what the register bank &lt;em&gt;powers up holding&lt;/em&gt;.
When a second program runs, &lt;code&gt;softInit&lt;/code&gt; throws the whole machine back to
&lt;code&gt;initState&lt;/code&gt;, &lt;code&gt;regs&lt;/code&gt; included, so every run begins from sixteen zeros and
the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;byte-identical re-run&lt;/a&gt; the intro promised extends cleanly
through the register file. The determinism has to reach all the way down
here, and &lt;code&gt;repeat 0&lt;/code&gt; is where it lands.&lt;/p&gt;
&lt;h2 id=&quot;a-name-is-not-a-slot&quot;&gt;A name is not a slot&lt;/h2&gt;
&lt;p&gt;Between the five-bit name a caller hands in and the physical register it
means sits one private helper — private, like the CRC’s &lt;code&gt;step&lt;/code&gt;, kept off
the door because nothing outside should depend on how a name becomes a
slot:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;regIndex&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 16&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;regIndex r &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; unpack &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;truncateB r&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;code&gt;Reg&lt;/code&gt; is a &lt;code&gt;BitVector 5&lt;/code&gt;; an &lt;code&gt;Index 16&lt;/code&gt; is a number in &lt;code&gt;0..15&lt;/code&gt;, the type
Clash uses to index a sixteen-element vector safely. &lt;code&gt;regIndex&lt;/code&gt; is the
translation, and it is two operations read right to left. &lt;code&gt;truncateB r&lt;/code&gt;
keeps the &lt;strong&gt;low four bits&lt;/strong&gt; of the five-bit selector and discards the top
one, yielding a &lt;code&gt;BitVector 4&lt;/code&gt;; &lt;code&gt;unpack&lt;/code&gt; then reinterprets those four bits
as an &lt;code&gt;Index 16&lt;/code&gt;, whose bit-width is exactly four.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-index-1&quot;&gt;&lt;a href=&quot;#fn-index&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt; The wider
selector is narrowed to the width the bank actually has.&lt;/p&gt;
&lt;p&gt;This is a small instance of the width-changing family — &lt;code&gt;truncateB&lt;/code&gt;,
&lt;code&gt;zeroExtend&lt;/code&gt;, &lt;code&gt;signExtend&lt;/code&gt; — as opposed to the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;structural &lt;code&gt;bitCoerce&lt;/code&gt;&lt;/a&gt;
that reshapes bits without changing how many there are. &lt;code&gt;truncateB&lt;/code&gt; changes
the count: five bits in, four out, the fifth dropped on the floor. And the
choice of which bit to drop is the design.&lt;/p&gt;
&lt;p&gt;Look at the result type. &lt;code&gt;regIndex&lt;/code&gt; returns an &lt;code&gt;Index 16&lt;/code&gt;, and an
&lt;code&gt;Index 16&lt;/code&gt; &lt;em&gt;cannot&lt;/em&gt; be out of range — the values it can hold are &lt;code&gt;0&lt;/code&gt;
through &lt;code&gt;15&lt;/code&gt; and no others, guaranteed by the type. So when &lt;code&gt;readReg&lt;/code&gt; and
&lt;code&gt;writeReg&lt;/code&gt; reach for the total Clash primitives &lt;code&gt;(!!)&lt;/code&gt; and &lt;code&gt;replace&lt;/code&gt;, there
is no bounds check to write and no out-of-range case to handle, because the
index they are handed made the bound part of its own type.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-prim-1&quot;&gt;&lt;a href=&quot;#fn-prim&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; This is
the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories’&lt;/a&gt; “widths live in the type, so there is no branch to
have,” moved one level over: the safety is not enforced at runtime, it is
made unrepresentable at compile time.&lt;/p&gt;
&lt;p&gt;But narrowing five bits to four has a visible consequence, and the design
chose it on purpose. Two selectors that differ only in their top bit —
&lt;code&gt;x1&lt;/code&gt; (&lt;code&gt;00001&lt;/code&gt;) and &lt;code&gt;x17&lt;/code&gt; (&lt;code&gt;10001&lt;/code&gt;) — truncate to the &lt;em&gt;same&lt;/em&gt; four bits and
therefore the same slot. The out-of-window selectors &lt;code&gt;x16..x31&lt;/code&gt; &lt;strong&gt;alias&lt;/strong&gt;
their low-four twins. The leaf does not reject them; it folds them back
into the sixteen it has. That keeps the function &lt;strong&gt;total&lt;/strong&gt; — every one of
the thirty-two names a five-bit field can hold yields a defined slot, none
traps — and totality is the property a leaf is supposed to guarantee.
Whether &lt;code&gt;x16..x31&lt;/code&gt; should be &lt;em&gt;refused&lt;/em&gt; is a real question, but it is the
assembler’s and the engine’s question, not this leaf’s: the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;instruction
set&lt;/a&gt; noted that &lt;code&gt;Reg&lt;/code&gt; is “wider than the register file behind it …
deliberate room to grow,” and the fifth bit is that room,
reserved for a future that implements thirty-two registers. Until then the
register file stays total by aliasing, and leaves the rejecting to the
layers whose job rejecting is.&lt;/p&gt;
&lt;h2 id=&quot;readreg-the-hole-at-x0&quot;&gt;&lt;code&gt;readReg&lt;/code&gt;: the hole at x0&lt;/h2&gt;
&lt;p&gt;The first of the two ports:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;readReg&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Regs&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;readReg &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Regs&lt;/span&gt;&lt;span&gt; v&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; r&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; idx &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; x0 is hardwired to 0.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; v &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;!!&lt;/span&gt;&lt;span&gt; idx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  idx &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; regIndex r&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;type is half the documentation&lt;/a&gt;: a bank and a name in, a
thirty-two-bit value out, and — the part the type says by saying nothing
— &lt;em&gt;always&lt;/em&gt; a value, no &lt;code&gt;Maybe&lt;/code&gt;, no error. Reading a register cannot fail.
It pattern-matches the bank open as &lt;code&gt;Regs v&lt;/code&gt; (allowed, we are inside the
wall), computes &lt;code&gt;idx = regIndex r&lt;/code&gt;, and forks on one guard.&lt;/p&gt;
&lt;p&gt;That guard is the design decision the whole &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;RISC-V flavour&lt;/a&gt; turns
on. When &lt;code&gt;idx == 0&lt;/code&gt;, &lt;code&gt;readReg&lt;/code&gt; does &lt;strong&gt;not&lt;/strong&gt; return &lt;code&gt;v !! 0&lt;/code&gt; — it does not
read slot zero’s contents at all. It returns the literal &lt;code&gt;0&lt;/code&gt;. Register
&lt;code&gt;x0&lt;/code&gt; is hardwired to zero: reading it yields zero no matter what is
physically stored in slot zero, and nothing can make it read anything
else. Every other index falls to &lt;code&gt;v !! idx&lt;/code&gt;, the total vector index that
pulls slot &lt;code&gt;idx&lt;/code&gt;’s word straight out.&lt;/p&gt;
&lt;p&gt;We have already spent this guarantee, a whole post before we built it. The
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch post’s&lt;/a&gt; operand selectors ended every table with a
catch-all &lt;code&gt;_ -&amp;gt; 0&lt;/code&gt;, so that an instruction with no second source “reads
&lt;code&gt;x0&lt;/code&gt;” and gets zero — “a read that is always safe, always defined, and
costs nothing, because &lt;code&gt;x0&lt;/code&gt; is not a register you can get wrong.” &lt;em&gt;This
guard is why.&lt;/em&gt; The reason reading &lt;code&gt;x0&lt;/code&gt; is free is that &lt;code&gt;readReg&lt;/code&gt; answers
&lt;code&gt;0&lt;/code&gt; without so much as glancing at the vector; &lt;code&gt;LoadImm&lt;/code&gt;, which names no
sources and reads &lt;code&gt;x0&lt;/code&gt; twice, pays nothing for the two reads because both
short-circuit here. The engine leaned on a promise, and the promise is one
line of guard in a leaf.&lt;/p&gt;
&lt;p&gt;In hardware &lt;code&gt;readReg&lt;/code&gt; is combinational — the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-isa/&quot;&gt;truth-table totality&lt;/a&gt;
the series keeps returning to, cashed as a &lt;strong&gt;16-to-1 multiplexer&lt;/strong&gt;: the
four-bit index selects one of sixteen thirty-two-bit lanes, and the lane
for slot zero is tied to constant zero rather than to a flip-flop’s
output.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-prim-2&quot;&gt;&lt;a href=&quot;#fn-prim&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; No clock, no latency; the value is there the moment the
index is. And because it is combinational, the datapath can call it twice
in one cycle — once for &lt;code&gt;rs1&lt;/code&gt;, once for &lt;code&gt;rs2&lt;/code&gt; — and get both source
values at once, which is why a module that exports a single &lt;code&gt;readReg&lt;/code&gt; gives
the datapath &lt;em&gt;two&lt;/em&gt; read ports. The one function, instantiated twice, is the
classic two-read shape of a register file; the &lt;code&gt;x0&lt;/code&gt; register even earns a
second keep as the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;always-true branch&lt;/a&gt;, where &lt;code&gt;j off&lt;/code&gt; is really
&lt;code&gt;beq x0, x0, off&lt;/code&gt;, a comparison of zero against zero that &lt;code&gt;readReg&lt;/code&gt; makes
free on both sides.&lt;/p&gt;
&lt;h2 id=&quot;writereg-the-write-that-replaces&quot;&gt;&lt;code&gt;writeReg&lt;/code&gt;: the write that replaces&lt;/h2&gt;
&lt;p&gt;The second port, and the more quietly radical one:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;writeReg&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Regs&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Regs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;writeReg regs&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;@&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Regs&lt;/span&gt;&lt;span&gt; v&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; r x&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; idx &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; regs &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; writes to x0 are ignored!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Regs&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;replace idx x v&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  idx &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; regIndex r&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read the type first, because its &lt;em&gt;return&lt;/em&gt; is the whole story. &lt;code&gt;writeReg&lt;/code&gt;
takes a bank, a name, and a value, and hands back — a bank. Not &lt;code&gt;()&lt;/code&gt;, not
&lt;code&gt;IO ()&lt;/code&gt;, not a mutation performed on the side: a fresh &lt;code&gt;Regs&lt;/code&gt;. A write to a
register does not &lt;em&gt;change&lt;/em&gt; the register file. It &lt;em&gt;computes a new one&lt;/em&gt; and
returns it, leaving the old one exactly as it was.&lt;/p&gt;
&lt;p&gt;This is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer’s&lt;/a&gt; “mapping, not procedure,” cashed the way the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC’s fold&lt;/a&gt; cashed it: nothing is overwritten. In C, &lt;code&gt;regs[rd] = x&lt;/code&gt;
reaches into an array and clobbers a cell. Here &lt;code&gt;replace idx x v&lt;/code&gt; produces a
&lt;em&gt;new&lt;/em&gt; vector identical to &lt;code&gt;v&lt;/code&gt; but with slot &lt;code&gt;idx&lt;/code&gt; set to &lt;code&gt;x&lt;/code&gt;, and &lt;code&gt;Regs&lt;/code&gt;
wraps it; the argument &lt;code&gt;v&lt;/code&gt; is never touched and is simply not referenced
again. What looks like the mutation of a register is a pure function from an
old bank to a new bank, and the “mutation” you imagine happens exactly once,
elsewhere: when the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;Mealy wrapper&lt;/a&gt; clocks the returned &lt;code&gt;State&lt;/code&gt; –
the one now carrying the new &lt;code&gt;Regs&lt;/code&gt; — into the fabric flip-flops at the
edge. The datapath itself assigns nothing.&lt;/p&gt;
&lt;p&gt;The guard is &lt;code&gt;x0&lt;/code&gt; again, from the writing side. &lt;code&gt;idx == 0&lt;/code&gt; returns &lt;code&gt;regs&lt;/code&gt;
— the whole original bank, unchanged — so a write to &lt;code&gt;x0&lt;/code&gt; is silently
discarded, which is the other half of “hardwired to zero”: you cannot read
anything but zero out, and you cannot put anything in. The
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch post&lt;/a&gt; promised this too, noting that &lt;code&gt;writeReg&lt;/code&gt; “quietly
enforces one rule … a write to &lt;code&gt;x0&lt;/code&gt; is discarded, because &lt;code&gt;x0&lt;/code&gt; is
hardwired zero.” Here is the enforcement, one guard wide. Every other index
takes the &lt;code&gt;replace&lt;/code&gt; path. And the as-pattern &lt;code&gt;regs@(Regs v)&lt;/code&gt; is a small
convenience doing real work: it binds the whole argument as &lt;code&gt;regs&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; its
unwrapped vector as &lt;code&gt;v&lt;/code&gt; in one shot, so the &lt;code&gt;x0&lt;/code&gt; arm can return the original
&lt;code&gt;regs&lt;/code&gt; without rebuilding it while the other arm still has &lt;code&gt;v&lt;/code&gt; in hand to
hand to &lt;code&gt;replace&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;one-invariant-one-place-twice&quot;&gt;One invariant, one place, twice&lt;/h2&gt;
&lt;p&gt;Two things are worth pausing on together, because they are really one
decision. First, &lt;code&gt;x0&lt;/code&gt; is guarded in &lt;em&gt;both&lt;/em&gt; ports — read returns zero,
write is discarded — and both guards test the &lt;strong&gt;truncated&lt;/strong&gt; index,
&lt;code&gt;idx == 0&lt;/code&gt;, not the raw five-bit selector. That is what makes the hole
consistent under aliasing: &lt;code&gt;x16&lt;/code&gt; truncates to slot zero, so &lt;code&gt;x16&lt;/code&gt; reads
zero and discards its writes &lt;em&gt;exactly&lt;/em&gt; as &lt;code&gt;x0&lt;/code&gt; does. The aliasing and the
hardwiring compose without anyone arranging it; the same &lt;code&gt;idx == 0&lt;/code&gt; that
protects &lt;code&gt;x0&lt;/code&gt; protects its out-of-window twin for free.&lt;/p&gt;
&lt;p&gt;Second, and larger: the &lt;code&gt;x0&lt;/code&gt; rule lives &lt;em&gt;here&lt;/em&gt;, in the register file, and
nowhere else. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;engine&lt;/a&gt; reads and writes registers uniformly —
&lt;code&gt;readReg (regs s) rd&lt;/code&gt;, &lt;code&gt;writeReg (regs s) rd result&lt;/code&gt; — and never checks
whether &lt;code&gt;rd&lt;/code&gt; is zero, because it never has to. If the rule lived in the
engine instead, every one of the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;thirteen DATA arms&lt;/a&gt; that writes a
register would need an &lt;code&gt;if rd /= 0&lt;/code&gt; around its writeback, and the invariant
would be scattered across a dozen call sites, each an opportunity to forget
it. Housed in one leaf, it is impossible to forget: the register file
&lt;em&gt;is&lt;/em&gt; the thing that makes &lt;code&gt;x0&lt;/code&gt; behave, so anything that goes through the
register file inherits the behaviour.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;x0&lt;/code&gt; is not a register the engine keeps at zero. It is a register that
cannot be anything else, because the only door to it refuses to carry a
value either way.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure class=&quot;rf-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;rf&quot; viewBox=&quot;0 0 760 244&quot; role=&quot;img&quot; aria-labelledby=&quot;rf-t rf-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;rf-t&quot;&gt;The tamal register file: sixteen registers with x0 a hardwired zero, two read ports, one write port&lt;/title&gt;
&lt;desc id=&quot;rf-d&quot;&gt;Sixteen register cells in a row, labelled x0 on the left through x15 on the right, together forming the opaque Regs value, a Vec of sixteen 32-bit words. x0 is drawn in the accent colour and marked identically zero: reading it returns zero and writes to it are discarded. A five-bit selector is truncated to its low four bits to choose a slot, so out-of-window selectors x16 through x31 alias their low-four twin. Below the bank, one write port labelled writeReg carries a destination and a value up into the bank and produces a fresh bank, while two read ports labelled readReg tap values rs1v and rs2v out of it.&lt;/desc&gt;
&lt;style&gt;
.rf{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.rf .cell{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.rf .cellA{fill:var(--bg-dim);stroke:var(--accent);stroke-width:2.5}
.rf .nm{fill:var(--fg-main);font-family:var(--mono);font-size:12px}
.rf .nmA{fill:var(--accent);font-family:var(--mono);font-size:12px}
.rf .zero{fill:var(--accent);font-family:var(--mono);font-size:11px}
.rf .brk{stroke:var(--fg-dim);stroke-width:1.5;fill:none}
.rf .cap{fill:var(--fg-dim);font-family:var(--mono);font-size:12px}
.rf .prd{fill:var(--fg-main);font-family:var(--mono);font-size:12px}
.rf .prdA{fill:var(--accent);font-family:var(--mono);font-size:12px}
.rf .wire{stroke:var(--fg-main);stroke-width:2;fill:none}
.rf .accw{stroke:var(--accent);stroke-width:2.5;fill:none}
.rf .ah{fill:var(--fg-main)}
.rf .aha{fill:var(--accent)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;rf-a&quot; markerWidth=&quot;9&quot; markerHeight=&quot;7&quot; refX=&quot;7&quot; refY=&quot;3.5&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L7,3.5 L0,7 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;rf-aa&quot; markerWidth=&quot;9&quot; markerHeight=&quot;7&quot; refX=&quot;7&quot; refY=&quot;3.5&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L7,3.5 L0,7 Z&quot; class=&quot;aha&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;!-- bracket over the whole bank --&gt;
&lt;path class=&quot;brk&quot; d=&quot;M28,64 V56 H728 V64&quot;/&gt;
&lt;text class=&quot;cap&quot; x=&quot;378&quot; y=&quot;48&quot; text-anchor=&quot;middle&quot;&gt;Regs — Vec 16 (BitVector 32)&lt;/text&gt;
&lt;!-- 16 cells --&gt;
&lt;rect class=&quot;cellA&quot; x=&quot;28&quot; y=&quot;70&quot; width=&quot;40&quot; height=&quot;54&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;nmA&quot; x=&quot;48&quot; y=&quot;94&quot; text-anchor=&quot;middle&quot;&gt;x0&lt;/text&gt;
&lt;text class=&quot;zero&quot; x=&quot;48&quot; y=&quot;112&quot; text-anchor=&quot;middle&quot;&gt;≡0&lt;/text&gt;
&lt;rect class=&quot;cell&quot; x=&quot;72&quot; y=&quot;70&quot; width=&quot;40&quot; height=&quot;54&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;nm&quot; x=&quot;92&quot; y=&quot;101&quot; text-anchor=&quot;middle&quot;&gt;x1&lt;/text&gt;
&lt;rect class=&quot;cell&quot; x=&quot;116&quot; y=&quot;70&quot; width=&quot;40&quot; height=&quot;54&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;nm&quot; x=&quot;136&quot; y=&quot;101&quot; text-anchor=&quot;middle&quot;&gt;x2&lt;/text&gt;
&lt;rect class=&quot;cell&quot; x=&quot;160&quot; y=&quot;70&quot; width=&quot;40&quot; height=&quot;54&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;nm&quot; x=&quot;180&quot; y=&quot;101&quot; text-anchor=&quot;middle&quot;&gt;x3&lt;/text&gt;
&lt;rect class=&quot;cell&quot; x=&quot;204&quot; y=&quot;70&quot; width=&quot;40&quot; height=&quot;54&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;nm&quot; x=&quot;224&quot; y=&quot;101&quot; text-anchor=&quot;middle&quot;&gt;x4&lt;/text&gt;
&lt;rect class=&quot;cell&quot; x=&quot;248&quot; y=&quot;70&quot; width=&quot;40&quot; height=&quot;54&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;nm&quot; x=&quot;268&quot; y=&quot;101&quot; text-anchor=&quot;middle&quot;&gt;x5&lt;/text&gt;
&lt;rect class=&quot;cell&quot; x=&quot;292&quot; y=&quot;70&quot; width=&quot;40&quot; height=&quot;54&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;nm&quot; x=&quot;312&quot; y=&quot;101&quot; text-anchor=&quot;middle&quot;&gt;x6&lt;/text&gt;
&lt;rect class=&quot;cell&quot; x=&quot;336&quot; y=&quot;70&quot; width=&quot;40&quot; height=&quot;54&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;nm&quot; x=&quot;356&quot; y=&quot;101&quot; text-anchor=&quot;middle&quot;&gt;x7&lt;/text&gt;
&lt;rect class=&quot;cell&quot; x=&quot;380&quot; y=&quot;70&quot; width=&quot;40&quot; height=&quot;54&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;nm&quot; x=&quot;400&quot; y=&quot;101&quot; text-anchor=&quot;middle&quot;&gt;x8&lt;/text&gt;
&lt;rect class=&quot;cell&quot; x=&quot;424&quot; y=&quot;70&quot; width=&quot;40&quot; height=&quot;54&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;nm&quot; x=&quot;444&quot; y=&quot;101&quot; text-anchor=&quot;middle&quot;&gt;x9&lt;/text&gt;
&lt;rect class=&quot;cell&quot; x=&quot;468&quot; y=&quot;70&quot; width=&quot;40&quot; height=&quot;54&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;nm&quot; x=&quot;488&quot; y=&quot;101&quot; text-anchor=&quot;middle&quot;&gt;x10&lt;/text&gt;
&lt;rect class=&quot;cell&quot; x=&quot;512&quot; y=&quot;70&quot; width=&quot;40&quot; height=&quot;54&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;nm&quot; x=&quot;532&quot; y=&quot;101&quot; text-anchor=&quot;middle&quot;&gt;x11&lt;/text&gt;
&lt;rect class=&quot;cell&quot; x=&quot;556&quot; y=&quot;70&quot; width=&quot;40&quot; height=&quot;54&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;nm&quot; x=&quot;576&quot; y=&quot;101&quot; text-anchor=&quot;middle&quot;&gt;x12&lt;/text&gt;
&lt;rect class=&quot;cell&quot; x=&quot;600&quot; y=&quot;70&quot; width=&quot;40&quot; height=&quot;54&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;nm&quot; x=&quot;620&quot; y=&quot;101&quot; text-anchor=&quot;middle&quot;&gt;x13&lt;/text&gt;
&lt;rect class=&quot;cell&quot; x=&quot;644&quot; y=&quot;70&quot; width=&quot;40&quot; height=&quot;54&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;nm&quot; x=&quot;664&quot; y=&quot;101&quot; text-anchor=&quot;middle&quot;&gt;x14&lt;/text&gt;
&lt;rect class=&quot;cell&quot; x=&quot;688&quot; y=&quot;70&quot; width=&quot;40&quot; height=&quot;54&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;nm&quot; x=&quot;708&quot; y=&quot;101&quot; text-anchor=&quot;middle&quot;&gt;x15&lt;/text&gt;
&lt;!-- write port: value up into the bank --&gt;
&lt;path class=&quot;wire&quot; d=&quot;M180,220 V124&quot; marker-end=&quot;url(#rf-a)&quot;/&gt;
&lt;text class=&quot;prd&quot; x=&quot;180&quot; y=&quot;230&quot; text-anchor=&quot;middle&quot;&gt;writeReg rd x&lt;/text&gt;
&lt;!-- read ports: values down out of the bank --&gt;
&lt;path class=&quot;wire&quot; d=&quot;M470,124 V210&quot; marker-end=&quot;url(#rf-a)&quot;/&gt;
&lt;text class=&quot;prd&quot; x=&quot;470&quot; y=&quot;230&quot; text-anchor=&quot;middle&quot;&gt;readReg rs1 → rs1v&lt;/text&gt;
&lt;path class=&quot;wire&quot; d=&quot;M632,124 V210&quot; marker-end=&quot;url(#rf-a)&quot;/&gt;
&lt;text class=&quot;prd&quot; x=&quot;632&quot; y=&quot;230&quot; text-anchor=&quot;middle&quot;&gt;readReg rs2 → rs2v&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The register file as the datapath sees it. The sixteen cells are the opaque &lt;code&gt;Regs&lt;/code&gt;, a &lt;code&gt;Vec 16 (BitVector 32)&lt;/code&gt;; &lt;code&gt;x0&lt;/code&gt; (accent) is the hole, &lt;code&gt;≡0&lt;/code&gt; --- &lt;code&gt;readReg&lt;/code&gt; returns zero for it and &lt;code&gt;writeReg&lt;/code&gt; discards writes to it, both keyed on the truncated index so &lt;code&gt;x16&lt;/code&gt;‥&lt;code&gt;x31&lt;/code&gt; alias &lt;code&gt;x0&lt;/code&gt;‥&lt;code&gt;x15&lt;/code&gt; and inherit the same behaviour. A five-bit &lt;code&gt;Reg&lt;/code&gt; is truncated to its low four bits (&lt;code&gt;regIndex&lt;/code&gt;) to pick a slot, so the index is an &lt;code&gt;Index 16&lt;/code&gt; that &lt;em&gt;cannot&lt;/em&gt; be out of range. One &lt;code&gt;writeReg&lt;/code&gt; port returns a fresh bank; one &lt;code&gt;readReg&lt;/code&gt;, instantiated twice, gives the two combinational read ports the datapath fetches &lt;code&gt;rs1&lt;/code&gt; and &lt;code&gt;rs2&lt;/code&gt; through.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;read-old-write-new-for-free&quot;&gt;Read-old, write-new, for free&lt;/h2&gt;
&lt;p&gt;Step back up one level, to the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch post’s&lt;/a&gt; &lt;code&gt;dataWb&lt;/code&gt;, and watch
all three functions meet in a single cycle:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  rs1v &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; readReg &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;regs s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;operandRs1 i&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  rs2v &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; readReg &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;regs s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;operandRs2 i&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  dataWb rd &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;advance s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;regs&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; writeReg &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;regs s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; rd &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dataResult i rs1v rs2v&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every one of them touches &lt;code&gt;regs s&lt;/code&gt; — the &lt;em&gt;current&lt;/em&gt; cycle’s bank. The two
reads pull &lt;code&gt;rs1v&lt;/code&gt; and &lt;code&gt;rs2v&lt;/code&gt; out of it; the write builds a &lt;em&gt;new&lt;/em&gt; bank from
it and tucks that into &lt;code&gt;s&#39;&lt;/code&gt; under &lt;code&gt;regs&lt;/code&gt;. Because &lt;code&gt;readReg&lt;/code&gt; reads the value
the state holds &lt;em&gt;now&lt;/em&gt;, and &lt;code&gt;writeReg&lt;/code&gt; merely computes the value the state
will hold &lt;em&gt;next&lt;/em&gt;, the two cannot collide. Even when a &lt;code&gt;DATA&lt;/code&gt; instruction
writes the very register it read — &lt;code&gt;add x3, x3, x1&lt;/code&gt;, where &lt;code&gt;rd&lt;/code&gt; and &lt;code&gt;rs1&lt;/code&gt;
are both &lt;code&gt;x3&lt;/code&gt; — &lt;code&gt;rs1v&lt;/code&gt; is the &lt;em&gt;old&lt;/em&gt; &lt;code&gt;x3&lt;/code&gt;, sampled before the write, and
the new &lt;code&gt;x3&lt;/code&gt; does not exist until the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;Mealy wrapper&lt;/a&gt; clocks &lt;code&gt;s&#39;&lt;/code&gt; at
the edge. Read-old, write-new, hazard-free, and nobody wrote a hazard
check: it falls out of the read and the write being pure functions of the
same value, with the “write” a separate return that only becomes state a
cycle later.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-hazard-1&quot;&gt;&lt;a href=&quot;#fn-hazard&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;This is why the register-file design rejected a &lt;code&gt;blockRam&lt;/code&gt;-backed
bank, and it is worth naming because it is the road not taken. A block RAM
has a one-cycle read latency and lives at &lt;code&gt;Signal&lt;/code&gt; level; it could not be a
field of a pure Mealy &lt;code&gt;State&lt;/code&gt;, and reading and writing the same address in
one cycle would open exactly the read-during-write hazard the pure version
closes by construction. Five hundred and twelve bits is small enough to
live in fabric flip-flops with a combinational read, so it does — and the
purity that made the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;whole engine testable&lt;/a&gt; makes its register
hazards vanish as a side effect. The same choice buys both.&lt;/p&gt;
&lt;h2 id=&quot;the-tests&quot;&gt;The tests&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Test.RegFile&lt;/code&gt; is short, the way a leaf’s tests are short, and it makes the
same quiet point the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; and the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt; made: a claim this
small can be pinned down &lt;em&gt;completely&lt;/em&gt;. Its cleverest line is a generator:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;genRegs&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Gen&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Regs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;genRegs &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ws &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Gen.&lt;/span&gt;&lt;span&gt;list &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Range.&lt;/span&gt;&lt;span&gt;linear &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 20&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; genReg &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span&gt; genWord&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  pure &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;foldl&amp;#39; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;\&lt;/span&gt;&lt;span&gt;rs &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; v&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; writeReg rs r v&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; initRegs ws&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read what it does &lt;em&gt;not&lt;/em&gt; do. It builds an arbitrary register bank by drawing
a random list of up to twenty &lt;code&gt;(register, value)&lt;/code&gt; pairs and &lt;em&gt;folding
&lt;code&gt;writeReg&lt;/code&gt; over them&lt;/em&gt;, starting from &lt;code&gt;initRegs&lt;/code&gt; — so every &lt;code&gt;Regs&lt;/code&gt; the
tests ever see is constructed &lt;strong&gt;only through the public door&lt;/strong&gt;. The
generator never reaches for the &lt;code&gt;Regs&lt;/code&gt; constructor, because the constructor
is not exported and is not the test’s to touch, exactly as the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC’s
tests&lt;/a&gt; “exercise the byte-level &lt;code&gt;crc8Update&lt;/code&gt; and never reach for
&lt;code&gt;step&lt;/code&gt;.” The wall holds even here; the properties test the bank the way a
real caller builds it, one &lt;code&gt;writeReg&lt;/code&gt; at a time. (The &lt;code&gt;foldl&#39;&lt;/code&gt; is
&lt;code&gt;Data.List&lt;/code&gt;’s, not the &lt;code&gt;Vec&lt;/code&gt; fold &lt;code&gt;import Clash.Prelude&lt;/code&gt; &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;shadowed&lt;/a&gt; —
&lt;code&gt;ws&lt;/code&gt; is a list — the same prelude-swap wrinkle that made the CRC tests
reach for &lt;code&gt;L.foldl&#39;&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;The properties then stake down every corner of the design at once:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;read-after-write (r /= x0)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  rs &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genRegs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  r &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genNonZeroReg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  v &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genWord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  readReg &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;writeReg rs r v&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; r &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;===&lt;/span&gt;&lt;span&gt; v&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Write a value to any non-&lt;code&gt;x0&lt;/code&gt; register and read it straight back: you get
the value, whatever the slot held before. That is the ordinary register’s
contract — an overwrite wins — and it is the first thing to prove. Three
more guard the &lt;code&gt;x0&lt;/code&gt; hole from all sides: &lt;code&gt;readReg rs 0 === 0&lt;/code&gt; says the read
is always zero; &lt;code&gt;writeReg rs 0 v === rs&lt;/code&gt; says the write is a no-op, and it
leans on the derived &lt;code&gt;Eq Regs&lt;/code&gt; to compare whole banks (the reason &lt;code&gt;Eq&lt;/code&gt; was
on the door); and a &lt;em&gt;register independence&lt;/em&gt; property draws two selectors
with distinct low-four indices and checks that writing one leaves the
other’s read untouched — no write scribbles on a neighbour. One pins the
power-up state, &lt;code&gt;readReg initRegs r === 0&lt;/code&gt; for every name. And two pin the
truncation itself:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;x16..x31 alias x0..x15&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  r &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genNonZeroReg &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; x1..x15&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  v &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genWord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  readReg &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;writeReg initRegs &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;r &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 16&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; v&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; r &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;===&lt;/span&gt;&lt;span&gt; v&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testCase &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;x16 aliases x0 (write discarded)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  $&lt;/span&gt;&lt;span&gt; writeReg initRegs &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 42&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  @?=&lt;/span&gt;&lt;span&gt; initRegs&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The property writes to &lt;code&gt;r + 16&lt;/code&gt; — an out-of-window selector — and reads
back through &lt;code&gt;r&lt;/code&gt;, demanding the value land in the twin slot; the &lt;a rel=&quot;external&quot; href=&quot;https://hedgehog.qa&quot;&gt;Hedgehog&lt;/a&gt;
run fires it across every &lt;code&gt;r&lt;/code&gt; in &lt;code&gt;x1..x15&lt;/code&gt;. The &lt;code&gt;HUnit&lt;/code&gt; case nails the one
corner the property cannot reach, since &lt;code&gt;x0&lt;/code&gt; has no non-zero twin to read:
writing &lt;code&gt;42&lt;/code&gt; to &lt;code&gt;x16&lt;/code&gt;, which aliases &lt;code&gt;x0&lt;/code&gt;, must leave the bank &lt;em&gt;identical&lt;/em&gt;,
because the write is discarded. Six checks and a witness, and between them
they describe the whole surface of the leaf — the bijection on
&lt;code&gt;x1..x15&lt;/code&gt;, the hole at &lt;code&gt;x0&lt;/code&gt;, the aliasing of everything above — with
nothing left to guess.&lt;/p&gt;
&lt;h2 id=&quot;what-we-read&quot;&gt;What we read&lt;/h2&gt;
&lt;p&gt;The last pure leaf, opened whole. A four-name door that exports one type
and three verbs, and exports the type &lt;em&gt;opaquely&lt;/em&gt;, so a &lt;code&gt;Vec 16 (BitVector 32)&lt;/code&gt; can be sealed behind the wall and touched only through &lt;code&gt;initRegs&lt;/code&gt;,
&lt;code&gt;readReg&lt;/code&gt;, and &lt;code&gt;writeReg&lt;/code&gt;. A single import — &lt;code&gt;Tamal.Isa&lt;/code&gt;, for the name of
a selector — and, pointedly, not the engine whose registers it holds: the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories’&lt;/a&gt; discipline, cashed as promised, a leaf built before the
&lt;code&gt;step&lt;/code&gt; that would use it. A &lt;code&gt;newtype&lt;/code&gt; bank of five hundred and twelve bits
that powers up to sixteen zeros and, deriving &lt;code&gt;NFDataX&lt;/code&gt;, &lt;em&gt;is&lt;/em&gt; the engine’s
register flip-flops in its &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;Mealy state&lt;/a&gt;. A private &lt;code&gt;regIndex&lt;/code&gt; that
truncates a five-bit name to a four-bit slot, so the index is an &lt;code&gt;Index 16&lt;/code&gt;
that cannot be out of range and the out-of-window &lt;code&gt;x16..x31&lt;/code&gt; alias their
low-four twins rather than trap — totality by construction, room to grow
left in the fifth bit. &lt;code&gt;readReg&lt;/code&gt;’s hole at &lt;code&gt;x0&lt;/code&gt;, the hardwired zero that
made “no operand” &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;free a post ago&lt;/a&gt;; &lt;code&gt;writeReg&lt;/code&gt;’s discarded write and
its functional update, a fresh bank &lt;em&gt;returned&lt;/em&gt; rather than a cell &lt;em&gt;mutated&lt;/em&gt;,
the primer’s mapping-not-procedure one last time. One &lt;code&gt;x0&lt;/code&gt; invariant, kept
in one place and keyed on the truncated index so aliasing inherits it. The
read-old/write-new hazard-freedom that falls out of a pure step for nothing.
And a test file that builds every bank through the public door and pins the
bijection, the hole, and the aliasing from six directions at once.&lt;/p&gt;
&lt;p&gt;The register file hands the datapath two values every cycle, and those
values do not sit idle. &lt;code&gt;rs1v&lt;/code&gt; and &lt;code&gt;rs2v&lt;/code&gt; — the two reads &lt;code&gt;dataWb&lt;/code&gt; took
before it wrote — flow straight on into the one box the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch
post&lt;/a&gt; still had shut over its writeback: &lt;code&gt;dataResult&lt;/code&gt;, the ALU, which
turns two register values and an instruction into the one value that comes
back to &lt;code&gt;writeReg&lt;/code&gt;. And the same two reads feed the branch comparator that
decided whether a jump was taken. Both were named and left closed. Next we
open them together — &lt;code&gt;Tamal.Alu&lt;/code&gt; and &lt;code&gt;Tamal.Branch&lt;/code&gt;, the add and the
&lt;code&gt;and&lt;/code&gt; and the taken branch — the compute layer that sits between the two
ports we just read, consuming what one hands out and producing what the
other takes in.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-newtype&quot;&gt;
&lt;p&gt;&lt;code&gt;newtype&lt;/code&gt; rather than &lt;code&gt;data&lt;/code&gt; is a deliberate, zero-cost choice.
A &lt;code&gt;newtype&lt;/code&gt; may wrap exactly one field, and Haskell guarantees it is
&lt;em&gt;erased&lt;/em&gt; at compile time: at runtime a &lt;code&gt;Regs&lt;/code&gt; &lt;strong&gt;is&lt;/strong&gt; a &lt;code&gt;Vec 16 (BitVector 32)&lt;/code&gt;, with no box, no tag, no indirection — the wrapper exists only in the
type-checker, where it does the work of keeping the two types distinct and
the constructor private. So the encapsulation the opaque export buys is
free: it costs a name in the type system and nothing in the gates. Clash
lowers the &lt;code&gt;Vec 16&lt;/code&gt; to a bank of flip-flops exactly as it would an
unwrapped vector, and the &lt;code&gt;Regs&lt;/code&gt; label vanishes before synthesis ever sees
it. You get the wall without paying for a wall. &lt;a href=&quot;#fr-newtype-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-index&quot;&gt;
&lt;p&gt;The widths line up by type inference, read outside in. &lt;code&gt;regIndex&lt;/code&gt;
is annotated to return &lt;code&gt;Index 16&lt;/code&gt;, and the &lt;code&gt;BitSize&lt;/code&gt; of &lt;code&gt;Index 16&lt;/code&gt; is &lt;code&gt;4&lt;/code&gt;
(four bits address sixteen slots). That return type fixes &lt;code&gt;unpack :: BitVector 4 -&amp;gt; Index 16&lt;/code&gt;, which in turn fixes &lt;code&gt;truncateB r :: BitVector 4&lt;/code&gt;
— &lt;code&gt;truncateB&lt;/code&gt; is polymorphic in its output width, so the surrounding type
is what tells it to keep four bits rather than three or five. Nothing here
names the number four; it is deduced from &lt;code&gt;Index 16&lt;/code&gt;, so changing the bank
to &lt;code&gt;Vec 32&lt;/code&gt; and the selector’s window would re-derive the truncation width
automatically. The type carries the arithmetic, and the code only says
“narrow to fit.” &lt;a href=&quot;#fr-index-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-prim&quot;&gt;
&lt;p&gt;&lt;code&gt;(!!) :: Vec n a -&amp;gt; Index n -&amp;gt; a&lt;/code&gt; and &lt;code&gt;replace :: Index n -&amp;gt; a -&amp;gt; Vec n a -&amp;gt; Vec n a&lt;/code&gt; are Clash’s &lt;em&gt;total&lt;/em&gt; vector primitives: because the
index has type &lt;code&gt;Index n&lt;/code&gt;, there is no out-of-range value to pass and thus no
partial case, no runtime bounds fault. In hardware &lt;code&gt;(!!)&lt;/code&gt; on a &lt;code&gt;Vec 16&lt;/code&gt;
lowers to a 16-to-1 multiplexer selected by the four index bits, and
&lt;code&gt;readReg&lt;/code&gt;’s &lt;code&gt;x0&lt;/code&gt; guard is a further two-way mux tying the output to constant
zero when the index is zero. &lt;code&gt;writeReg&lt;/code&gt;’s &lt;code&gt;replace&lt;/code&gt; lowers to sixteen
thirty-two-bit registers, each with a load-enable asserted when &lt;code&gt;idx&lt;/code&gt;
equals its position &lt;em&gt;and&lt;/em&gt; &lt;code&gt;idx&lt;/code&gt; is non-zero — which is precisely why slot
zero is never written. All of it is combinational logic over five hundred
and twelve flip-flops; none of it is a &lt;code&gt;blockRam&lt;/code&gt;, and that is the point. &lt;a href=&quot;#fr-prim-1&quot;&gt;↩&lt;/a&gt; &lt;a href=&quot;#fr-prim-2&quot;&gt;↩2&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-hazard&quot;&gt;
&lt;p&gt;The hazard-freedom is a property of &lt;em&gt;values&lt;/em&gt;, not of careful
sequencing. &lt;code&gt;readReg (regs s) rs&lt;/code&gt; and &lt;code&gt;writeReg (regs s) rd v&lt;/code&gt; are both
functions of the same immutable &lt;code&gt;regs s&lt;/code&gt;; neither can observe the other,
because there is no mutable cell for one to write and the other to read.
The new bank is a distinct value that the caller places into &lt;code&gt;s&#39;&lt;/code&gt;, and only
the clock edge — the one genuinely stateful event, owned by the &lt;code&gt;mealy&lt;/code&gt;
wrapper a level up — turns &lt;code&gt;s&#39;&lt;/code&gt; into the next &lt;code&gt;regs s&lt;/code&gt;. So “read the old,
write the new” is not a rule the register file implements; it is the only
thing that &lt;em&gt;can&lt;/em&gt; happen when reads and writes are pure and state advances
once per cycle. A conventional imperative register file has to design
around the write-during-read case; this one cannot express it. &lt;a href=&quot;#fr-hazard-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Tamal: The Instruction Set</title>
    <published>2026-08-05T09:00:00+00:00</published>
    <updated>2026-08-05T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-isa/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-isa/</id>
    <summary type="html">&lt;p&gt;For &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;three posts&lt;/a&gt; we held the leaves shut. We read the engine’s
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape&lt;/a&gt;, watched it &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch an instruction&lt;/a&gt;, and &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;ran its
wire&lt;/a&gt;, and all the while &lt;code&gt;decode&lt;/code&gt;, &lt;code&gt;dataResult&lt;/code&gt;, &lt;code&gt;branchTaken&lt;/code&gt;, and
the rest were names with one-line jobs and no insides. Now we start
opening them — in the order the engine reaches for each — and the
very first thing the engine does to any word, before it can compute or
branch or drive a pin, is &lt;code&gt;decode&lt;/code&gt; it. So the descent begins where every
road out of &lt;code&gt;Exec&lt;/code&gt; began: in &lt;code&gt;Tamal.Isa&lt;/code&gt;, the instruction set itself.&lt;/p&gt;
&lt;p&gt;This is a leaf again, read whole the way the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; and the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt; were — but it is the leaf the entire engine is written
against. Every constructor &lt;code&gt;execInstr&lt;/code&gt; matched, every field it pulled out
with an underscore or a name, every trap that fired on a word the engine
could not read: all of it is defined here, in one file, as a type and two
functions that are exact inverses of each other over exactly the words
that are legal.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;For &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;three posts&lt;/a&gt; we held the leaves shut. We read the engine’s
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape&lt;/a&gt;, watched it &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch an instruction&lt;/a&gt;, and &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;ran its
wire&lt;/a&gt;, and all the while &lt;code&gt;decode&lt;/code&gt;, &lt;code&gt;dataResult&lt;/code&gt;, &lt;code&gt;branchTaken&lt;/code&gt;, and
the rest were names with one-line jobs and no insides. Now we start
opening them — in the order the engine reaches for each — and the
very first thing the engine does to any word, before it can compute or
branch or drive a pin, is &lt;code&gt;decode&lt;/code&gt; it. So the descent begins where every
road out of &lt;code&gt;Exec&lt;/code&gt; began: in &lt;code&gt;Tamal.Isa&lt;/code&gt;, the instruction set itself.&lt;/p&gt;
&lt;p&gt;This is a leaf again, read whole the way the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; and the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt; were — but it is the leaf the entire engine is written
against. Every constructor &lt;code&gt;execInstr&lt;/code&gt; matched, every field it pulled out
with an underscore or a name, every trap that fired on a word the engine
could not read: all of it is defined here, in one file, as a type and two
functions that are exact inverses of each other over exactly the words
that are legal.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;the-word&quot;&gt;The word&lt;/h2&gt;
&lt;p&gt;Every tamal instruction is exactly thirty-two bits, and those bits are
carved into six fields the same way for every opcode. The module’s
doc-comment draws the map:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; 31 30 | 29 .. 26 | 25 .. 21 | 20 .. 16 | 15 .. 11 | 10 .. 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; group |   sub    |    rd    |   rs1    |   rs2    |  imm&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two bits of &lt;strong&gt;group&lt;/strong&gt;, four of &lt;strong&gt;sub&lt;/strong&gt;, then three five-bit register
selectors — &lt;strong&gt;rd&lt;/strong&gt;, &lt;strong&gt;rs1&lt;/strong&gt;, &lt;strong&gt;rs2&lt;/strong&gt; — and an eleven-bit &lt;strong&gt;imm&lt;/strong&gt; at
the bottom. The group and sub together are the opcode: the group picks
one of four families, the sub picks an operation within it. The three
register fields and the immediate are the operands, and which of them an
opcode actually uses depends on the opcode — the rest are &lt;em&gt;reserved&lt;/em&gt;,
and we will see that “reserved” is a promise the decoder enforces, not a
comment.&lt;/p&gt;
&lt;figure class=&quot;isaw-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;isaw&quot; viewBox=&quot;0 0 760 138&quot; role=&quot;img&quot; aria-labelledby=&quot;isaw-t isaw-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;isaw-t&quot;&gt;The 32-bit tamal instruction word and its six fields&lt;/title&gt;
&lt;desc id=&quot;isaw-d&quot;&gt;A 32-bit instruction word drawn left to right, most significant bit 31 on the left. Six fields shown in proportion to their bit counts: a two-bit group in bits 31 to 30 and a four-bit sub in bits 29 to 26 together form the opcode; then a five-bit rd in bits 25 to 21, a five-bit rs1 in bits 20 to 16, a five-bit rs2 in bits 15 to 11, and an eleven-bit imm in bits 10 to 0 are the operands.&lt;/desc&gt;
&lt;style&gt;
.isaw{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.isaw .box{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.isaw .boxA{fill:var(--bg-dim);stroke:var(--accent);stroke-width:2.5}
.isaw .nm{fill:var(--fg-main);font-family:var(--mono);font-size:14px}
.isaw .nmA{fill:var(--accent);font-family:var(--mono);font-size:14px}
.isaw .rng{fill:var(--fg-dim);font-family:var(--mono);font-size:11.5px}
.isaw .brk{stroke:var(--fg-dim);stroke-width:1.5;fill:none}
.isaw .brkA{stroke:var(--accent);stroke-width:1.5;fill:none}
.isaw .cap{fill:var(--fg-dim);font-family:var(--sans);font-size:11.5px}
.isaw .capA{fill:var(--accent);font-family:var(--sans);font-size:11.5px}
&lt;/style&gt;
&lt;!-- opcode / operand brackets --&gt;
&lt;path class=&quot;brkA&quot; d=&quot;M54,50 V42 H174 V50&quot;/&gt;
&lt;text class=&quot;capA&quot; x=&quot;114&quot; y=&quot;34&quot; text-anchor=&quot;middle&quot;&gt;opcode&lt;/text&gt;
&lt;path class=&quot;brk&quot; d=&quot;M174,50 V42 H694 V50&quot;/&gt;
&lt;text class=&quot;cap&quot; x=&quot;434&quot; y=&quot;34&quot; text-anchor=&quot;middle&quot;&gt;operands&lt;/text&gt;
&lt;!-- field boxes --&gt;
&lt;rect class=&quot;boxA&quot; x=&quot;54&quot; y=&quot;56&quot; width=&quot;40&quot; height=&quot;46&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;rng&quot; x=&quot;74&quot; y=&quot;83&quot; text-anchor=&quot;middle&quot;&gt;31:30&lt;/text&gt;
&lt;text class=&quot;nmA&quot; x=&quot;74&quot; y=&quot;122&quot; text-anchor=&quot;middle&quot;&gt;group&lt;/text&gt;
&lt;rect class=&quot;boxA&quot; x=&quot;94&quot; y=&quot;56&quot; width=&quot;80&quot; height=&quot;46&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;rng&quot; x=&quot;134&quot; y=&quot;83&quot; text-anchor=&quot;middle&quot;&gt;29:26&lt;/text&gt;
&lt;text class=&quot;nmA&quot; x=&quot;134&quot; y=&quot;122&quot; text-anchor=&quot;middle&quot;&gt;sub&lt;/text&gt;
&lt;rect class=&quot;box&quot; x=&quot;174&quot; y=&quot;56&quot; width=&quot;100&quot; height=&quot;46&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;rng&quot; x=&quot;224&quot; y=&quot;83&quot; text-anchor=&quot;middle&quot;&gt;25:21&lt;/text&gt;
&lt;text class=&quot;nm&quot; x=&quot;224&quot; y=&quot;122&quot; text-anchor=&quot;middle&quot;&gt;rd&lt;/text&gt;
&lt;rect class=&quot;box&quot; x=&quot;274&quot; y=&quot;56&quot; width=&quot;100&quot; height=&quot;46&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;rng&quot; x=&quot;324&quot; y=&quot;83&quot; text-anchor=&quot;middle&quot;&gt;20:16&lt;/text&gt;
&lt;text class=&quot;nm&quot; x=&quot;324&quot; y=&quot;122&quot; text-anchor=&quot;middle&quot;&gt;rs1&lt;/text&gt;
&lt;rect class=&quot;box&quot; x=&quot;374&quot; y=&quot;56&quot; width=&quot;100&quot; height=&quot;46&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;rng&quot; x=&quot;424&quot; y=&quot;83&quot; text-anchor=&quot;middle&quot;&gt;15:11&lt;/text&gt;
&lt;text class=&quot;nm&quot; x=&quot;424&quot; y=&quot;122&quot; text-anchor=&quot;middle&quot;&gt;rs2&lt;/text&gt;
&lt;rect class=&quot;box&quot; x=&quot;474&quot; y=&quot;56&quot; width=&quot;220&quot; height=&quot;46&quot; rx=&quot;4&quot;/&gt;
&lt;text class=&quot;rng&quot; x=&quot;584&quot; y=&quot;83&quot; text-anchor=&quot;middle&quot;&gt;10:0&lt;/text&gt;
&lt;text class=&quot;nm&quot; x=&quot;584&quot; y=&quot;122&quot; text-anchor=&quot;middle&quot;&gt;imm&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The 32-bit instruction word, MSB (bit 31) on the left. The two-bit &lt;code&gt;group&lt;/code&gt; and four-bit &lt;code&gt;sub&lt;/code&gt; form the opcode; &lt;code&gt;rd&lt;/code&gt;, &lt;code&gt;rs1&lt;/code&gt;, &lt;code&gt;rs2&lt;/code&gt; are five-bit register selectors and &lt;code&gt;imm&lt;/code&gt; is an eleven-bit immediate. Widths sum to exactly 32. Every opcode reads the same fields from the same bits; which operands it &lt;em&gt;uses&lt;/em&gt; is what distinguishes them, and every bit an opcode does not use is reserved-must-be-zero.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The register fields are five bits wide, and that width is a small piece of
the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;RISC-V flavour&lt;/a&gt; the project borrows:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;-- |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; A 5-bit register selector (RISC-V-standard field width). v1 uses x0..x15.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Five bits can name thirty-two registers; v1 implements sixteen and lets
the top bit go unused, so &lt;code&gt;Reg&lt;/code&gt; is wider than the register file behind it.
That is deliberate room to grow, and it means a register selector is just
five bits of a word — not yet an index into anything, only a name. The
thing it names is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;next post’s&lt;/a&gt; subject; here it is a field.&lt;/p&gt;
&lt;h2 id=&quot;one-constructor-per-opcode&quot;&gt;One constructor per opcode&lt;/h2&gt;
&lt;p&gt;The instruction set is a Haskell data type. Every opcode is a
constructor, and every constructor carries exactly the operands its opcode
uses — no more, so the type itself refuses to build an &lt;code&gt;Add&lt;/code&gt; without
three registers or a &lt;code&gt;Halt&lt;/code&gt; without a status:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Instr&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; BUS group (group 00)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    CsAssert&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; CsDeassert&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; PutByteImm&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; PutByteReg&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; GetByte&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; PutBitsImm&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; n-1, bits (n = count in 1..8)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; PutBitsReg&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; GetBits&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TarImm&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TarReg&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RstAssert&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RstDeassert&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; GetAlert&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; CTRL group (group 01)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    Halt&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Beq&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 11&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Bne&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 11&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Bltu&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 11&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Bgeu&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 11&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; WaitOn&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 9&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; rd, cond, timeout&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; SetConfig&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 6&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Mark&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 11&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;               --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; label, payload reg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; CrcReset&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; DATA group (group 10)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    LoadImm&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 21&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Lui&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 21&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Mov&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Add&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Addi&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 11&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Sub&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; … And_, Andi, Or_, Ori, Xor_, Xori, all Reg Reg (Reg | imm) …&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Shift&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 5&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; rd, rs1, op, amt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Rdsr&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 5&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;                     --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; rd, sr#&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thirty-six constructors, three groups, and every one of them is a name you
have already seen &lt;code&gt;execInstr&lt;/code&gt; &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;match on&lt;/a&gt;. The grouping is the
opcode’s top structure made into comments: &lt;code&gt;group 00&lt;/code&gt; is BUS, the
thirteen opcodes that touch the wire; &lt;code&gt;group 01&lt;/code&gt; is CTRL, the nine for
flow and configuration; &lt;code&gt;group 10&lt;/code&gt; is DATA, the fourteen that compute. The
fourth group, &lt;code&gt;11&lt;/code&gt;, has no constructors at all — it is reserved, and a
word in it decodes to nothing.&lt;/p&gt;
&lt;p&gt;Read the operand types and the encoding starts to show through the ADT.
&lt;code&gt;PutByteImm&lt;/code&gt; carries a &lt;code&gt;BitVector 8&lt;/code&gt;, the byte it will drive. &lt;code&gt;Beq&lt;/code&gt; carries
two &lt;code&gt;Reg&lt;/code&gt;s and a &lt;code&gt;BitVector 11&lt;/code&gt;, the two sources and the branch offset —
the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;PC arithmetic from the dispatch post&lt;/a&gt; operated on exactly that
eleven-bit field. &lt;code&gt;PutBitsImm&lt;/code&gt; carries an &lt;code&gt;Index 8&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; a byte, because
“put n bits” needs both a count and the bits; the comment &lt;code&gt;n-1&lt;/code&gt; warns that
the field holds one less than the count, a detail the encoder will handle.
And &lt;code&gt;LoadImm&lt;/code&gt; carries a &lt;code&gt;BitVector 21&lt;/code&gt; — a twenty-one-bit immediate, wider
than the eleven-bit &lt;code&gt;imm&lt;/code&gt; field, which is the one interesting packing
problem in the whole module. Hold that thought; the encoder solves it in
two lines.&lt;/p&gt;
&lt;p&gt;Beside &lt;code&gt;Instr&lt;/code&gt; sits the type that says why a word was &lt;em&gt;not&lt;/em&gt; an
instruction:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; DecodeError&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; ReservedFieldNonZero&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; OpcodeUnimplemented&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; IllegalOpcode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three ways to fail: a reserved field that was not zero, an opcode
recognised but not yet built, an opcode that is illegal outright. These
are the &lt;code&gt;Left&lt;/code&gt; the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch post’s&lt;/a&gt; &lt;code&gt;stepExec&lt;/code&gt; turned into a
trap&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-unimpl-1&quot;&gt;&lt;a href=&quot;#fn-unimpl&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; — and now we are on the other side of that &lt;code&gt;Left&lt;/code&gt;, in the
code that produces it.&lt;/p&gt;
&lt;h2 id=&quot;encode-the-easy-direction&quot;&gt;Encode: the easy direction&lt;/h2&gt;
&lt;p&gt;Turning an &lt;code&gt;Instr&lt;/code&gt; into a word is the easy half, because an &lt;code&gt;Instr&lt;/code&gt; is
already valid by construction — the type would not let you build a
malformed one — so the encoder only has to &lt;em&gt;place&lt;/em&gt; the operands and
zero everything else. It is a total &lt;code&gt;\case&lt;/code&gt;, one arm per constructor, and
every arm calls the same helper:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Instr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;encode &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; \&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;case&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  CsAssert&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;       -&amp;gt;&lt;/span&gt;&lt;span&gt; joinW &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0b00&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  PutByteImm&lt;/span&gt;&lt;span&gt; b   &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; joinW &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0b00&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; zeroExtend b&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  GetByte&lt;/span&gt;&lt;span&gt; rd     &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; joinW &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0b00&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x4&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; rd&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Beq&lt;/span&gt;&lt;span&gt; a b off    &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; joinW &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0b01&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; off&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  WaitOn&lt;/span&gt;&lt;span&gt; rd c t  &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; joinW &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0b01&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x5&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; rd&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; bitCoerce &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; t&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Add&lt;/span&gt;&lt;span&gt; rd a b     &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; joinW &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0b10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; rd&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Shift&lt;/span&gt;&lt;span&gt; rd a op a5 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; joinW &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0b10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0xC&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; rd&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; bitCoerce &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;op&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; a5&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; … thirty-odd arms in the same shape …&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every arm reads the same way: pick the &lt;code&gt;group&lt;/code&gt; and &lt;code&gt;sub&lt;/code&gt; that name the
opcode, drop the operands into their fields, and put &lt;code&gt;0&lt;/code&gt; everywhere else.
&lt;code&gt;CsAssert&lt;/code&gt; has no operands, so it is all opcode and zeros. &lt;code&gt;GetByte&lt;/code&gt; uses
only &lt;code&gt;rd&lt;/code&gt;. &lt;code&gt;Beq&lt;/code&gt; fills &lt;code&gt;rs1&lt;/code&gt;, &lt;code&gt;rs2&lt;/code&gt;, and &lt;code&gt;imm&lt;/code&gt; with its two sources and
its offset, and leaves &lt;code&gt;rd&lt;/code&gt; zero. The zeros are the reserved fields, and
the encoder writes them faithfully — which is what makes a decode able
to &lt;em&gt;check&lt;/em&gt; them later.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;joinW&lt;/code&gt; is the field-packer, and it is one of a small family of functions
that are all the same trick:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Fields&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 5&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 5&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 5&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 11&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;splitWord&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Fields&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;splitWord &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; bitCoerce&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;joinW&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Fields&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;joinW &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; bitCoerce&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Fields&lt;/code&gt; is the word’s six fields as a tuple, and &lt;code&gt;joinW&lt;/code&gt; and &lt;code&gt;splitWord&lt;/code&gt;
are both just &lt;code&gt;bitCoerce&lt;/code&gt; — the same &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;structural bit-reinterpretation the
trace records used&lt;/a&gt; to pack a tag and a payload into one word. Here it
lays six fields end to end into thirty-two bits, or takes them back apart,
and the type system checks the arithmetic: &lt;code&gt;2 + 4 + 5 + 5 + 5 + 11 = 32&lt;/code&gt;,
so the coercion is total and lossless, a reshaping of bits with nothing
added and nothing dropped.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-bitcoerce-1&quot;&gt;&lt;a href=&quot;#fn-bitcoerce&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt; &lt;code&gt;WaitOn&lt;/code&gt; and &lt;code&gt;Shift&lt;/code&gt; reach for
&lt;code&gt;bitCoerce&lt;/code&gt; a second time, to pack &lt;em&gt;sub-fields&lt;/em&gt; of the immediate — a
condition and a timeout, or a shift-op and an amount — into the eleven
bits they share.&lt;/p&gt;
&lt;p&gt;And the twenty-one-bit immediate that did not fit? It is split across the
three fields nobody else was using:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;joinImm21&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 5&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 5&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 11&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 21&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;joinImm21 rs1 rs2 imm &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; bitCoerce &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rs1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;#&lt;/span&gt;&lt;span&gt; rs2 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;#&lt;/span&gt;&lt;span&gt; imm&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;LoadImm&lt;/code&gt; and &lt;code&gt;Lui&lt;/code&gt; have no register sources, so &lt;code&gt;rs1&lt;/code&gt;, &lt;code&gt;rs2&lt;/code&gt;, and &lt;code&gt;imm&lt;/code&gt;
— five plus five plus eleven — sit empty and adjacent, exactly
twenty-one bits, exactly the room a wide immediate needs. The encoder
glues them into one &lt;code&gt;BitVector 21&lt;/code&gt; on the way out and the decoder splits
them back on the way in. It is a small, satisfying piece of format design:
the widest immediate in the ISA is carved out of the fields the
immediate-loading opcodes had no other use for.&lt;/p&gt;
&lt;h2 id=&quot;decode-and-the-reserved-zero&quot;&gt;Decode, and the reserved zero&lt;/h2&gt;
&lt;p&gt;Decoding is the hard direction, because now the input is &lt;em&gt;any&lt;/em&gt; thirty-two
bits, most of which are not valid instructions, and the decoder has to
tell which is which. It begins by splitting the word and dispatching on the
group:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Either&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; DecodeError&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Instr&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;decode w &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  case&lt;/span&gt;&lt;span&gt; grp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;    0b00&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; decodeBus sub&amp;#39; rd rs1 rs2 imm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;    0b01&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; decodeCtrl sub&amp;#39; rd rs1 rs2 imm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;    0b10&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; decodeData sub&amp;#39; rd rs1 rs2 imm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Left&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; IllegalOpcode&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; group 11 reserved&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;grp&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; sub&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; rd&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; rs1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; rs2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; imm&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; splitWord w&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;splitWord&lt;/code&gt; takes the word apart into the six fields — the inverse of the
&lt;code&gt;joinW&lt;/code&gt; the encoder used — and the group selects one of three per-group
decoders, or, for the reserved group &lt;code&gt;11&lt;/code&gt;, rejects outright. Each
per-group decoder is a &lt;code&gt;case&lt;/code&gt; on the sub-opcode that rebuilds the
constructor. But rebuilding is not enough: the decoder must also &lt;em&gt;refuse&lt;/em&gt; a
word whose reserved fields are not zero, and that refusal is the heart of
the module. It runs through one tiny guard:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;only&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Instr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Either&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; DecodeError&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Instr&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;only ok r &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; ok &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Right&lt;/span&gt;&lt;span&gt; r &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Left&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; ReservedFieldNonZero&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;only&lt;/code&gt; takes a condition and an instruction and hands back the instruction
&lt;em&gt;only&lt;/em&gt; when the condition holds. The condition is always the same kind of
thing — “every field this opcode does not use is zero” — and you can
read it plainly in the BUS decoder:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;decodeBus sub&amp;#39; rd rs1 rs2 imm &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  case&lt;/span&gt;&lt;span&gt; sub&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;    0x0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; only &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;z rd &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; z rs1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; z rs2 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; z imm&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; CsAssert&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;       --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; no operands&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;    0x2&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; only &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;z rd &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; z rs1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; z rs2 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; immHi8 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;PutByteImm&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;truncateB imm&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;    0x3&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; only &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;z rd &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; z rs2 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; z imm&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;PutByteReg&lt;/span&gt;&lt;span&gt; rs1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;         --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; rs1 used&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;    0x4&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; only &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;z rs1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; z rs2 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; z imm&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;GetByte&lt;/span&gt;&lt;span&gt; rd&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;            --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; rd used&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;    --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; …&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;    0xc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; only &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;z rs1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; z rs2 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; z imm&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;GetAlert&lt;/span&gt;&lt;span&gt; rd&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Left&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; IllegalOpcode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  z &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  immHi8 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; slice d10 d8 imm  &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; imm[10:8] reserved for PUT_BYTE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;z&lt;/code&gt; is “is zero.” Every arm names the fields its opcode &lt;em&gt;uses&lt;/em&gt; and asserts
&lt;code&gt;z&lt;/code&gt; on all the others. &lt;code&gt;CsAssert&lt;/code&gt; uses nothing, so all four operand fields
must be zero. &lt;code&gt;GetByte&lt;/code&gt; uses &lt;code&gt;rd&lt;/code&gt;, so &lt;code&gt;rs1&lt;/code&gt;, &lt;code&gt;rs2&lt;/code&gt;, and &lt;code&gt;imm&lt;/code&gt; must be zero.
&lt;code&gt;PutByteImm&lt;/code&gt; uses the low eight bits of &lt;code&gt;imm&lt;/code&gt; for its byte and reserves
the top three — &lt;code&gt;immHi8 == 0&lt;/code&gt; — so a &lt;code&gt;PUT_BYTE&lt;/code&gt; word with junk in
&lt;code&gt;imm[10:8]&lt;/code&gt; is rejected even though its opcode is real. The decoder is
strict about the whole word, not just the opcode: a legal instruction is a
legal opcode &lt;em&gt;and&lt;/em&gt; clean reserved bits. &lt;code&gt;decodeCtrl&lt;/code&gt; and &lt;code&gt;decodeData&lt;/code&gt; are
the same shape at greater length, each checking the exact bits its
opcodes leave unused — &lt;code&gt;HALT&lt;/code&gt; reserving &lt;code&gt;imm[10:8]&lt;/code&gt;, &lt;code&gt;SET_CONFIG&lt;/code&gt;
reserving &lt;code&gt;imm[10:6]&lt;/code&gt;, the reserved shift-op &lt;code&gt;0b11&lt;/code&gt; refused, and so on.&lt;/p&gt;
&lt;h2 id=&quot;total-both-ways&quot;&gt;Total, both ways&lt;/h2&gt;
&lt;p&gt;Why be this strict? Because the strictness buys two laws, and the two laws
are what make an instruction encoding trustworthy.&lt;/p&gt;
&lt;p&gt;The first is the &lt;strong&gt;round trip&lt;/strong&gt;: &lt;code&gt;decode (encode i) == Right i&lt;/code&gt;. Encode any
instruction and decode the result and you get the instruction back. This
is nearly free — the encoder places operands and zeroes reserved fields,
the decoder reads operands and checks reserved fields are zero, so of
course they agree. It says the encoder and decoder are inverses on the
instructions.&lt;/p&gt;
&lt;p&gt;The second is deeper and is the one the reserved zero is really for:
&lt;strong&gt;canonicity&lt;/strong&gt;. Over &lt;em&gt;every&lt;/em&gt; thirty-two-bit word — not just the ones the
encoder produces, but all four billion of them — any word &lt;code&gt;decode&lt;/code&gt;
accepts re-encodes to itself: &lt;code&gt;encode (decode w) == w&lt;/code&gt; whenever &lt;code&gt;decode w&lt;/code&gt;
succeeds. There are no two words that decode to the same instruction,
because the only words decode accepts are the ones with zero reserved
fields, which is exactly the set the encoder emits. Drop the reserved-field
checks and this law dies: a hundred different &lt;code&gt;imm[10:8]&lt;/code&gt; values would all
decode to the same &lt;code&gt;PUT_BYTE&lt;/code&gt;, the mapping would be many-to-one, and the
word would no longer be a &lt;em&gt;canonical&lt;/em&gt; name for the instruction. The
&lt;code&gt;only&lt;/code&gt; guard is what keeps the correspondence one-to-one.&lt;/p&gt;
&lt;p&gt;And under both laws sits totality. &lt;code&gt;decode :: BitVector 32 -&amp;gt; Either DecodeError Instr&lt;/code&gt; is a total function: every one of the $2^{32}$ possible
words has a defined answer, an &lt;code&gt;Instr&lt;/code&gt; or a named error, with no partial
case, no crash, no loop. That is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer’s&lt;/a&gt; “totality is a
truth table” cashed at scale — decode is a four-billion-row truth table,
which in hardware is exactly what it becomes: a combinational decoder that
wires every input pattern to an output.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-truthtable-1&quot;&gt;&lt;a href=&quot;#fn-truthtable&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch
post’s&lt;/a&gt; &lt;code&gt;Left -&amp;gt; trap&lt;/code&gt; was this totality made visible from the
outside: because decode has a defined answer for &lt;em&gt;every&lt;/em&gt; word, a corrupted
or malicious program cannot reach an undefined state through the front
door. Every word either is an instruction or is a trap with a reason.&lt;/p&gt;
&lt;h2 id=&quot;a-decode-within-the-decode&quot;&gt;A decode within the decode&lt;/h2&gt;
&lt;p&gt;One BUS-group neighbour we passed over has an inside of its own.
&lt;code&gt;SetConfig&lt;/code&gt; carries a &lt;code&gt;BitVector 6&lt;/code&gt;, and back in the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch post&lt;/a&gt;
its arm ran that payload through &lt;code&gt;decodeConfig&lt;/code&gt; and trapped on a &lt;code&gt;Left&lt;/code&gt;.
That function lives in &lt;code&gt;Tamal.Config&lt;/code&gt;, and it is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;fold-in this post
promised&lt;/a&gt; — because decoding a configuration payload is the same
act as decoding an instruction, one level down.&lt;/p&gt;
&lt;p&gt;The shape is identical: a total function from a bitfield to &lt;code&gt;Either&lt;/code&gt; an
error or a typed value.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;decodeConfig&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 6&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Either&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; ConfigError&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Config&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;decodeConfig p &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  case&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;role&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; io&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; sck&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0b0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0b00&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0b00&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Right&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Controller&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; X1&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Sck20&lt;/span&gt;&lt;span&gt; alertSrc&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0b1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; _&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; _&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;         -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Left&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; UnsupportedRole&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; io&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; _&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span&gt; io&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;/=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0b00&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Left&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; UnsupportedIoMode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    _                   &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Left&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; UnsupportedSck&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;role&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; io&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; sck&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; alert&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; bitCoerce p &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  alertSrc &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; alert &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; then&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; AlertPin&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; else&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; AlertIo1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Six bits come apart by &lt;code&gt;bitCoerce&lt;/code&gt; — the same field-split, smaller — into
a one-bit role, a two-bit I/O mode, a two-bit clock, and a one-bit alert
source. And then the striking part: only &lt;em&gt;one&lt;/em&gt; combination is accepted.
&lt;code&gt;(0b0, 0b00, 0b00)&lt;/code&gt; — controller role, single I/O, 20 MHz — decodes to a
&lt;code&gt;Config&lt;/code&gt;; every other role traps &lt;code&gt;UnsupportedRole&lt;/code&gt;, every other I/O mode
&lt;code&gt;UnsupportedIoMode&lt;/code&gt;, every other clock &lt;code&gt;UnsupportedSck&lt;/code&gt;. The values it
decodes into are a little vocabulary of their own:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Role&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Controller&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Target&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; IoMode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; X1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; X2&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; X4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Sck&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;         =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Sck20&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Sck33&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Sck50&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Sck66&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; AlertSource&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; AlertPin&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; AlertIo1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Config&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Config&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; cfgRole&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Role&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; cfgIoMode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; IoMode&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; cfgSck&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Sck&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; cfgAlertSource&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; AlertSource&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The types describe the &lt;em&gt;whole&lt;/em&gt; eSPI configuration space — &lt;code&gt;Target&lt;/code&gt;,
&lt;code&gt;X2&lt;/code&gt;/&lt;code&gt;X4&lt;/code&gt;, the faster clocks — but &lt;code&gt;decodeConfig&lt;/code&gt; accepts only the corner
of it v1 actually implements, and names the rest as errors rather than
pretending they work. This is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;introduction’s&lt;/a&gt; honesty line
turned into code: the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape post’s&lt;/a&gt; &lt;code&gt;powerUpDefault&lt;/code&gt; — controller,
x1, 20 MHz, alert pin — is the one config that decodes, and the alert
source is the single bit v1 lets you actually choose. The reserved-zero
discipline of the ISA has a cousin here: a config the engine cannot honour
is refused at decode, so &lt;code&gt;SET_CONFIG&lt;/code&gt; can never quietly leave the engine in
a mode its gates do not implement. It traps instead, &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;reason two&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-tests&quot;&gt;The tests&lt;/h2&gt;
&lt;p&gt;A module whose whole job is a bijection over a word space is a module you
can test &lt;em&gt;completely&lt;/em&gt;, and &lt;code&gt;Test.Isa&lt;/code&gt; does, from both directions at once.
The round trips are one property per group:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;BUS: decode . encode == Right&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genBusInstr&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  decode &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;encode i&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ===&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Right&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://hedgehog.qa&quot;&gt;Hedgehog&lt;/a&gt; draws a random instruction, encodes it, decodes the
result, and demands the original back — the first law, checked across
hundreds of generated instructions per group. But the property that earns
its keep is the second one, and it runs over the &lt;em&gt;whole word space&lt;/em&gt;, not
the image of the encoder:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;any 32-bit word: decode is canonical or traps&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  w &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genWord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  case&lt;/span&gt;&lt;span&gt; decode w &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    Right&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; encode i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;===&lt;/span&gt;&lt;span&gt; w&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    Left&lt;/span&gt;&lt;span&gt; _  &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; success&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read what this checks. Draw &lt;em&gt;any&lt;/em&gt; thirty-two-bit word — valid or
garbage, any group, any junk in any reserved field. If &lt;code&gt;decode&lt;/code&gt; rejects it,
fine, &lt;code&gt;success&lt;/code&gt;. But if &lt;code&gt;decode&lt;/code&gt; &lt;em&gt;accepts&lt;/em&gt; it, the accepted instruction
must re-encode to the very word we started from. That is canonicity stated
as a test: there is no word decode accepts that is not the encoder’s own
output for that instruction. A single too-lax reserved-field check — one
&lt;code&gt;only&lt;/code&gt; that forgot a field — and Hedgehog would find a word that decodes
to an instruction whose encoding differs, and the property would go red
with the counterexample in hand. It is the reserved zero, guarded from the
outside.&lt;/p&gt;
&lt;p&gt;Two &lt;code&gt;HUnit&lt;/code&gt; cases pin the reserved discipline to specific words:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testCase &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;reserved non-zero field traps (CS_ASSERT with junk imm)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  decode &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;busWord &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0b00&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; +&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @?=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Left&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; ReservedFieldNonZero&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testCase &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;reserved SHIFT op (0b11) traps&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  decode &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;encode &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Shift&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0b11&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @?=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Left&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; ReservedFieldNonZero&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first takes a clean &lt;code&gt;CS_ASSERT&lt;/code&gt; word and sets one reserved bit; it must
trap. The second is sharper: &lt;code&gt;encode&lt;/code&gt; never validates, so it will happily
build a &lt;code&gt;SHIFT&lt;/code&gt; word with the reserved op &lt;code&gt;0b11&lt;/code&gt; — and the tightened
decoder must reject what the encoder was willing to write. That gap between
a permissive encoder and a strict decoder is the safe direction to err:
the engine only ever runs &lt;code&gt;decode&lt;/code&gt;, so the decoder is the gate, and the
test proves the gate is shut.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Test.Config&lt;/code&gt; does the same for the fold-in, five cases wide: the v1
default decodes, the in-band alert source decodes, and target role, dual
I/O, and 33 MHz each trap with their own error. The whole legal surface
and three witnesses that the illegal surface is refused.&lt;/p&gt;
&lt;h2 id=&quot;what-we-read&quot;&gt;What we read&lt;/h2&gt;
&lt;p&gt;The first box, opened. The instruction set is a thirty-two-bit word in six
fields, a data type with one constructor per opcode across three groups and
a reserved fourth, and two functions that are inverses over exactly the
legal words. &lt;code&gt;encode&lt;/code&gt; is the easy direction — an &lt;code&gt;Instr&lt;/code&gt; is valid by
construction, so encoding only places operands and zeroes the reserved
fields, &lt;code&gt;bitCoerce&lt;/code&gt; doing the packing the way it packed the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-bus/&quot;&gt;trace
records&lt;/a&gt;, the widest immediate carved from the fields its opcodes left
spare. &lt;code&gt;decode&lt;/code&gt; is the hard one, dispatching on the group and accepting a
word &lt;em&gt;only&lt;/em&gt; when every reserved field is zero — and that strictness buys
the round trip, canonicity, and a totality that gives every one of four
billion words a defined home, which is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;trap&lt;/a&gt; seen from the
inside. The folded &lt;code&gt;Config&lt;/code&gt; is the same function one level down, six bits
to a role and a mode and a clock, accepting the single corner v1
implements and naming the rest as errors. And the tests check the bijection
across the whole word space, not a sample of it.&lt;/p&gt;
&lt;p&gt;We opened &lt;code&gt;decode&lt;/code&gt; because it was the engine’s first act on any word. Its
last act on that word — once decoded, the very next thing &lt;code&gt;execInstr&lt;/code&gt;
did — was to read the registers the operands named: &lt;code&gt;readReg (regs s) (operandRs1 i)&lt;/code&gt;. Those register selectors were &lt;code&gt;Reg&lt;/code&gt;s, the five-bit fields
we met at the top of this post, still only &lt;em&gt;names&lt;/em&gt;. Next we open the thing
they name: &lt;code&gt;Tamal.RegFile&lt;/code&gt;, the sixteen registers, the hardwired zero, and
the two ports the datapath read and wrote through the whole time we were
holding it shut.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-unimpl&quot;&gt;
&lt;p&gt;&lt;code&gt;OpcodeUnimplemented&lt;/code&gt; is the one &lt;code&gt;DecodeError&lt;/code&gt; the current
decoders never actually return — they reject with &lt;code&gt;IllegalOpcode&lt;/code&gt; (an
unknown sub-opcode, or the reserved group) or &lt;code&gt;ReservedFieldNonZero&lt;/code&gt; (a
dirty reserved field). It exists in the type for the case of an opcode that
is &lt;em&gt;recognised&lt;/em&gt; but not yet built, so that a future partial implementation
can distinguish “this is not an instruction” from “this is an instruction I
have not finished.” Declaring the variant now costs nothing and reserves
the vocabulary; the engine treats all three the same way, as a trap. &lt;a href=&quot;#fr-unimpl-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-bitcoerce&quot;&gt;
&lt;p&gt;&lt;code&gt;bitCoerce&lt;/code&gt; is Clash’s total, zero-cost reinterpretation of a
value as another type of the &lt;em&gt;same bit width&lt;/em&gt; — it is &lt;code&gt;pack&lt;/code&gt; followed by
&lt;code&gt;unpack&lt;/code&gt;, and it lowers to no gates at all, only a relabelling of wires.
The safety is entirely in the widths: &lt;code&gt;joinW :: Fields -&amp;gt; BitVector 32&lt;/code&gt;
typechecks only because the six fields sum to thirty-two, so a field-layout
error is a compile error, not a silently truncated word. It is the same
“widths live in the type” guarantee the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt; opened with, doing the
load-bearing work in a format definition — the reason the ISA can be
described as tuples of &lt;code&gt;BitVector&lt;/code&gt;s and trusted to pack correctly. &lt;a href=&quot;#fr-bitcoerce-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-truthtable&quot;&gt;
&lt;p&gt;The hardware reading matters because &lt;code&gt;decode&lt;/code&gt; is not a
program the engine runs step by step — it is a combinational function the
&lt;code&gt;Fetch&lt;/code&gt;-to-&lt;code&gt;Exec&lt;/code&gt; boundary evaluates in a single cycle. Clash turns the
nested &lt;code&gt;case&lt;/code&gt;s into a tree of multiplexers and comparators: the group
selects a per-group decoder, the sub selects an arm, the reserved-field
tests become AND-reductions over the unused bits. There is no loop and no
sequencing, because a truth table has none; the whole of &lt;code&gt;decode&lt;/code&gt; is one
wide slab of logic that turns thirty-two input wires into a decoded
instruction and a valid bit, every cycle, for whatever word &lt;code&gt;Fetch&lt;/code&gt;
happened to hand it. &lt;a href=&quot;#fr-truthtable-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Tamal: The Wire and the Record</title>
    <published>2026-08-04T09:00:00+00:00</published>
    <updated>2026-08-04T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-engine-bus/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-engine-bus/</id>
    <summary type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;last post&lt;/a&gt; ended four instructions mid-gesture. &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;GET&lt;/code&gt;,
&lt;code&gt;TAR&lt;/code&gt;, &lt;code&gt;MARK&lt;/code&gt;, and &lt;code&gt;WAIT_ON&lt;/code&gt; each spent their one cycle in &lt;code&gt;Exec&lt;/code&gt; not
finishing but &lt;em&gt;arming&lt;/em&gt; — a shifter loaded, a beat count set, a write
recorded in &lt;code&gt;pending&lt;/code&gt;, a phase changed — and then the cycle ended and
they handed off to a longer phase. This post is the other side of that
handoff: &lt;code&gt;BusBeat&lt;/code&gt;, &lt;code&gt;TraceEmit&lt;/code&gt;, and &lt;code&gt;WaitAlert&lt;/code&gt;, the three phases where
the arming is spent. It is where the engine finally touches the wire.&lt;/p&gt;
&lt;p&gt;Everything until now has been decision. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape&lt;/a&gt; was the map;
&lt;code&gt;Exec&lt;/code&gt; was the choice of what to do. Here the engine &lt;em&gt;does&lt;/em&gt; it: it drives
and samples two-and-a-half wires on a clock it makes itself, and records
what happened into a ring the host will later read back. Two subjects,
then — the wire and the record — and they meet in the one phase that
runs the bus.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;last post&lt;/a&gt; ended four instructions mid-gesture. &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;GET&lt;/code&gt;,
&lt;code&gt;TAR&lt;/code&gt;, &lt;code&gt;MARK&lt;/code&gt;, and &lt;code&gt;WAIT_ON&lt;/code&gt; each spent their one cycle in &lt;code&gt;Exec&lt;/code&gt; not
finishing but &lt;em&gt;arming&lt;/em&gt; — a shifter loaded, a beat count set, a write
recorded in &lt;code&gt;pending&lt;/code&gt;, a phase changed — and then the cycle ended and
they handed off to a longer phase. This post is the other side of that
handoff: &lt;code&gt;BusBeat&lt;/code&gt;, &lt;code&gt;TraceEmit&lt;/code&gt;, and &lt;code&gt;WaitAlert&lt;/code&gt;, the three phases where
the arming is spent. It is where the engine finally touches the wire.&lt;/p&gt;
&lt;p&gt;Everything until now has been decision. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape&lt;/a&gt; was the map;
&lt;code&gt;Exec&lt;/code&gt; was the choice of what to do. Here the engine &lt;em&gt;does&lt;/em&gt; it: it drives
and samples two-and-a-half wires on a clock it makes itself, and records
what happened into a ring the host will later read back. Two subjects,
then — the wire and the record — and they meet in the one phase that
runs the bus.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;sck-is-the-fabric-clock-divided-by-five&quot;&gt;SCK is the fabric clock, divided by five&lt;/h2&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;introduction&lt;/a&gt; made a point of insisting that Tamal is not a
throughput problem but a &lt;em&gt;timing-alignment&lt;/em&gt; problem — that the engine
does not need to be fast, it needs to be precise about &lt;em&gt;when&lt;/em&gt; it drives,
samples, and lets go. All of that precision lives in one small counter
and the function that reads it:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;sckOf&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 5&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sckOf &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; boolToBit &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;gt;=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 3&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;SCK&lt;/code&gt;, the serial clock, is the fabric clock divided by five. The
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape&lt;/a&gt; post named the field that does the dividing: &lt;code&gt;busPhase :: Index 5&lt;/code&gt;, a counter that walks &lt;code&gt;0, 1, 2, 3, 4&lt;/code&gt; and wraps, one step per
100 MHz fabric cycle. &lt;code&gt;sckOf&lt;/code&gt; turns that counter into a clock level with
a single comparison — &lt;code&gt;(&amp;gt;= 3)&lt;/code&gt; is true for phases three and four, false
for zero, one, two, and &lt;code&gt;boolToBit&lt;/code&gt; maps that to a &lt;code&gt;Bit&lt;/code&gt;.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-sckof-1&quot;&gt;&lt;a href=&quot;#fn-sckof&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; So
&lt;code&gt;SCK&lt;/code&gt; is &lt;strong&gt;low across phases {0, 1, 2}&lt;/strong&gt; and &lt;strong&gt;high across phases {3, 4}&lt;/strong&gt;:
three cycles low, two cycles high, five to a beat, one bit of eSPI per
beat.&lt;/p&gt;
&lt;p&gt;The duty cycle is lopsided — three-fifths low — and that is a
deliberate, honest compromise. A clean fifty-percent clock at this divisor
would need a hardware clocking primitive; a plain counter cannot split
five evenly. Rather than spend an MMCM to make the clock pretty, the
engine takes the asymmetry, which at 20 MHz an eSPI target does not care
about: what it cares about is that the edges are where the spec says, and
they are. The rising edge — the one that matters — falls at the &lt;strong&gt;two-
to-three boundary&lt;/strong&gt;, and both directions of the transfer are pinned to
it.&lt;/p&gt;
&lt;figure class=&quot;sck-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;sck&quot; viewBox=&quot;0 0 760 232&quot; role=&quot;img&quot; aria-labelledby=&quot;sck-t sck-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;sck-t&quot;&gt;SCK as the fabric clock divided by five, with the drive and sample points&lt;/title&gt;
&lt;desc id=&quot;sck-d&quot;&gt;Two bus beats are shown side by side, each five fabric cycles wide, the cycles numbered by busPhase zero through four. SCK is low across phases zero, one and two, and high across phases three and four, so each beat is three cycles low then two high. The rising edge falls at the two-to-three boundary. A driven PUT bit is placed at phase zero at the start of each beat; a GET bit is sampled from IO1 at phase three, on the rising edge.&lt;/desc&gt;
&lt;style&gt;
.sck{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.sck .grid{stroke:var(--fg-dim);stroke-width:1;opacity:.32}
.sck .beat{stroke:var(--fg-main);stroke-width:1.5}
.sck .sckw{stroke:var(--accent);stroke-width:2.5;fill:none}
.sck .edge{stroke:var(--accent);stroke-width:1.5;stroke-dasharray:4 4}
.sck .rlbl{fill:var(--fg-main);font-family:var(--mono);font-size:12.5px}
.sck .ph{fill:var(--fg-main);font-family:var(--mono);font-size:12px}
.sck .beatlbl{fill:var(--fg-dim);font-family:var(--sans);font-size:12.5px}
.sck .ev{fill:var(--fg-dim);font-family:var(--sans);font-size:11px}
.sck .evA{fill:var(--accent);font-family:var(--sans);font-size:11px}
.sck .node{fill:var(--accent)}
&lt;/style&gt;
&lt;!-- beat labels --&gt;
&lt;text class=&quot;beatlbl&quot; x=&quot;290&quot; y=&quot;24&quot; text-anchor=&quot;middle&quot;&gt;beat 1 (bit 7)&lt;/text&gt;
&lt;text class=&quot;beatlbl&quot; x=&quot;590&quot; y=&quot;24&quot; text-anchor=&quot;middle&quot;&gt;beat 2 (bit 6)&lt;/text&gt;
&lt;!-- gridlines --&gt;
&lt;line class=&quot;grid&quot; x1=&quot;200&quot; y1=&quot;38&quot; x2=&quot;200&quot; y2=&quot;150&quot;/&gt;
&lt;line class=&quot;grid&quot; x1=&quot;260&quot; y1=&quot;38&quot; x2=&quot;260&quot; y2=&quot;150&quot;/&gt;
&lt;line class=&quot;grid&quot; x1=&quot;320&quot; y1=&quot;38&quot; x2=&quot;320&quot; y2=&quot;150&quot;/&gt;
&lt;line class=&quot;grid&quot; x1=&quot;380&quot; y1=&quot;38&quot; x2=&quot;380&quot; y2=&quot;150&quot;/&gt;
&lt;line class=&quot;grid&quot; x1=&quot;500&quot; y1=&quot;38&quot; x2=&quot;500&quot; y2=&quot;150&quot;/&gt;
&lt;line class=&quot;grid&quot; x1=&quot;560&quot; y1=&quot;38&quot; x2=&quot;560&quot; y2=&quot;150&quot;/&gt;
&lt;line class=&quot;grid&quot; x1=&quot;620&quot; y1=&quot;38&quot; x2=&quot;620&quot; y2=&quot;150&quot;/&gt;
&lt;line class=&quot;grid&quot; x1=&quot;680&quot; y1=&quot;38&quot; x2=&quot;680&quot; y2=&quot;150&quot;/&gt;
&lt;!-- beat boundaries --&gt;
&lt;line class=&quot;beat&quot; x1=&quot;140&quot; y1=&quot;34&quot; x2=&quot;140&quot; y2=&quot;205&quot;/&gt;
&lt;line class=&quot;beat&quot; x1=&quot;440&quot; y1=&quot;34&quot; x2=&quot;440&quot; y2=&quot;205&quot;/&gt;
&lt;line class=&quot;beat&quot; x1=&quot;740&quot; y1=&quot;34&quot; x2=&quot;740&quot; y2=&quot;205&quot;/&gt;
&lt;!-- row labels --&gt;
&lt;text class=&quot;rlbl&quot; x=&quot;128&quot; y=&quot;60&quot; text-anchor=&quot;end&quot;&gt;busPhase&lt;/text&gt;
&lt;text class=&quot;rlbl&quot; x=&quot;128&quot; y=&quot;115&quot; text-anchor=&quot;end&quot;&gt;SCK&lt;/text&gt;
&lt;!-- busPhase numbers --&gt;
&lt;text class=&quot;ph&quot; x=&quot;170&quot; y=&quot;60&quot; text-anchor=&quot;middle&quot;&gt;0&lt;/text&gt;
&lt;text class=&quot;ph&quot; x=&quot;230&quot; y=&quot;60&quot; text-anchor=&quot;middle&quot;&gt;1&lt;/text&gt;
&lt;text class=&quot;ph&quot; x=&quot;290&quot; y=&quot;60&quot; text-anchor=&quot;middle&quot;&gt;2&lt;/text&gt;
&lt;text class=&quot;ph&quot; x=&quot;350&quot; y=&quot;60&quot; text-anchor=&quot;middle&quot;&gt;3&lt;/text&gt;
&lt;text class=&quot;ph&quot; x=&quot;410&quot; y=&quot;60&quot; text-anchor=&quot;middle&quot;&gt;4&lt;/text&gt;
&lt;text class=&quot;ph&quot; x=&quot;470&quot; y=&quot;60&quot; text-anchor=&quot;middle&quot;&gt;0&lt;/text&gt;
&lt;text class=&quot;ph&quot; x=&quot;530&quot; y=&quot;60&quot; text-anchor=&quot;middle&quot;&gt;1&lt;/text&gt;
&lt;text class=&quot;ph&quot; x=&quot;590&quot; y=&quot;60&quot; text-anchor=&quot;middle&quot;&gt;2&lt;/text&gt;
&lt;text class=&quot;ph&quot; x=&quot;650&quot; y=&quot;60&quot; text-anchor=&quot;middle&quot;&gt;3&lt;/text&gt;
&lt;text class=&quot;ph&quot; x=&quot;710&quot; y=&quot;60&quot; text-anchor=&quot;middle&quot;&gt;4&lt;/text&gt;
&lt;!-- SCK waveform: low {0,1,2}, high {3,4} --&gt;
&lt;path class=&quot;sckw&quot; d=&quot;M140,132 H320 V90 H440 V132 H620 V90 H740&quot;/&gt;
&lt;!-- rising-edge markers --&gt;
&lt;circle class=&quot;node&quot; cx=&quot;320&quot; cy=&quot;90&quot; r=&quot;4&quot;/&gt;
&lt;circle class=&quot;node&quot; cx=&quot;620&quot; cy=&quot;90&quot; r=&quot;4&quot;/&gt;
&lt;line class=&quot;edge&quot; x1=&quot;320&quot; y1=&quot;94&quot; x2=&quot;320&quot; y2=&quot;166&quot;/&gt;
&lt;line class=&quot;edge&quot; x1=&quot;620&quot; y1=&quot;94&quot; x2=&quot;620&quot; y2=&quot;166&quot;/&gt;
&lt;!-- events --&gt;
&lt;text class=&quot;ev&quot; x=&quot;170&quot; y=&quot;182&quot; text-anchor=&quot;middle&quot;&gt;drive @ φ0&lt;/text&gt;
&lt;text class=&quot;ev&quot; x=&quot;470&quot; y=&quot;182&quot; text-anchor=&quot;middle&quot;&gt;drive @ φ0&lt;/text&gt;
&lt;text class=&quot;evA&quot; x=&quot;320&quot; y=&quot;182&quot; text-anchor=&quot;middle&quot;&gt;sample @ φ3&lt;/text&gt;
&lt;text class=&quot;evA&quot; x=&quot;620&quot; y=&quot;182&quot; text-anchor=&quot;middle&quot;&gt;sample @ φ3&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;&lt;code&gt;SCK&lt;/code&gt; is the fabric clock ÷ 5. Each beat is &lt;code&gt;busPhase&lt;/code&gt; 0–4: three cycles low, two high, one eSPI bit. A &lt;code&gt;PUT&lt;/code&gt; places its bit on &lt;code&gt;IO0&lt;/code&gt; at the start of the beat (φ0); a &lt;code&gt;GET&lt;/code&gt; samples &lt;code&gt;IO1&lt;/code&gt; at the rising edge (the 2→3 boundary, φ3). Eight beats make a byte, forty fabric cycles. The waveform is the whole of the engine&#39;s timing discipline --- a counter, a comparison, and one edge everything is pinned to.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;one-beat-five-cycles&quot;&gt;One beat, five cycles&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;stepBusBeat&lt;/code&gt; runs once per fabric cycle while a transfer is in flight,
and its whole logic is a three-way guard — am I mid-beat, between
beats, or done?&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;stepBusBeat&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;State&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;stepBusBeat s inp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; busPhase s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;tick s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tick s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; mid-beat: advance phase&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; beatIx s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span&gt; beatTot s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;nextBeat s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;nextBeat s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; more beats&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; complete s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read the three arms as the life of a beat. While &lt;code&gt;busPhase &amp;lt; 4&lt;/code&gt; the beat
is still running, so &lt;code&gt;tick&lt;/code&gt; advances it one phase. Once &lt;code&gt;busPhase&lt;/code&gt; reaches
&lt;code&gt;4&lt;/code&gt; the beat is over, and the question becomes whether there is another:
if &lt;code&gt;beatIx + 1 &amp;lt; beatTot&lt;/code&gt; — more bits to go — &lt;code&gt;nextBeat&lt;/code&gt; starts the
next one; otherwise the transfer is finished and &lt;code&gt;complete&lt;/code&gt; closes it out.
&lt;code&gt;beatIx&lt;/code&gt; is the bit we are on, &lt;code&gt;beatTot&lt;/code&gt; the total the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;&lt;code&gt;Exec&lt;/code&gt;
setup&lt;/a&gt; wrote (eight for a byte). The whole of a &lt;code&gt;PUT&lt;/code&gt;, a &lt;code&gt;GET&lt;/code&gt;, or a
turnaround is this guard, iterated: five cycles a beat, &lt;code&gt;beatTot&lt;/code&gt; beats,
then done.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tick&lt;/code&gt; is where a single fabric cycle of a beat happens:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tick t &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; p &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; busPhase t &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        t1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; t&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;busPhase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; p&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; sck&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; sckOf p&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; p &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 3&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; then&lt;/span&gt;&lt;span&gt; sampleGet t1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;else&lt;/span&gt;&lt;span&gt; t1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It steps &lt;code&gt;busPhase&lt;/code&gt; up by one and sets &lt;code&gt;sck&lt;/code&gt; to &lt;code&gt;sckOf&lt;/code&gt; of the new phase
— the waveform, generated one cycle at a time. And it carries the one
event that has to happen on the rising edge: &lt;code&gt;if p == 3 then sampleGet&lt;/code&gt;.
Phase three is the first high phase, the tick that crosses the two-to-
three boundary, and that is exactly when a &lt;code&gt;GET&lt;/code&gt; must read the wire. So
&lt;code&gt;tick&lt;/code&gt; samples there and nowhere else.&lt;/p&gt;
&lt;p&gt;Between beats, &lt;code&gt;nextBeat&lt;/code&gt; resets the phase counter and sets up the next
bit:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  nextBeat t &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; bi &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; beatIx t &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span&gt; t&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;busPhase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; beatIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; bi&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; sck&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lanes&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; beatLanes t bi&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;busPhase&lt;/code&gt; back to zero, &lt;code&gt;beatIx&lt;/code&gt; up by one, &lt;code&gt;SCK&lt;/code&gt; back to idle-low, and
— the important part — &lt;code&gt;lanes = beatLanes t bi&lt;/code&gt;, the drive for the new
bit placed &lt;em&gt;now&lt;/em&gt;, at the start of the beat, at phase zero. That is the
“drive @ φ0” the diagram marks: each bit’s value goes onto the lanes as
its beat begins, and holds for the whole five cycles while &lt;code&gt;SCK&lt;/code&gt; pulses
once beneath it.&lt;/p&gt;
&lt;h2 id=&quot;what-each-beat-drives&quot;&gt;What each beat drives&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;beatLanes&lt;/code&gt; is the function that decides what the lanes carry, and it is a
three-way case on the one thing that distinguishes a &lt;code&gt;PUT&lt;/code&gt; from a &lt;code&gt;GET&lt;/code&gt;
from a turnaround — the &lt;code&gt;pending&lt;/code&gt; field:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;beatLanes&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Lanes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;beatLanes t bi &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; pending t &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  PendGet&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; hiZ&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  PendTar&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; tarBeat bi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; serializeX1 &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;shifter t&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; !!&lt;/span&gt;&lt;span&gt; bi&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three directions, one field. A &lt;code&gt;GET&lt;/code&gt; has &lt;code&gt;pending = PendGet …&lt;/code&gt;, so its
lanes are &lt;code&gt;hiZ&lt;/code&gt; — all drivers released, the bus handed to the other side
to drive, because on a read &lt;em&gt;we&lt;/em&gt; are the one sampling. A turnaround has
&lt;code&gt;PendTar&lt;/code&gt;, so its lanes are &lt;code&gt;tarBeat bi&lt;/code&gt; — the turnaround waveform for
bit &lt;code&gt;bi&lt;/code&gt;, which drives the first clock and releases the rest, the
handover choreography an eSPI bus does between command and response.
Anything else — a &lt;code&gt;PUT&lt;/code&gt;, whose &lt;code&gt;pending&lt;/code&gt; is &lt;code&gt;PendNone&lt;/code&gt; — serialises:
&lt;code&gt;serializeX1 (shifter t) !! bi&lt;/code&gt; takes the byte in the shifter and picks
out bit &lt;code&gt;bi&lt;/code&gt;, MSB-first, to drive on &lt;code&gt;IO0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Two of those three — &lt;code&gt;serializeX1&lt;/code&gt; and &lt;code&gt;tarBeat&lt;/code&gt;, along with &lt;code&gt;hiZ&lt;/code&gt; and
the &lt;code&gt;Lanes&lt;/code&gt; type itself — are the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;last shut box&lt;/a&gt; in this arc,
&lt;code&gt;Tamal.Bus.Serdes&lt;/code&gt;, the serialiser. We are using it exactly as before:
&lt;code&gt;serializeX1&lt;/code&gt; turns a byte into per-bit lane drives, &lt;code&gt;tarBeat&lt;/code&gt; turns a
clock index into a turnaround drive, &lt;code&gt;hiZ&lt;/code&gt; means “release.” &lt;em&gt;How&lt;/em&gt; they do
it is the final post. Here it is enough that &lt;code&gt;beatLanes&lt;/code&gt; reads the
&lt;code&gt;pending&lt;/code&gt; field and asks the serialiser for the right drive, and the fact
that all three eSPI directions fall out of one three-way case is the
whole shape of the thing.&lt;/p&gt;
&lt;h2 id=&quot;sampling-a-get&quot;&gt;Sampling a GET&lt;/h2&gt;
&lt;p&gt;The other half of a transfer is reading, and it is the &lt;code&gt;sampleGet&lt;/code&gt; that
&lt;code&gt;tick&lt;/code&gt; fires at phase three:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  sampleGet t &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; pending t &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    PendGet&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; t&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;shifter&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; shifter t &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;shiftL&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; .|.&lt;/span&gt;&lt;span&gt; zeroExtend &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pack &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ioIn inp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;!!&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; t&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Only a &lt;code&gt;GET&lt;/code&gt; samples — the guard is &lt;code&gt;PendGet{}&lt;/code&gt;, and everything else
passes the state through untouched. When it is a &lt;code&gt;GET&lt;/code&gt;, the move is the
classic serial-in shift: &lt;code&gt;shifter &amp;lt;&amp;lt; 1&lt;/code&gt; makes room at the bottom, and
&lt;code&gt;.|. … (ioIn inp !! 1)&lt;/code&gt; drops the sampled bit of &lt;code&gt;IO1&lt;/code&gt; — the eSPI
response lane — into that new low bit. Do that once per beat, at each
rising edge, and after eight beats the shifter holds the byte that came
back, assembled MSB-first: the first bit sampled has been shifted left
seven times to the top, the last sits at the bottom. It is the mirror of
the &lt;code&gt;PUT&lt;/code&gt;’s serialise — one drives a byte out a bit at a time, the other
shifts a byte in a bit at a time, both pinned to the same phase-three
edge.&lt;/p&gt;
&lt;h2 id=&quot;completing-the-transfer&quot;&gt;Completing the transfer&lt;/h2&gt;
&lt;p&gt;When the last beat’s &lt;code&gt;busPhase&lt;/code&gt; reaches four with no beats left,
&lt;code&gt;complete&lt;/code&gt; closes the op — and this is where a &lt;code&gt;GET&lt;/code&gt; finally pays out
everything it recorded as &lt;code&gt;pending&lt;/code&gt; back in &lt;code&gt;Exec&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;complete&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;State&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;complete s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; pending s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  PendGet&lt;/span&gt;&lt;span&gt; rd nbits crc &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; byte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; shifter s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        crc&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; crc &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;then&lt;/span&gt;&lt;span&gt; crc8Update &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rxCrc s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; byte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;else&lt;/span&gt;&lt;span&gt; rxCrc s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        capW &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; captureWord &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pack nbits&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; byte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        (&lt;/span&gt;&lt;span&gt;ptr&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; ovf&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; mw&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; pushWord &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ringPtr s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ovf s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; capW&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          (&lt;/span&gt;&lt;span&gt;advance s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; sck&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; busPhase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; beatIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; pending&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; PendNone&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxCrc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; crc&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; regs&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; writeReg &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;regs s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; rd &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;resize byte&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ringPtr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; ptr&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ovf&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; ovf&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; mw&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;advance s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;sck&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; busPhase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; beatIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; pending&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; PendNone&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second arm is the easy one: a &lt;code&gt;PUT&lt;/code&gt; or &lt;code&gt;TAR&lt;/code&gt; has nothing to hand back,
so completion just idles &lt;code&gt;SCK&lt;/code&gt;, clears the beat scratch, and &lt;code&gt;advance&lt;/code&gt;s to
the next instruction. The first arm is where a &lt;code&gt;GET&lt;/code&gt;’s three recorded
promises — the &lt;code&gt;rd&lt;/code&gt;, the &lt;code&gt;nbits&lt;/code&gt;, the &lt;code&gt;crc&lt;/code&gt; flag it wrote into &lt;code&gt;PendGet&lt;/code&gt;
— come due at once.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;byte = shifter s&lt;/code&gt; is the assembled result. Three things happen to it.
It is written to the register: &lt;code&gt;writeReg (regs s) rd (resize byte)&lt;/code&gt;,
the &lt;code&gt;rd&lt;/code&gt; the instruction named, widened to the register’s width. It is
folded into the CRC residue — but &lt;em&gt;only if&lt;/em&gt; &lt;code&gt;crc&lt;/code&gt;: &lt;code&gt;if crc then crc8Update (rxCrc s) byte else rxCrc s&lt;/code&gt;. That flag is the difference
between &lt;code&gt;GET_BYTE&lt;/code&gt;, which advances the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; so a program can check a
packet’s residue, and &lt;code&gt;GET_BITS&lt;/code&gt;, which is CRC-neutral and leaves the
residue alone. &lt;code&gt;crc8Update&lt;/code&gt; is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC block&lt;/a&gt; we read whole, called
here on the byte just sampled — the residue the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;last post&lt;/a&gt;’s
&lt;code&gt;RDSR&lt;/code&gt; reads is &lt;em&gt;built&lt;/em&gt; right here, one received byte at a time. And it is
recorded into the trace: &lt;code&gt;captureWord&lt;/code&gt; and &lt;code&gt;pushWord&lt;/code&gt;, which are the
record half of the post.&lt;/p&gt;
&lt;p&gt;Notice what completion never does: stall. The register write, the CRC
fold, and the &lt;code&gt;advance&lt;/code&gt; happen unconditionally; only the trace push can
fail, and when it does it drops the record and sets a flag rather than
holding the bus. That is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;introduction&lt;/a&gt;’s load-bearing rule —
&lt;em&gt;never block the bus on trace backpressure&lt;/em&gt; — and it is enforced here,
in the order of these bindings: the bus work is done before the trace is
even attempted, so the trace can never delay it.&lt;/p&gt;
&lt;h2 id=&quot;the-trace-ring&quot;&gt;The trace ring&lt;/h2&gt;
&lt;p&gt;A &lt;code&gt;GET&lt;/code&gt;’s completion writes one record — a &lt;code&gt;CAPTURE&lt;/code&gt; — and the two
functions that build and place it are the whole of how the engine writes
to the trace. First the word:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;captureWord&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;captureWord nbits byte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; bitCoerce &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0b00&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 18&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; nbits&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; byte&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;code&gt;CAPTURE&lt;/code&gt; is thirty-two bits: a two-bit &lt;strong&gt;tag&lt;/strong&gt; &lt;code&gt;00&lt;/code&gt;, eighteen zero bits
of padding, the four-bit &lt;code&gt;nbits&lt;/code&gt; (how many bits this capture holds), and
the eight-bit &lt;code&gt;byte&lt;/code&gt;. &lt;code&gt;bitCoerce&lt;/code&gt; packs that tuple into a single word by
laying its fields end to end — &lt;code&gt;2 + 18 + 4 + 8 = 32&lt;/code&gt;, exactly full. The
tag in the top two bits is how the host, reading the ring back later,
tells one kind of record from another.&lt;/p&gt;
&lt;p&gt;Then placement, which is the ring’s one interesting decision:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;pushWord&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RW&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RW&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;pushWord ptr ov w&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; ov &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ptr&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; ptr &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; termAddr &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ptr &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Ring&lt;/span&gt;&lt;span&gt; ptr w&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ptr&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three cases, and they are the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;trace ring’s&lt;/a&gt; whole contract. If
the overflow flag &lt;code&gt;ov&lt;/code&gt; is already set, the ring has given up: drop the
word, keep the flag high, write nothing. If there is room — &lt;code&gt;ptr &amp;lt;= termAddr - 1&lt;/code&gt;, at or below the last usable slot — write the word at
&lt;code&gt;ptr&lt;/code&gt; and bump the pointer. Otherwise the ring is full: set overflow,
drop the word, and — the important part — write &lt;em&gt;nothing&lt;/em&gt;, so the
pointer never climbs onto &lt;code&gt;termAddr&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That last slot is reserved on purpose. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories post&lt;/a&gt; noticed it
from the other side: the ring is 4096 words, &lt;code&gt;termAddr = maxBound&lt;/code&gt;, and
the top slot is kept empty so a terminator always has somewhere
guaranteed to land. &lt;code&gt;pushWord&lt;/code&gt; is the code that keeps the promise —
records fill from the bottom up to &lt;code&gt;termAddr - 1&lt;/code&gt; and stop, dropping
themselves with a sticky flag rather than ever clobbering the slot the
&lt;code&gt;HALT&lt;/code&gt; word is going to need. &lt;strong&gt;Drop on overflow, never wrap, never stall,
never touch the terminator.&lt;/strong&gt; A compliance transcript wants an honest
ordered prefix and a flag that says “there was more,” not a silently
overwritten tail.&lt;/p&gt;
&lt;h2 id=&quot;mark-a-two-word-record&quot;&gt;MARK: a two-word record&lt;/h2&gt;
&lt;p&gt;Most records are one word and are written in the cycle that produces
them. &lt;code&gt;MARK&lt;/code&gt; is the exception — it is two words, a label and a payload
— and it is the reason the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape post&lt;/a&gt;’s lifecycle had a phase
called &lt;code&gt;TraceEmit&lt;/code&gt; that did nothing but “write a MARK’s second word.” Back
in &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;&lt;code&gt;Exec&lt;/code&gt;&lt;/a&gt;, a &lt;code&gt;MARK&lt;/code&gt; wrote its &lt;em&gt;first&lt;/em&gt; word (the label) and set
&lt;code&gt;pending = PendMark payload&lt;/code&gt;; &lt;code&gt;TraceEmit&lt;/code&gt; is where the second word lands:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;stepTraceEmit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;State&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;stepTraceEmit s _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; pending s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  PendMark&lt;/span&gt;&lt;span&gt; payload &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;advance s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;ringPtr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; ringPtr s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; pending&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; PendNone&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Ring&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ringPtr s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; payload&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;advance s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It spills the &lt;code&gt;payload&lt;/code&gt; the &lt;code&gt;Exec&lt;/code&gt; cycle stashed in &lt;code&gt;pending&lt;/code&gt; to the next
ring slot, bumps the pointer, and advances. The label word it followed was
built by a sibling of &lt;code&gt;captureWord&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;markLabelWord&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 14&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;markLabelWord lbl &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; bitCoerce &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0b10&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 16&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; lbl&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tag &lt;code&gt;10&lt;/code&gt;, sixteen zeros, a fourteen-bit label. Then the payload word,
whole and untagged, in the cycle after. Splitting the write across two
cycles is why the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;&lt;code&gt;Exec&lt;/code&gt;&lt;/a&gt; side checked that &lt;em&gt;two&lt;/em&gt; slots were free
before committing to either — a half-written &lt;code&gt;MARK&lt;/code&gt;, a label with no
payload, would be a corrupt record, so the engine writes both or neither.
The two cycles are one atomic act stretched over a phase boundary, and
&lt;code&gt;TraceEmit&lt;/code&gt; is the second half of it.&lt;/p&gt;
&lt;h2 id=&quot;wait-on-blocking-on-the-alert&quot;&gt;WAIT_ON: blocking on the alert&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;WaitAlert&lt;/code&gt; is the last of the three deferred phases, and the only one
that can spend more than a fixed number of cycles. It is where a &lt;code&gt;WAIT_ON&lt;/code&gt;
sits and watches the alert line:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;stepWaitAlert&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;State&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;stepWaitAlert s inp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; pending s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  PendWait&lt;/span&gt;&lt;span&gt; rd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; asserted &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; done rd &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; waitTimer s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; done rd &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;waitTimer&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; waitTimer s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;advance s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    if&lt;/span&gt;&lt;span&gt; cfgAlertSource &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cfg s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; AlertPin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      then&lt;/span&gt;&lt;span&gt; alertIn inp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      else&lt;/span&gt;&lt;span&gt; ioIn inp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;!!&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  asserted &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; ALERT#/IO[1] are both active low (§6.5)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  done rd v &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;advance s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;regs&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; writeReg &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;regs s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; rd v&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; pending&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; PendNone&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each cycle in &lt;code&gt;WaitAlert&lt;/code&gt; asks one question in priority order. Is the
alert &lt;strong&gt;asserted&lt;/strong&gt;? The alert is active-low — &lt;code&gt;b == 0&lt;/code&gt; — and &lt;code&gt;b&lt;/code&gt; is
chosen the same way &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;&lt;code&gt;GET_ALERT&lt;/code&gt;&lt;/a&gt; chose it, the configured pin or
in-band &lt;code&gt;IO[1]&lt;/code&gt;. If asserted, the wait is satisfied: &lt;code&gt;done rd 1&lt;/code&gt; writes a
one into the result register and moves on. Otherwise, has the timer run
out? &lt;code&gt;waitTimer == 0&lt;/code&gt; means it has, and &lt;code&gt;done rd 0&lt;/code&gt; falls through with a
zero — timed out, no alert. Otherwise, decrement &lt;code&gt;waitTimer&lt;/code&gt; and wait
another cycle. The register ends up holding a one if the alert came and a
zero if the clock beat it, so the program can branch on which happened.
&lt;code&gt;WAIT_ON&lt;/code&gt; armed the timer in &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;&lt;code&gt;Exec&lt;/code&gt;&lt;/a&gt;; &lt;code&gt;WaitAlert&lt;/code&gt; counts it down.&lt;/p&gt;
&lt;h2 id=&quot;halt-and-the-record-a-run-ends-on&quot;&gt;HALT, and the record a run ends on&lt;/h2&gt;
&lt;p&gt;Every run ends by writing one last record, and every trap in the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;last
post&lt;/a&gt; — the decode error, the config trap, the reserved status
register — ended by calling this:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;haltWith&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 3&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;State&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;haltWith trap reason status s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;phase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Halted&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      w &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; bitCoerce &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0b11&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 17&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; reason&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; trap&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ovf s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; status&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;   in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Ring&lt;/span&gt;&lt;span&gt; termAddr w&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;haltWith&lt;/code&gt; builds the &lt;strong&gt;terminator&lt;/strong&gt; and drops the machine into &lt;code&gt;Halted&lt;/code&gt;.
The word is the third record shape: tag &lt;code&gt;11&lt;/code&gt;, seventeen zeros, a three-bit
&lt;code&gt;reason&lt;/code&gt;, a one-bit &lt;code&gt;trap&lt;/code&gt; flag, the sticky &lt;code&gt;ovf&lt;/code&gt; bit, and an eight-bit
&lt;code&gt;status&lt;/code&gt;. A plain &lt;code&gt;HALT&lt;/code&gt; calls it with &lt;code&gt;trap = False, reason = 0&lt;/code&gt; and the
program’s own status byte; a trap calls it with &lt;code&gt;trap = True&lt;/code&gt;, one of the
five reasons, and — crucially — on a state that has been run through
&lt;code&gt;safePins&lt;/code&gt; first:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;safePins&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;safePins s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;csN&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; sck&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rstN&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lanes&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; hiZ&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;CS#&lt;/code&gt; high, &lt;code&gt;SCK&lt;/code&gt; low, &lt;code&gt;RESET#&lt;/code&gt; released, lanes &lt;code&gt;hiZ&lt;/code&gt; — the bus put back
to idle before the machine stops, so a program that dies mid-frame leaves
nothing driving. And the terminator goes to &lt;code&gt;Ring termAddr&lt;/code&gt; — the
reserved top slot &lt;code&gt;pushWord&lt;/code&gt; guarded so carefully. That guarding pays off
exactly here: no matter how many records overflowed, the slot the &lt;code&gt;HALT&lt;/code&gt;
word needs is free, so a run &lt;em&gt;always&lt;/em&gt; ends with a readable terminator that
carries its overflow bit along. A truncated trace still says, in its last
word, that it was truncated.&lt;/p&gt;
&lt;p&gt;That is the fourth and last record shape, and it completes a small
taxonomy. Every word the ring ever holds is one of four:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;REVISION&lt;/code&gt;&lt;/strong&gt; at slot zero — &lt;code&gt;[major | minor | patch]&lt;/code&gt;, the version
stamp the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape post&lt;/a&gt;’s &lt;code&gt;Preamble&lt;/code&gt; wrote on the first cycle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;CAPTURE&lt;/code&gt;&lt;/strong&gt;, tag &lt;code&gt;00&lt;/code&gt; — one word, a sampled byte and its bit count.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;MARK&lt;/code&gt;&lt;/strong&gt;, tag &lt;code&gt;10&lt;/code&gt; — two words, a label then a payload.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;HALT&lt;/code&gt;&lt;/strong&gt;, tag &lt;code&gt;11&lt;/code&gt; — the terminator, with &lt;code&gt;trap&lt;/code&gt;, &lt;code&gt;reason&lt;/code&gt;, &lt;code&gt;ovf&lt;/code&gt;,
and &lt;code&gt;status&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And there is a quiet honesty in how they are built. Every one of these
words is assembled inline with &lt;code&gt;bitCoerce&lt;/code&gt;, right here in the engine,
because &lt;code&gt;step&lt;/code&gt; has to be synthesizable — it emits at most one word a
cycle, and it cannot call the list-returning encoder that would be the
natural way to describe a record. So the &lt;em&gt;layouts&lt;/em&gt; live twice: once here,
as &lt;code&gt;bitCoerce&lt;/code&gt; tuples in &lt;code&gt;captureWord&lt;/code&gt;, &lt;code&gt;markLabelWord&lt;/code&gt;, and &lt;code&gt;haltWith&lt;/code&gt;,
and once in &lt;code&gt;Tamal.Trace&lt;/code&gt;, as an &lt;code&gt;encodeRecord&lt;/code&gt; that the tests use as the
reference model. Every comment in this file that says &lt;em&gt;mirrors
Trace.encodeRecord&lt;/em&gt; is a promise that the two agree — the engine’s fast
inline form and the trace module’s readable canonical form, checked
against each other so the bytes the host reads back are the bytes the
format says. &lt;code&gt;Tamal.Trace&lt;/code&gt; never runs on the fabric; it exists so the
engine’s inline packing has something to be proven equal to.&lt;/p&gt;
&lt;h2 id=&quot;what-we-read&quot;&gt;What we read&lt;/h2&gt;
&lt;p&gt;The wire and the record, both. On the wire: &lt;code&gt;SCK&lt;/code&gt; is the fabric clock over
five, low three, high two, and every edge that matters is the rising one
at the two-to-three boundary. &lt;code&gt;stepBusBeat&lt;/code&gt; is a three-way guard that
ticks a beat, starts the next, or completes; &lt;code&gt;beatLanes&lt;/code&gt; reads one
&lt;code&gt;pending&lt;/code&gt; field and gets all three eSPI directions — release for a
&lt;code&gt;GET&lt;/code&gt;, turn around for a &lt;code&gt;TAR&lt;/code&gt;, serialise for a &lt;code&gt;PUT&lt;/code&gt; — out of the
still-shut serialiser; &lt;code&gt;sampleGet&lt;/code&gt; shifts the response in MSB-first on the
rising edge; and &lt;code&gt;complete&lt;/code&gt; writes the register, folds the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt;
residue that &lt;code&gt;RDSR&lt;/code&gt; will read, and pushes the capture — bus work first,
so the trace can never stall it.&lt;/p&gt;
&lt;p&gt;And the record: a ring that fills from the bottom, drops on overflow with
a sticky flag, and never touches the terminator slot the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt;
kept empty; a &lt;code&gt;MARK&lt;/code&gt; whose two words are written across a phase boundary
or not at all; a &lt;code&gt;WAIT_ON&lt;/code&gt; that counts down against an active-low alert;
and a &lt;code&gt;HALT&lt;/code&gt; that ends every run with a status, a reason, and, on a trap,
a bus set safe — four record shapes, built fast inline but pinned to
&lt;code&gt;Tamal.Trace&lt;/code&gt;’s readable encoder so the two can be checked equal.&lt;/p&gt;
&lt;p&gt;With that, the engine is whole &lt;em&gt;as a map.&lt;/em&gt; The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape&lt;/a&gt; drew it, the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-exec/&quot;&gt;dispatch&lt;/a&gt; chose within it, and this post ran its wire and wrote its
trace. Across three posts we have used — and never opened — every leaf
it composes: &lt;code&gt;decode&lt;/code&gt;, &lt;code&gt;dataResult&lt;/code&gt;, &lt;code&gt;branchTaken&lt;/code&gt;, &lt;code&gt;readReg&lt;/code&gt; and
&lt;code&gt;writeReg&lt;/code&gt;, &lt;code&gt;decodeConfig&lt;/code&gt;, &lt;code&gt;serializeX1&lt;/code&gt;, and &lt;code&gt;tarBeat&lt;/code&gt;. That was the
whole wager of reading &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;outside in&lt;/a&gt;: hold the pieces as one-line
promises long enough to understand the machine that spends them, and only
then open them.&lt;/p&gt;
&lt;p&gt;Now we open them, in the order the engine first reaches for each. And the
engine’s very first act on any word — before compute, before the bus,
before anything — was &lt;code&gt;decode&lt;/code&gt;. So that is where we go next: down into
&lt;code&gt;Tamal.Isa&lt;/code&gt;, the instruction set itself, the box every road out of &lt;code&gt;Exec&lt;/code&gt;
began by opening.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-sckof&quot;&gt;
&lt;p&gt;The point-free spelling &lt;code&gt;boolToBit &amp;lt;$&amp;gt; (&amp;gt;= 3)&lt;/code&gt; reads oddly the
first time. &lt;code&gt;(&amp;gt;= 3)&lt;/code&gt; is a function &lt;code&gt;Index 5 -&amp;gt; Bool&lt;/code&gt;; &lt;code&gt;&amp;lt;$&amp;gt;&lt;/code&gt; over the
function “functor” is ordinary composition, so &lt;code&gt;boolToBit &amp;lt;$&amp;gt; (&amp;gt;= 3)&lt;/code&gt; is
&lt;code&gt;\p -&amp;gt; boolToBit (p &amp;gt;= 3)&lt;/code&gt; — take a phase, test whether it is three or
four, turn the answer into a &lt;code&gt;Bit&lt;/code&gt;. It is the same &lt;code&gt;fmap&lt;/code&gt;-is-composition
trick the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt; noted, used to write a one-liner that would otherwise
need a lambda. Clash lowers it to a comparator and nothing else. &lt;a href=&quot;#fr-sckof-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Tamal: One Instruction, One Cycle</title>
    <published>2026-08-03T09:00:00+00:00</published>
    <updated>2026-08-03T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-engine-exec/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-engine-exec/</id>
    <summary type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;last post&lt;/a&gt; drew the map and left every road on it unwalked.
We read the engine’s shape — the pure &lt;code&gt;step&lt;/code&gt;, the eight phases, the
seventeen-field state it threads — and then stopped at the one phase
where the work actually happens: &lt;code&gt;Exec&lt;/code&gt;, with the instruction word from
memory finally valid in hand, decoded by nothing yet. This post walks in.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Exec&lt;/code&gt; is the hub. Every instruction the engine runs passes through it
exactly once, and what happens in that one cycle is the whole of the
machine’s behaviour above the wire. Most instructions &lt;em&gt;finish&lt;/em&gt; here —
compute a value, take a branch, poke a pin — and hand the machine back
to &lt;code&gt;Fetch&lt;/code&gt; for the next word. A few use their one cycle in &lt;code&gt;Exec&lt;/code&gt; not to
finish but to &lt;em&gt;arm themselves&lt;/em&gt; and hand off to a longer phase. Either
way, the cycle is the same shape: decode the word, and dispatch on what
it turned out to be.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;last post&lt;/a&gt; drew the map and left every road on it unwalked.
We read the engine’s shape — the pure &lt;code&gt;step&lt;/code&gt;, the eight phases, the
seventeen-field state it threads — and then stopped at the one phase
where the work actually happens: &lt;code&gt;Exec&lt;/code&gt;, with the instruction word from
memory finally valid in hand, decoded by nothing yet. This post walks in.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Exec&lt;/code&gt; is the hub. Every instruction the engine runs passes through it
exactly once, and what happens in that one cycle is the whole of the
machine’s behaviour above the wire. Most instructions &lt;em&gt;finish&lt;/em&gt; here —
compute a value, take a branch, poke a pin — and hand the machine back
to &lt;code&gt;Fetch&lt;/code&gt; for the next word. A few use their one cycle in &lt;code&gt;Exec&lt;/code&gt; not to
finish but to &lt;em&gt;arm themselves&lt;/em&gt; and hand off to a longer phase. Either
way, the cycle is the same shape: decode the word, and dispatch on what
it turned out to be.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;decode-then-dispatch&quot;&gt;Decode, then dispatch&lt;/h2&gt;
&lt;p&gt;The phase helper &lt;code&gt;step&lt;/code&gt; hands the cycle to is &lt;code&gt;stepExec&lt;/code&gt;, and it is two
lines:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;stepExec&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;State&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;stepExec s inp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; decode &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;instrWord inp&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Left&lt;/span&gt;&lt;span&gt; _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; haltWith &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;safePins s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; decode error -&amp;gt; reason 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Right&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; execInstr i s inp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everything the engine does with a word begins with &lt;code&gt;decode (instrWord inp)&lt;/code&gt;. &lt;code&gt;decode&lt;/code&gt; is the first of the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shut boxes&lt;/a&gt; we reach for
— it lives in &lt;code&gt;Tamal.Isa&lt;/code&gt;, it turns the raw 32 bits the memory
handed back into a typed &lt;code&gt;Instr&lt;/code&gt;, and it is the whole subject of a
later post. Here we treat it exactly as its type asks us to: it
returns an &lt;code&gt;Either&lt;/code&gt; — a cousin of Rust’s &lt;code&gt;Result&amp;lt;T,E&amp;gt;&lt;/code&gt; —, and the
two sides are the fork this function is built on.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Left&lt;/code&gt; is a &lt;strong&gt;decode error&lt;/strong&gt;: the word was not a legal instruction —
a reserved opcode, a bad field, a bit pattern the ISA refuses. There is
no “ignore it and move on”; an engine that silently skips a word it
cannot read is an engine that turns a corrupted program into undefined
behaviour. So a decode error &lt;em&gt;traps&lt;/em&gt;: &lt;code&gt;haltWith True 1 0 (safePins s)&lt;/code&gt;.
We will read &lt;code&gt;haltWith&lt;/code&gt; in full in the next post — it writes the
terminator record that ends a trace — but its arguments already tell
the story. &lt;code&gt;True&lt;/code&gt; is the trap flag; &lt;code&gt;1&lt;/code&gt; is the &lt;strong&gt;reason&lt;/strong&gt;, and it is the
first of five the engine can give&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-reasons-1&quot;&gt;&lt;a href=&quot;#fn-reasons&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;; &lt;code&gt;safePins s&lt;/code&gt; drives &lt;code&gt;CS#&lt;/code&gt;,
&lt;code&gt;RESET#&lt;/code&gt;, &lt;code&gt;SCK&lt;/code&gt;, and the lanes back to their safe idle before the machine
stops, so a program that dies mid-transaction does not leave the bus
wedged. A trap is a &lt;code&gt;HALT&lt;/code&gt; with a cause and clean pins.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Right i&lt;/code&gt; is the good path: a decoded &lt;code&gt;Instr&lt;/code&gt;, handed to &lt;code&gt;execInstr&lt;/code&gt;
along with the state and this cycle’s inputs. That function is the rest
of the post.&lt;/p&gt;
&lt;h2 id=&quot;a-case-as-wide-as-the-isa&quot;&gt;A case as wide as the ISA&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;execInstr&lt;/code&gt; dispatches on the decoded instruction, and its &lt;code&gt;case&lt;/code&gt; is the
widest in the series — one arm per instruction the ISA defines. Here is
its shape, the DATA block collapsed to save your scrolling:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;execInstr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Instr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;State&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;execInstr i s inp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  LoadImm&lt;/span&gt;&lt;span&gt; rd _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; dataWb rd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Lui&lt;/span&gt;&lt;span&gt; rd _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; dataWb rd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Mov&lt;/span&gt;&lt;span&gt; rd _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; dataWb rd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Add&lt;/span&gt;&lt;span&gt; rd _ _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; dataWb rd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; … Addi, Sub, And_, Andi, Or_, Ori, Xor_, Xori, Shift: all dataWb rd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Halt&lt;/span&gt;&lt;span&gt; st &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; haltWith &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt; st s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Rdsr&lt;/span&gt;&lt;span&gt; rd srn &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; {-&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; status read or trap &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;-}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Beq&lt;/span&gt;&lt;span&gt; a b off &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; branch &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Br.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Beq&lt;/span&gt;&lt;span&gt; a b off&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; … Bne, Bltu, Bgeu&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  CsAssert&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; pinOp &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;csN&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; … CsDeassert, RstAssert, RstDeassert, CrcReset, SetConfig, GetAlert&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  PutByteImm&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; startPut b &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; … the other PUT/GET forms, TarImm, TarReg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Mark&lt;/span&gt;&lt;span&gt; lbl a &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; {-&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; arm TraceEmit &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;-}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  WaitOn&lt;/span&gt;&lt;span&gt; rd _ timeout &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; {-&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; arm WaitAlert &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;-}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The width is real — there are more than thirty constructors — but the
&lt;em&gt;handlers&lt;/em&gt; are few, and that gap is the first thing worth seeing. A
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;total case&lt;/a&gt; over &lt;code&gt;Instr&lt;/code&gt; means every instruction the ISA can
produce has a defined action here, checked by the compiler: leave one out
and Clash refuses to build. But thirteen of those arms say the same three
words, &lt;code&gt;dataWb rd&lt;/code&gt;, and four of them say &lt;code&gt;branch&lt;/code&gt;, and a cluster of them
say &lt;code&gt;pinOp&lt;/code&gt;. The &lt;code&gt;case&lt;/code&gt; is wide because the instruction set is wide; the
behaviour behind it collapses into a handful of shapes. Read the shapes,
and you have read the dispatch.&lt;/p&gt;
&lt;p&gt;There are exactly three destinations, and they are the three exits from
&lt;code&gt;Exec&lt;/code&gt; the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape post&lt;/a&gt;’s lifecycle diagram drew without opening:&lt;/p&gt;
&lt;figure class=&quot;exdisp-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;exdisp&quot; viewBox=&quot;0 0 760 372&quot; role=&quot;img&quot; aria-labelledby=&quot;exdisp-t exdisp-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;exdisp-t&quot;&gt;How Exec dispatches a decoded instruction to three destinations&lt;/title&gt;
&lt;desc id=&quot;exdisp-d&quot;&gt;The instruction word enters a decode box on the left. Decode either fails, which traps, or yields an Instr that execInstr dispatches to one of three destinations. The top destination, drawn in the accent colour, is single-cycle and returns to Fetch: the thirteen DATA instructions, RDSR, the four branches, and the CS, RST, CRC_RESET, SET_CONFIG and GET_ALERT pokes. The middle destination is multi-cycle and hands off to the BusBeat, TraceEmit and WaitAlert phases opened in the next post: PUT, GET, TAR, MARK and WAIT_ON. The bottom destination stops the machine at Halted: HALT, a decode error, and the config or rdsr traps.&lt;/desc&gt;
&lt;style&gt;
.exdisp{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.exdisp .box{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.exdisp .boxA{fill:var(--bg-dim);stroke:var(--accent);stroke-width:2.5}
.exdisp .wire{stroke:var(--fg-main);stroke-width:2;fill:none}
.exdisp .accw{stroke:var(--accent);stroke-width:2.5;fill:none}
.exdisp .kw{fill:var(--fg-main);font-family:var(--mono);font-size:14px}
.exdisp .ti{fill:var(--fg-main);font-family:var(--sans);font-size:13px}
.exdisp .tiA{fill:var(--accent);font-family:var(--sans);font-size:13px}
.exdisp .mem{fill:var(--fg-dim);font-family:var(--mono);font-size:10.5px}
.exdisp .sig{fill:var(--fg-dim);font-family:var(--mono);font-size:11.5px}
.exdisp .ah{fill:var(--fg-main)}
.exdisp .aha{fill:var(--accent)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;ex-a&quot; markerWidth=&quot;9&quot; markerHeight=&quot;7&quot; refX=&quot;7&quot; refY=&quot;3.5&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L7,3.5 L0,7 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;ex-aa&quot; markerWidth=&quot;9&quot; markerHeight=&quot;7&quot; refX=&quot;7&quot; refY=&quot;3.5&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L7,3.5 L0,7 Z&quot; class=&quot;aha&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;!-- input + decode --&gt;
&lt;text class=&quot;sig&quot; x=&quot;16&quot; y=&quot;192&quot; text-anchor=&quot;start&quot;&gt;instrWord&lt;/text&gt;
&lt;line class=&quot;wire&quot; x1=&quot;86&quot; y1=&quot;188&quot; x2=&quot;98&quot; y2=&quot;188&quot; marker-end=&quot;url(#ex-a)&quot;/&gt;
&lt;rect class=&quot;boxA&quot; x=&quot;100&quot; y=&quot;166&quot; width=&quot;92&quot; height=&quot;44&quot; rx=&quot;6&quot;/&gt;
&lt;text class=&quot;kw&quot; x=&quot;146&quot; y=&quot;192&quot; text-anchor=&quot;middle&quot;&gt;decode&lt;/text&gt;
&lt;!-- fan arrows --&gt;
&lt;path class=&quot;accw&quot; d=&quot;M192,180 C280,150 330,96 424,86&quot; marker-end=&quot;url(#ex-aa)&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M192,192 C300,192 320,192 424,192&quot; marker-end=&quot;url(#ex-a)&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M192,200 C280,230 330,290 424,300&quot; marker-end=&quot;url(#ex-a)&quot;/&gt;
&lt;!-- top: single-cycle -&gt; Fetch --&gt;
&lt;rect class=&quot;boxA&quot; x=&quot;426&quot; y=&quot;52&quot; width=&quot;318&quot; height=&quot;68&quot; rx=&quot;8&quot;/&gt;
&lt;text class=&quot;tiA&quot; x=&quot;440&quot; y=&quot;74&quot; text-anchor=&quot;start&quot;&gt;single-cycle → Fetch&lt;/text&gt;
&lt;text class=&quot;mem&quot; x=&quot;440&quot; y=&quot;94&quot; text-anchor=&quot;start&quot;&gt;13× DATA · RDSR · BEQ BNE BLTU BGEU&lt;/text&gt;
&lt;text class=&quot;mem&quot; x=&quot;440&quot; y=&quot;110&quot; text-anchor=&quot;start&quot;&gt;CS/RST · CRC_RESET · SET_CONFIG · GET_ALERT&lt;/text&gt;
&lt;!-- mid: multi-cycle -&gt; part 3 --&gt;
&lt;rect class=&quot;box&quot; x=&quot;426&quot; y=&quot;160&quot; width=&quot;318&quot; height=&quot;60&quot; rx=&quot;8&quot;/&gt;
&lt;text class=&quot;ti&quot; x=&quot;440&quot; y=&quot;182&quot; text-anchor=&quot;start&quot;&gt;multi-cycle → BusBeat / TraceEmit / WaitAlert&lt;/text&gt;
&lt;text class=&quot;mem&quot; x=&quot;440&quot; y=&quot;202&quot; text-anchor=&quot;start&quot;&gt;PUT/GET/TAR · MARK · WAIT_ON&lt;/text&gt;
&lt;!-- bot: stop -&gt; Halted --&gt;
&lt;rect class=&quot;box&quot; x=&quot;426&quot; y=&quot;260&quot; width=&quot;318&quot; height=&quot;56&quot; rx=&quot;8&quot;/&gt;
&lt;text class=&quot;ti&quot; x=&quot;440&quot; y=&quot;282&quot; text-anchor=&quot;start&quot;&gt;stop → Halted&lt;/text&gt;
&lt;text class=&quot;mem&quot; x=&quot;440&quot; y=&quot;302&quot; text-anchor=&quot;start&quot;&gt;HALT · decode error · config / rdsr trap&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;One &lt;code&gt;Exec&lt;/code&gt; cycle, zoomed in from the &lt;a href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape post&lt;/a&gt;&#39;s lifecycle. &lt;code&gt;decode&lt;/code&gt; turns the fetched word into an &lt;code&gt;Instr&lt;/code&gt; (or fails, and traps). &lt;code&gt;execInstr&lt;/code&gt; sends it to one of three destinations: the accent path is the single-cycle majority that computes or jumps or pokes and returns to &lt;code&gt;Fetch&lt;/code&gt;; the middle path arms a multi-cycle transfer and hands off to the phases the next post opens; the bottom path stops the machine. The &lt;code&gt;case&lt;/code&gt; is wide, but every arm lands in one of these three.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We walk the accent path first, because it is where nearly every arm ends
up.&lt;/p&gt;
&lt;h2 id=&quot;thirteen-instructions-one-handler&quot;&gt;Thirteen instructions, one handler&lt;/h2&gt;
&lt;p&gt;The DATA group is the engine’s compute: load an immediate, move a
register, add, subtract, the three bitwise operations and their immediate
forms, the shift. Thirteen instructions — and all thirteen dispatch to
the same three words:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  LoadImm&lt;/span&gt;&lt;span&gt; rd _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; dataWb rd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Lui&lt;/span&gt;&lt;span&gt; rd _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; dataWb rd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Mov&lt;/span&gt;&lt;span&gt; rd _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; dataWb rd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Add&lt;/span&gt;&lt;span&gt; rd _ _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; dataWb rd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Addi&lt;/span&gt;&lt;span&gt; rd _ _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; dataWb rd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Sub&lt;/span&gt;&lt;span&gt; rd _ _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; dataWb rd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  And_&lt;/span&gt;&lt;span&gt; rd _ _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; dataWb rd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Andi&lt;/span&gt;&lt;span&gt; rd _ _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; dataWb rd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Or_&lt;/span&gt;&lt;span&gt; rd _ _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; dataWb rd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Ori&lt;/span&gt;&lt;span&gt; rd _ _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; dataWb rd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Xor_&lt;/span&gt;&lt;span&gt; rd _ _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; dataWb rd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Xori&lt;/span&gt;&lt;span&gt; rd _ _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; dataWb rd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Shift&lt;/span&gt;&lt;span&gt; rd _ _ _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; dataWb rd&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every arm names its destination register &lt;code&gt;rd&lt;/code&gt; and throws the rest of its
fields away with underscores, because the fields do not matter &lt;em&gt;here&lt;/em&gt; —
they matter to the thing that computes the value. The arm’s whole job is
to say “this instruction writes a register, and the register is &lt;code&gt;rd&lt;/code&gt;.”
The computing is &lt;code&gt;dataWb&lt;/code&gt;, one of the helpers in the &lt;code&gt;where&lt;/code&gt; block:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  rs1v &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; readReg &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;regs s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;operandRs1 i&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  rs2v &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; readReg &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;regs s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;operandRs2 i&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  dataWb rd &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;advance s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;regs&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; writeReg &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;regs s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; rd &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dataResult i rs1v rs2v&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read &lt;code&gt;dataWb&lt;/code&gt; inside out. &lt;code&gt;dataResult i rs1v rs2v&lt;/code&gt; is the value —
computed by &lt;code&gt;dataResult&lt;/code&gt;, another &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shut box&lt;/a&gt;, the ALU that a later
post opens. Notice &lt;em&gt;how&lt;/em&gt; it is called: it is handed the whole instruction
&lt;code&gt;i&lt;/code&gt; and two operand values, and it works out the rest — which operation,
which immediate — from &lt;code&gt;i&lt;/code&gt; itself. That is why all thirteen arms could
collapse into one handler: they do not each need to pick out their own
operation, because &lt;code&gt;dataResult&lt;/code&gt; reads the operation off the instruction.
The arm supplies &lt;code&gt;rd&lt;/code&gt;; the ALU supplies everything else.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;writeReg (regs s) rd (…)&lt;/code&gt; stores that value into the register file —
&lt;code&gt;writeReg&lt;/code&gt; belongs to the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;register file&lt;/a&gt;, a box for a later post, and
it quietly enforces one rule we will lean on: a write to &lt;code&gt;x0&lt;/code&gt; is
discarded, because &lt;code&gt;x0&lt;/code&gt; is hardwired zero. &lt;code&gt;advance s&lt;/code&gt; sets the phase back
to &lt;code&gt;Fetch&lt;/code&gt; and steps the program counter to &lt;code&gt;pc + 1&lt;/code&gt;. And the output is
&lt;code&gt;(s&#39;, busOut s&#39;, Nothing)&lt;/code&gt; — next state, its projected pins, and
&lt;code&gt;Nothing&lt;/code&gt;, because a register write leaves no trace record. One cycle,
one register, back to &lt;code&gt;Fetch&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;which-registers-though&quot;&gt;Which registers, though&lt;/h2&gt;
&lt;p&gt;Two lines in that &lt;code&gt;where&lt;/code&gt; block did the reading: &lt;code&gt;rs1v&lt;/code&gt; and &lt;code&gt;rs2v&lt;/code&gt;, the
values of the two source registers, each &lt;code&gt;readReg (regs s)&lt;/code&gt; applied to a
register &lt;em&gt;number&lt;/em&gt; that comes from &lt;code&gt;operandRs1 i&lt;/code&gt; and &lt;code&gt;operandRs2 i&lt;/code&gt;. Those
two selectors are small tables, and they are pure engine logic — not a
leaf, nothing shut, so we read them here:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;operandRs1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Instr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;operandRs1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; \&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;case&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Mov&lt;/span&gt;&lt;span&gt; _ a &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Add&lt;/span&gt;&lt;span&gt; _ a _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; … every instruction with a first source register returns it …&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Beq&lt;/span&gt;&lt;span&gt; a _ _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  PutByteReg&lt;/span&gt;&lt;span&gt; a &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Mark&lt;/span&gt;&lt;span&gt; _ a &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;operandRs2&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Instr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;operandRs2 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; \&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;case&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Add&lt;/span&gt;&lt;span&gt; _ _ b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Sub&lt;/span&gt;&lt;span&gt; _ _ b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; … only the instructions with a second source register …&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Bgeu&lt;/span&gt;&lt;span&gt; _ b _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each is a &lt;code&gt;\case&lt;/code&gt; — a bare &lt;code&gt;case&lt;/code&gt; on the function’s one argument — that
picks the relevant field out of the constructors that have one, and falls
through to &lt;code&gt;0&lt;/code&gt; for everything else. &lt;code&gt;operandRs1&lt;/code&gt; returns the first source
register of any instruction that has one; &lt;code&gt;operandRs2&lt;/code&gt; the second, which
far fewer instructions have. The catch-all &lt;code&gt;_ -&amp;gt; 0&lt;/code&gt; is doing real work:
&lt;code&gt;0&lt;/code&gt; is &lt;code&gt;x0&lt;/code&gt;, the register hardwired to zero, so an instruction with no
second operand “reads &lt;code&gt;x0&lt;/code&gt;” and gets zero — a read that is always safe,
always defined, and costs nothing, because &lt;code&gt;x0&lt;/code&gt; is not a register you can
get wrong. An immediate instruction like &lt;code&gt;Addi&lt;/code&gt; names one source and lets
the other resolve to &lt;code&gt;x0&lt;/code&gt;; &lt;code&gt;LoadImm&lt;/code&gt; names neither and reads &lt;code&gt;x0&lt;/code&gt; twice.
The value it does not use is a harmless zero, and the ALU ignores it.&lt;/p&gt;
&lt;p&gt;This is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;RISC-V flavour&lt;/a&gt; the engine borrows showing through:
sources named &lt;code&gt;rs1&lt;/code&gt; and &lt;code&gt;rs2&lt;/code&gt;, a zero register that makes “no operand”
and “the zero operand” the same thing, so the datapath never needs a
special case for an absent argument.&lt;/p&gt;
&lt;h2 id=&quot;the-one-status-the-engine-answers&quot;&gt;The one status the engine answers&lt;/h2&gt;
&lt;p&gt;Most of what a program might ask about lives in the host, not the engine
— but there is exactly one status read the engine answers itself:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Rdsr&lt;/span&gt;&lt;span&gt; rd srn&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; srn &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        let&lt;/span&gt;&lt;span&gt; s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;advance s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;regs&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; writeReg &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;regs s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; rd &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;zeroExtend &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rxCrc s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;         in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; haltWith &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 3&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;safePins s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; reserved sr# -&amp;gt; reason 3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;RDSR&lt;/code&gt; — read status register — names a status register number &lt;code&gt;srn&lt;/code&gt;,
and the engine defines exactly one: number &lt;code&gt;0&lt;/code&gt;, the running receive
CRC-8 residue. When &lt;code&gt;srn == 0&lt;/code&gt; the engine writes &lt;code&gt;zeroExtend (rxCrc s)&lt;/code&gt;
into &lt;code&gt;rd&lt;/code&gt; — the eight-bit residue, widened to the register’s
thirty-two — and advances. Any other &lt;code&gt;srn&lt;/code&gt; is a register the engine
does not have, and reading it traps with reason &lt;code&gt;3&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That residue is not new; it is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC block&lt;/a&gt; cashed in. &lt;code&gt;rxCrc&lt;/code&gt; is
the accumulator that post built, folded over every byte the engine reads
off the bus, and the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;introduction&lt;/a&gt;’s &lt;code&gt;peripheral_io_read.s&lt;/code&gt; ended
its response phase by reading it — &lt;code&gt;rdsr t2, CRC&lt;/code&gt; — and branching on
whether it had driven to zero. This arm is the other end of that program
line: the one place the engine reaches into its own CRC state and hands
it to a register a program can test. The residue law the CRC post proved
by algebra is the check a real eSPI program runs, and &lt;code&gt;RDSR sr0&lt;/code&gt; is how
it runs it.&lt;/p&gt;
&lt;h2 id=&quot;branches-and-arithmetic-the-engine-owns&quot;&gt;Branches, and arithmetic the engine owns&lt;/h2&gt;
&lt;p&gt;Four arms are conditional branches, and all four defer to one helper:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Beq&lt;/span&gt;&lt;span&gt; a b off &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; branch &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Br.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Beq&lt;/span&gt;&lt;span&gt; a b off&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Bne&lt;/span&gt;&lt;span&gt; a b off &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; branch &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Br.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Bne&lt;/span&gt;&lt;span&gt; a b off&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Bltu&lt;/span&gt;&lt;span&gt; a b off &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; branch &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Br.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Bltu&lt;/span&gt;&lt;span&gt; a b off&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Bgeu&lt;/span&gt;&lt;span&gt; a b off &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; branch &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Br.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Bgeu&lt;/span&gt;&lt;span&gt; a b off&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;branch&lt;/code&gt; is where the engine does arithmetic that is genuinely its own
— not delegated to a leaf, but computed right here, because it is about
the program counter and nothing else knows the program counter:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  branch op a b off &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; taken &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Br.&lt;/span&gt;&lt;span&gt;branchTaken op &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;readReg &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;regs s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;readReg &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;regs s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;        --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; offset is 11-bit signed; PC is AW=10-bit. Take the low AW bits;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;        --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; pc + off ≡ pc + (off mod 2^AW) (mod 2^AW)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        offAw &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; unpack &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;truncateB off&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; AW&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        s&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          |&lt;/span&gt;&lt;span&gt; taken &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;phase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Fetch&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; pc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; pc s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span&gt; offAw&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; advance s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The condition, &lt;code&gt;taken&lt;/code&gt;, is &lt;code&gt;Br.branchTaken op …&lt;/code&gt; — the comparator from
&lt;code&gt;Tamal.Branch&lt;/code&gt;, imported qualified as &lt;code&gt;Br&lt;/code&gt;, a &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shut box&lt;/a&gt; for the
compute post. It takes the comparison (&lt;code&gt;Beq&lt;/code&gt;, &lt;code&gt;Bne&lt;/code&gt;, and the unsigned
&lt;code&gt;Bltu&lt;/code&gt;/&lt;code&gt;Bgeu&lt;/code&gt;) and the two register values and answers yes or no. Give
that to the engine and the rest is the program counter.&lt;/p&gt;
&lt;p&gt;The offset arithmetic repays a close look, because it is a small, exact
piece of hardware reasoning. The branch offset &lt;code&gt;off&lt;/code&gt; is an eleven-bit
&lt;em&gt;signed&lt;/em&gt; number — it can reach forwards or backwards. The program
counter is ten bits (&lt;code&gt;AW&lt;/code&gt;), addressing the thousand-and-twenty-four-word
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;instruction store&lt;/a&gt;. So the offset is one bit wider than the counter
it adjusts, and the code reconciles them in one move: &lt;code&gt;truncateB off&lt;/code&gt;
keeps the low ten bits, &lt;code&gt;unpack … :: Unsigned AW&lt;/code&gt; reads them as an
&lt;em&gt;unsigned&lt;/em&gt; ten-bit number, and &lt;code&gt;pc s + offAw&lt;/code&gt; adds. That looks like it
throws the sign away — and it does, and it is still correct, because the
addition is modular. A backwards jump of four is the signed offset &lt;code&gt;-4&lt;/code&gt;,
which in eleven bits is &lt;code&gt;0x7FC&lt;/code&gt;; its low ten bits are &lt;code&gt;0x3FC&lt;/code&gt;, which is
&lt;code&gt;1020&lt;/code&gt; unsigned; and &lt;code&gt;pc + 1020&lt;/code&gt; modulo &lt;code&gt;1024&lt;/code&gt; is &lt;code&gt;pc - 4&lt;/code&gt;. The
two’s-complement wraparound that makes signed and unsigned addition the
same operation — the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC post&lt;/a&gt;’s “add and subtract are one thing”
has a cousin here — means the engine needs no signed adder and no
special case for a backwards branch. It adds the low bits and lets the
tenth-bit overflow fall off, and the arithmetic comes out right.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-offset-1&quot;&gt;&lt;a href=&quot;#fn-offset&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The two outcomes differ in one field. Taken: &lt;code&gt;s{phase = Fetch, pc = pc s + offAw}&lt;/code&gt; — jump, by pointing the counter at the target and going
straight to &lt;code&gt;Fetch&lt;/code&gt;. Not taken: &lt;code&gt;advance s&lt;/code&gt; — the ordinary &lt;code&gt;pc + 1&lt;/code&gt;. The offset is measured from the branch’s own address, because &lt;code&gt;pc&lt;/code&gt;
still holds that address in &lt;code&gt;Exec&lt;/code&gt;; &lt;code&gt;Fetch&lt;/code&gt; spent a cycle but never
touched it.  And the unconditional jump the assembler offers, &lt;code&gt;j off&lt;/code&gt;,
is not a new instruction at all — it is &lt;code&gt;beq x0, x0, off&lt;/code&gt;, a branch
whose condition compares &lt;code&gt;x0&lt;/code&gt; to itself and is therefore always
taken. The zero register earns its keep twice: once as the absent
operand, once as the always-true comparison.&lt;/p&gt;
&lt;h2 id=&quot;pokes-pins-and-config&quot;&gt;Pokes: pins and config&lt;/h2&gt;
&lt;p&gt;A cluster of arms change one piece of state and step on. They share a
handler, and the handler’s name is a small, honest lie:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  CsAssert&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; pinOp &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;csN&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  CsDeassert&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; pinOp &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;csN&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lanes&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; hiZ&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  RstAssert&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; pinOp &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rstN&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  RstDeassert&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; pinOp &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rstN&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  CrcReset&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; pinOp &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxCrc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  SetConfig&lt;/span&gt;&lt;span&gt; p &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; decodeConfig p &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    Right&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; pinOp &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;cfg&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    Left&lt;/span&gt;&lt;span&gt; _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; haltWith &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;safePins s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; unsupported config -&amp;gt; reason 2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;pinOp&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;State&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;pinOp s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; let&lt;/span&gt;&lt;span&gt; s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; advance s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pinOp&lt;/code&gt; is the single-cycle latch: take a state that already has one
field changed, &lt;code&gt;advance&lt;/code&gt; it to the next instruction, project the pins,
write no trace. Read the arms as “change this, then step.” &lt;code&gt;CsAssert&lt;/code&gt;
pulls &lt;code&gt;CS#&lt;/code&gt; low to open a frame; &lt;code&gt;CsDeassert&lt;/code&gt; raises it &lt;em&gt;and&lt;/em&gt; releases
the lanes to &lt;code&gt;hiZ&lt;/code&gt;, ending the frame cleanly; &lt;code&gt;RstAssert&lt;/code&gt;/&lt;code&gt;RstDeassert&lt;/code&gt;
drive the &lt;code&gt;RESET#&lt;/code&gt; sideband; &lt;code&gt;CrcReset&lt;/code&gt; clears the receive residue so the
next &lt;code&gt;RDSR&lt;/code&gt; starts fresh.&lt;/p&gt;
&lt;p&gt;The name is a lie because &lt;code&gt;CrcReset&lt;/code&gt; and &lt;code&gt;SetConfig&lt;/code&gt; are not pins —
&lt;code&gt;pinOp&lt;/code&gt; is really “the shape of a single-cycle state poke,” and it just
happened to be named for its most common user. &lt;code&gt;SetConfig&lt;/code&gt; is the one
with a fork: it runs its immediate through &lt;code&gt;decodeConfig&lt;/code&gt; — a &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shut
box&lt;/a&gt;, and the one whose contents fold into the ISA post, since
decoding a configuration field is the same act as decoding an instruction
— and either latches the new &lt;code&gt;cfg&lt;/code&gt; or, on a configuration the engine
does not support, traps with reason &lt;code&gt;2&lt;/code&gt;. The two traps we have now met in
passing, config and &lt;code&gt;RDSR&lt;/code&gt;, are reasons &lt;code&gt;2&lt;/code&gt; and &lt;code&gt;3&lt;/code&gt;; the decode error was
&lt;code&gt;1&lt;/code&gt;; the fourth and fifth wait for the parts that raise them.&lt;/p&gt;
&lt;p&gt;One arm in this neighbourhood is not a &lt;code&gt;pinOp&lt;/code&gt;, because it does not just
change state — it reads the world and writes a register:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  GetAlert&lt;/span&gt;&lt;span&gt; rd &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          if&lt;/span&gt;&lt;span&gt; cfgAlertSource &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cfg s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; AlertPin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            then&lt;/span&gt;&lt;span&gt; alertIn inp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            else&lt;/span&gt;&lt;span&gt; ioIn inp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;!!&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;advance s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;regs&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; writeReg &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;regs s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; rd &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;zeroExtend &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pack b&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;GET_ALERT&lt;/code&gt; samples the alert line and drops it into &lt;code&gt;rd&lt;/code&gt;. Which line
“the alert” is depends on configuration: an eSPI target can raise its
alert on a dedicated &lt;code&gt;ALERT#&lt;/code&gt; pin or in-band on &lt;code&gt;IO[1]&lt;/code&gt;, and
&lt;code&gt;cfgAlertSource (cfg s)&lt;/code&gt; picks between &lt;code&gt;alertIn&lt;/code&gt; (the synchronised pin
from this cycle’s &lt;code&gt;BusIn&lt;/code&gt;) and &lt;code&gt;ioIn inp !! 1&lt;/code&gt; (lane one of the sampled
IO). Either way the sampled bit is zero-extended into the register. It is
a single-cycle read — no clock toggles, no beat — because
electrically it &lt;em&gt;is&lt;/em&gt; just a sample, which is exactly why it lives here in
&lt;code&gt;Exec&lt;/code&gt; among the one-cycle arms rather than out in the bus phase with the
transfers.&lt;/p&gt;
&lt;h2 id=&quot;the-ones-that-don-t-finish-here&quot;&gt;The ones that don’t finish here&lt;/h2&gt;
&lt;p&gt;Everything so far has finished inside its one &lt;code&gt;Exec&lt;/code&gt; cycle and returned
to &lt;code&gt;Fetch&lt;/code&gt;. The remaining arms are the transfers and the trace and the
wait — the instructions the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape post&lt;/a&gt; drew detouring through
&lt;code&gt;BusBeat&lt;/code&gt;, &lt;code&gt;TraceEmit&lt;/code&gt;, and &lt;code&gt;WaitAlert&lt;/code&gt; — and their arms do something
subtly different. They spend their &lt;code&gt;Exec&lt;/code&gt; cycle &lt;em&gt;arming&lt;/em&gt; the state and
setting the next phase, then hand off. The finishing is the next post’s
subject; the &lt;em&gt;arming&lt;/em&gt; is this one’s, because it happens right here.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;PUT&lt;/code&gt; and &lt;code&gt;GET&lt;/code&gt; families are the clearest pair. Putting a byte on the
wire:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  PutByteImm&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; startPut b &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  PutByteReg&lt;/span&gt;&lt;span&gt; a &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; startPut &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;truncateB &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;readReg &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;regs s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  startPut byte total &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; phase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; BusBeat&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; busPhase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; beatIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; beatTot&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; total&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; shifter&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; byte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; pending&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; PendNone&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lanes&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; serializeX1 byte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;!!&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read what &lt;code&gt;startPut&lt;/code&gt; writes into the state, because every field is a
piece of setup the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape post&lt;/a&gt; named and left idle. &lt;code&gt;phase = BusBeat&lt;/code&gt; points the machine at the bus phase. &lt;code&gt;busPhase&lt;/code&gt;, &lt;code&gt;beatIx&lt;/code&gt; reset
the SCK counter and the bit counter to the start of a transfer.
&lt;code&gt;beatTot = total&lt;/code&gt; records how many bits — eight for a byte. &lt;code&gt;shifter = byte&lt;/code&gt; loads the bits to send. And &lt;code&gt;lanes = serializeX1 byte !! 0&lt;/code&gt; drives
the &lt;em&gt;first&lt;/em&gt; bit onto the lanes immediately, using &lt;code&gt;serializeX1&lt;/code&gt;, the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;serialiser&lt;/a&gt; that is a shut box for the last post in this arc.
&lt;code&gt;startPut&lt;/code&gt; does not run the transfer; it loads the gun and points the
machine at the phase that pulls the trigger, and then it is done, in one
cycle, like everything else in &lt;code&gt;Exec&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Getting a byte is the mirror image, and it uses the &lt;code&gt;pending&lt;/code&gt; field the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape post&lt;/a&gt; introduced as the machine’s “deferred work” slot:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  startGet rd total crc &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; phase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; BusBeat&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;            --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; … busPhase, beatIx, beatTot as before …&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; shifter&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lanes&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; hiZ&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; pending&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; PendGet&lt;/span&gt;&lt;span&gt; rd total crc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;code&gt;GET&lt;/code&gt; cannot write its result now — the bits have not been sampled
yet. So it records what it will owe when they have: &lt;code&gt;pending = PendGet rd total crc&lt;/code&gt; says “when this transfer completes, write the result into
&lt;code&gt;rd&lt;/code&gt;, it is &lt;code&gt;total&lt;/code&gt; bits wide, and &lt;code&gt;crc&lt;/code&gt; decides whether to fold it into
the residue.” The register the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;shape post&lt;/a&gt; saw in &lt;code&gt;PendGet&lt;/code&gt;’s
first field is filled in right here, by the instruction that will be paid
out cycles later. &lt;code&gt;lanes = hiZ&lt;/code&gt; releases the bus so the other side can
drive it. The rest — &lt;code&gt;TAR&lt;/code&gt;, which drives a turnaround; &lt;code&gt;MARK&lt;/code&gt;, which
writes the first word of a two-word trace record and arms &lt;code&gt;TraceEmit&lt;/code&gt; for
the second; &lt;code&gt;WAIT_ON&lt;/code&gt;, which loads &lt;code&gt;waitTimer&lt;/code&gt; and enters &lt;code&gt;WaitAlert&lt;/code&gt; —
are the same move in different clothes: set the phase, arm the scratch,
record what is pending, hand off. Their completions, and the bus timing
that drives them, are the next post.&lt;/p&gt;
&lt;h2 id=&quot;what-we-read&quot;&gt;What we read&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Exec&lt;/code&gt;, walked end to end. It begins by asking &lt;code&gt;decode&lt;/code&gt; to turn the
fetched word into an &lt;code&gt;Instr&lt;/code&gt;, and forks on the answer: a &lt;code&gt;Left&lt;/code&gt; traps
with reason one and safe pins, a &lt;code&gt;Right&lt;/code&gt; goes to a &lt;code&gt;case&lt;/code&gt; as wide as the
instruction set. That width is a surface; underneath it the arms collapse
to three destinations. Thirteen DATA instructions share one &lt;code&gt;dataWb&lt;/code&gt; that
hands the whole instruction to the still-shut ALU and writes one
register; the operand-selector tables say which registers each reads, and
&lt;code&gt;x0&lt;/code&gt; makes “no operand” free. &lt;code&gt;RDSR&lt;/code&gt; answers the one status the engine
owns, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; residue. The four branches defer their condition to
the shut comparator but keep the PC arithmetic for themselves — an
eleven-bit signed offset added modulo a ten-bit counter, backwards jumps
falling out of wraparound, &lt;code&gt;j&lt;/code&gt; revealed as &lt;code&gt;beq x0, x0&lt;/code&gt;. The pin and
config pokes latch one field through a handler misnamed for its commonest
user, and &lt;code&gt;GET_ALERT&lt;/code&gt; samples a line the configuration chooses. And the
transfers, the trace, and the wait spend their one cycle here not
finishing but &lt;em&gt;arming&lt;/em&gt; — loading a shifter, counting out beats,
recording in &lt;code&gt;pending&lt;/code&gt; what they will owe — and pointing the machine at
a longer phase.&lt;/p&gt;
&lt;p&gt;We have now used, without opening, every leaf the engine composes:
&lt;code&gt;decode&lt;/code&gt;, &lt;code&gt;dataResult&lt;/code&gt;, &lt;code&gt;branchTaken&lt;/code&gt;, &lt;code&gt;readReg&lt;/code&gt; and &lt;code&gt;writeReg&lt;/code&gt;,
&lt;code&gt;decodeConfig&lt;/code&gt;, and &lt;code&gt;serializeX1&lt;/code&gt;. That was the bet of reading &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-engine-shape/&quot;&gt;outside
in&lt;/a&gt;, and it has paid: you can follow every instruction the engine
runs while holding those seven as one-line promises. The promises come
due later in the arc.&lt;/p&gt;
&lt;p&gt;But first, the handoff. Four arms pointed the machine at &lt;code&gt;BusBeat&lt;/code&gt;,
&lt;code&gt;TraceEmit&lt;/code&gt;, and &lt;code&gt;WaitAlert&lt;/code&gt; and stopped mid-gesture — a shifter loaded,
a beat count set, a write pending, a phase changed — and the cycle
ended there. Next we follow them across that seam: the bus micro-FSM that
divides the fabric clock into an &lt;code&gt;SCK&lt;/code&gt;, walks the beats a &lt;code&gt;PUT&lt;/code&gt; or &lt;code&gt;GET&lt;/code&gt;
counts out, samples what comes back and folds it into the residue and the
trace ring, and finally writes the terminator records — &lt;code&gt;HALT&lt;/code&gt; and the
traps we kept meeting — that end a run. The engine has decided what to
do; next it does it, on the wire, one quarter-clock at a time.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-reasons&quot;&gt;
&lt;p&gt;The engine gives five trap reasons, and this post meets three
of them where they are raised: &lt;code&gt;1&lt;/code&gt; for a decode error (here in
&lt;code&gt;stepExec&lt;/code&gt;), &lt;code&gt;2&lt;/code&gt; for an unsupported configuration (&lt;code&gt;SetConfig&lt;/code&gt;), and &lt;code&gt;3&lt;/code&gt;
for a reserved status register (&lt;code&gt;RDSR&lt;/code&gt;). The remaining two — &lt;code&gt;4&lt;/code&gt; for an
illegal or reserved-group instruction, and the machinery that folds the
reason, a trap flag, and the sticky overflow bit into a single terminator
word — belong to the next post, where &lt;code&gt;haltWith&lt;/code&gt; is read in full. Every
trap is a &lt;code&gt;HALT&lt;/code&gt; that also says &lt;em&gt;why&lt;/em&gt;, and drives the pins safe on the way
down, so a program that dies mid-frame cannot leave &lt;code&gt;CS#&lt;/code&gt; or the lanes
stuck driving the bus. &lt;a href=&quot;#fr-reasons-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-offset&quot;&gt;
&lt;p&gt;The one thing the truncation &lt;em&gt;does&lt;/em&gt; cost is range: an
eleven-bit offset can name distances a ten-bit counter cannot, so the top
bit of the offset is redundant with the sign that the low ten bits
already imply modulo &lt;code&gt;1024&lt;/code&gt;. In practice the assembler only emits offsets
that resolve to a real target inside the thousand-and-twenty-four-word
program, so the reachable set is the whole store either way, and the
wraparound is a correctness argument, not a reachability limit. It is the
same reason a byte-addressed CPU can use a signed branch displacement
narrower than its full address space: you never branch further than the
program is long. &lt;a href=&quot;#fr-offset-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Tamal: Eight Phases and a Pure Step</title>
    <published>2026-07-31T09:00:00+00:00</published>
    <updated>2026-07-31T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-engine-shape/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-engine-shape/</id>
    <summary type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt; closed on a promise: &lt;em&gt;next, we walk through.&lt;/em&gt; Every
block before them — the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt;, the UART, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader&lt;/a&gt;, the
two &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;block RAMs&lt;/a&gt; — was read the same way, opened and emptied and
set back down, and each ended by pointing at the one door still shut.
This is that door. &lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/tamal&quot;&gt;Tamal&lt;/a&gt;’s engine is the machine the whole series has
been circling: the thing the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader&lt;/a&gt; fills, triggers, and
drains; the thing that turns the words in one memory into the records in
the other; the reason any of the plumbing exists.&lt;/p&gt;
&lt;p&gt;But you do not read a machine the way you read a leaf.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt; closed on a promise: &lt;em&gt;next, we walk through.&lt;/em&gt; Every
block before them — the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt;, the UART, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader&lt;/a&gt;, the
two &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;block RAMs&lt;/a&gt; — was read the same way, opened and emptied and
set back down, and each ended by pointing at the one door still shut.
This is that door. &lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/tamal&quot;&gt;Tamal&lt;/a&gt;’s engine is the machine the whole series has
been circling: the thing the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader&lt;/a&gt; fills, triggers, and
drains; the thing that turns the words in one memory into the records in
the other; the reason any of the plumbing exists.&lt;/p&gt;
&lt;p&gt;But you do not read a machine the way you read a leaf.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;read-the-shape-first&quot;&gt;Read the shape first&lt;/h2&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; was twenty-eight lines and one idea; the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt;
were four lines and a contract. Each fit on a screen, and each was read
&lt;em&gt;whole&lt;/em&gt; — top to bottom, every line accounted for, nothing left shut
when the post ended. That is what a leaf is: small enough to hold
entire.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Tamal.Engine&lt;/code&gt; is not small. It is five hundred and thirty-four lines
(four hundred and ten of which are actual code), and — this is the
part that changes how we have to read it — it &lt;em&gt;composes eight other
modules.&lt;/em&gt; It calls the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; we already read, and seven blocks we
have not: a decoder, an ALU, a branch comparator, a register file, a
serialiser, a configuration decoder, and a pair of width aliases. Open
all of that at once, in the order the lines happen to fall, and the
shape is gone before the second page — you are three levels deep in
a bus-timing detail with no memory of the machine it serves.&lt;/p&gt;
&lt;p&gt;So we read it the other way. This post reads the engine’s &lt;strong&gt;shape&lt;/strong&gt; —
its skeleton, its lifecycle, the type of the one function that is the
whole machine — and it leaves the rooms shut. When the engine reaches
for &lt;code&gt;decode&lt;/code&gt; we will say &lt;em&gt;decode turns a word into an instruction&lt;/em&gt; and
walk on; when it reaches for &lt;code&gt;dataResult&lt;/code&gt; we will say &lt;em&gt;that is the ALU&lt;/em&gt;
and walk on. The boxes get names here and get opened later, one post
each, in the order the engine actually touches them.&lt;/p&gt;
&lt;p&gt;This is the exact inverse of how we read the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;UART&lt;/a&gt;. There we built
&lt;em&gt;up&lt;/em&gt;: the baud generator, then the transmitter, then the receiver, and
only at the end the five-line &lt;code&gt;uart&lt;/code&gt; umbrella — which could simply
&lt;em&gt;name&lt;/em&gt; &lt;code&gt;oversampleTick&lt;/code&gt;, &lt;code&gt;uartRx&lt;/code&gt;, and &lt;code&gt;uartTx&lt;/code&gt;, because by then we knew
every one of them. Leaves first, umbrella last. The engine turns that
around. We start at the umbrella — the biggest one in the project —
and it will name blocks we have &lt;em&gt;not&lt;/em&gt; read, on purpose, because with a
machine this size the shape is the thing you need before the pieces, not
after. Outside in, not inside out.&lt;/p&gt;
&lt;p&gt;Here is the whole map in one sentence, and the rest of the post is its
legend: &lt;strong&gt;the engine is a single pure function that, once a cycle, looks
at where it is and decides what to do next.&lt;/strong&gt; Everything below is what
“where it is” and “what to do next” are made of.&lt;/p&gt;
&lt;h2 id=&quot;the-widest-door-yet&quot;&gt;The widest door yet&lt;/h2&gt;
&lt;p&gt;A &lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/tamal&quot;&gt;Tamal&lt;/a&gt; module opens by naming itself and listing what leaves through
the wall. We have read that opening five times now, and each time the
door was narrow: the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; exported one name, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt;
exported two. Here is the engine’s:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Engine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Phase&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Pending&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusIn&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusOut&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; initState&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; powerUpDefault&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; revisionWord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; busOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; step&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eleven names — the widest export list in the series — and the first
thing to notice is that &lt;strong&gt;six of them are types.&lt;/strong&gt; &lt;code&gt;Phase&lt;/code&gt;, &lt;code&gt;Pending&lt;/code&gt;,
&lt;code&gt;State&lt;/code&gt;, &lt;code&gt;BusIn&lt;/code&gt;, &lt;code&gt;BusOut&lt;/code&gt;, &lt;code&gt;Ring&lt;/code&gt;: half the door is given over not to
functions but to the &lt;em&gt;shapes of data&lt;/em&gt;, each exported with the &lt;code&gt;(..)&lt;/code&gt;
that says “and all of its constructors too.” The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; exported a
verb; the engine exports mostly nouns.&lt;/p&gt;
&lt;p&gt;That is not clutter, and it is worth pausing on, because it tells you
what kind of module this is. A leaf like &lt;code&gt;crc8Update&lt;/code&gt; can hide its
types — it takes a &lt;code&gt;BitVector 8&lt;/code&gt; and gives one back, and the outside
world never needs a name for anything &lt;em&gt;inside&lt;/em&gt;. The engine cannot hide
its types, because the engine does not run itself. Look at the last two
names on the list: &lt;code&gt;step&lt;/code&gt;, the transition function, and &lt;code&gt;busOut&lt;/code&gt;, a
projection of it. Neither is &lt;code&gt;mealy&lt;/code&gt;. Neither touches a &lt;code&gt;Signal&lt;/code&gt;.
&lt;code&gt;Tamal.Engine&lt;/code&gt; is a &lt;em&gt;pure&lt;/em&gt; description of a machine, and something one
level up — the board’s &lt;code&gt;system&lt;/code&gt;, a later post — has to lift it onto
a clock and wire its inputs and outputs to real pins. To do that, the
top must be able to &lt;em&gt;say&lt;/em&gt; &lt;code&gt;State&lt;/code&gt;, and &lt;code&gt;BusIn&lt;/code&gt;, and &lt;code&gt;BusOut&lt;/code&gt;. The types
are exported because they are the engine’s contract with whatever runs
it: the wiring diagram one level up is written in these nouns. A machine
that will be assembled elsewhere has to publish the shape of its own
plugs.&lt;/p&gt;
&lt;h2 id=&quot;eight-closed-boxes&quot;&gt;Eight closed boxes&lt;/h2&gt;
&lt;p&gt;Under the header sit the imports, and this is where reading outside in
first costs us something:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Clash.Prelude&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Alu&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;dataResult&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; qualified&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Branch&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; as&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Br&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Bus.Serdes&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Lanes&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; hiZ&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; serializeX1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; tarBeat&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Config&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;AlertSource&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Config&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; IoMode&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Role&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Sck&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; decodeConfig&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Crc&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;crc8Update&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Isa&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Instr&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; decode&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Params&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;AW&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RW&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.RegFile&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Regs&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; initRegs&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; readReg&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; writeReg&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;prelude swap&lt;/a&gt; on the first line is the same one every block has
carried — the line that trades ordinary Haskell’s furniture for the
&lt;code&gt;Signal&lt;/code&gt;, &lt;code&gt;Bit&lt;/code&gt;, &lt;code&gt;Vec&lt;/code&gt;, and &lt;code&gt;BitVector&lt;/code&gt; that lower to gates — and I
will not re-derive it a sixth time. It is the eight lines &lt;em&gt;under&lt;/em&gt; it that
are new. When we read the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;UART umbrella&lt;/a&gt;, its three imports pulled
in blocks we had already opened; naming &lt;code&gt;uartRx&lt;/code&gt; meant something because
we had spent a post inside it. Here, eight modules come across the wall,
and we have opened exactly &lt;em&gt;one&lt;/em&gt; of them.&lt;/p&gt;
&lt;p&gt;That one is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt;: &lt;code&gt;crc8Update&lt;/code&gt;, the polynomial-remainder block
we read whole, arrives here to keep a running residue over the bytes the
engine reads off the bus. And one more is not quite an import of code but
of vocabulary — &lt;code&gt;Tamal.Params (AW, RW)&lt;/code&gt;, the two width aliases we
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;met alongside the memories&lt;/a&gt;: &lt;code&gt;AW = 10&lt;/code&gt; for the program counter,
&lt;code&gt;RW = 12&lt;/code&gt; for the ring pointer. Those two are known quantities.&lt;/p&gt;
&lt;p&gt;The other six are the boxes this series will open next, and it is worth
naming what each one &lt;em&gt;is&lt;/em&gt; now, precisely because we are about to spend
three posts using them without looking inside:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Tamal.Isa&lt;/code&gt; — &lt;code&gt;decode&lt;/code&gt;.&lt;/strong&gt; Turns a raw 32-bit word into a typed
&lt;code&gt;Instr&lt;/code&gt;, or fails. The instruction set itself: every opcode the engine
knows, and the reserved patterns it refuses. The engine’s first act on
any word is to call this.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Tamal.Alu&lt;/code&gt; — &lt;code&gt;dataResult&lt;/code&gt;.&lt;/strong&gt; The combinational compute for the
DATA instructions: add, subtract, the bitwise operations, the shift.
Give it a decoded instruction and two register values, it hands back
the result.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Tamal.Branch&lt;/code&gt; — &lt;code&gt;branchTaken&lt;/code&gt;&lt;/strong&gt; (imported qualified as &lt;code&gt;Br&lt;/code&gt;).
The unsigned comparator behind the conditional branches: given a
comparison and two values, is the branch taken?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Tamal.RegFile&lt;/code&gt; — &lt;code&gt;readReg&lt;/code&gt;, &lt;code&gt;writeReg&lt;/code&gt;, &lt;code&gt;initRegs&lt;/code&gt;, &lt;code&gt;Regs&lt;/code&gt;.&lt;/strong&gt;
The sixteen general-purpose registers, &lt;code&gt;x0&lt;/code&gt; hardwired to zero, that the
datapath reads its operands from and writes its results to.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Tamal.Bus.Serdes&lt;/code&gt; — &lt;code&gt;serializeX1&lt;/code&gt;, &lt;code&gt;tarBeat&lt;/code&gt;, &lt;code&gt;Lanes&lt;/code&gt;, &lt;code&gt;hiZ&lt;/code&gt;.&lt;/strong&gt;
The bit-level serialiser: how a byte becomes MSB-first drive on the IO
lanes, what a turnaround looks like, and what “all lanes released”
means. The closest thing to the wire.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Tamal.Config&lt;/code&gt; — &lt;code&gt;decodeConfig&lt;/code&gt;&lt;/strong&gt; and the configuration types
(&lt;code&gt;Role&lt;/code&gt;, &lt;code&gt;IoMode&lt;/code&gt;, &lt;code&gt;Sck&lt;/code&gt;, &lt;code&gt;AlertSource&lt;/code&gt;). How a &lt;code&gt;SET_CONFIG&lt;/code&gt; immediate
becomes the role, IO width, clock rate, and alert source the engine
runs under.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Six modules, four posts, opened in that rough order once the shape is
drawn. For now they are names with one-line jobs, and that is &lt;em&gt;enough&lt;/em&gt; —
which is the whole bet of reading outside in. You can understand what the
engine &lt;em&gt;does&lt;/em&gt; with a decoder long before you understand how the decoder
works, the same way you can read a paragraph knowing a word means
“remainder” without having derived long division. Hold the eight names.
We will spend them.&lt;/p&gt;
&lt;h2 id=&quot;one-line-that-is-the-whole-machine&quot;&gt;One line that is the whole machine&lt;/h2&gt;
&lt;p&gt;Skip past the type declarations for a moment — we will come back and
read every field — to the function they all exist to serve. Stripped of
its doc-comment, this is &lt;code&gt;step&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;State&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;step s inp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; phase s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Idle&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; stepIdle s inp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Halted&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; stepHalted s inp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Preamble&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; stepPreamble s inp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Fetch&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; stepFetch s inp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Exec&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; stepExec s inp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  BusBeat&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; stepBusBeat s inp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  TraceEmit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; stepTraceEmit s inp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  WaitAlert&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; stepWaitAlert s inp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The signature is the map, and under the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt;’s reading — a type is
half the documentation — it very nearly tells the whole story before
the body does. Read it as three arrows’ worth of intent:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Given the machine’s current &lt;code&gt;State&lt;/code&gt; and this one cycle’s inputs
(&lt;code&gt;BusIn&lt;/code&gt;), produce the next &lt;code&gt;State&lt;/code&gt;, the pins to drive right now
(&lt;code&gt;BusOut&lt;/code&gt;), and &lt;em&gt;maybe&lt;/em&gt; one word to write into the trace ring.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is a &lt;strong&gt;Mealy machine&lt;/strong&gt;, the exact shape the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt; introduced and
the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;transmitter&lt;/a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;receiver&lt;/a&gt;, and &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader&lt;/a&gt; each wore:
a pure function from current-state-and-input to next-state-and-output,
with all the memory pulled out into that first argument and threaded back
out as the first result. What makes the engine’s version the keystone is
not its shape — it is the same &lt;code&gt;s -&amp;gt; i -&amp;gt; (s, o)&lt;/code&gt; as every mealy before
it — but its &lt;em&gt;size&lt;/em&gt;, and one deliberate choice hiding in the return
type.&lt;/p&gt;
&lt;p&gt;The choice is that the output is split in three: &lt;code&gt;(State, BusOut, Maybe Ring)&lt;/code&gt;, not the two-tuple &lt;code&gt;(State, output)&lt;/code&gt; the primer’s &lt;code&gt;mealy&lt;/code&gt; wants.
The middle element, &lt;code&gt;BusOut&lt;/code&gt;, is the pins — everything the engine is
driving onto the world this cycle. The third, &lt;code&gt;Maybe Ring&lt;/code&gt;, is a
&lt;em&gt;write&lt;/em&gt;: &lt;code&gt;Just&lt;/code&gt; a record to store in the trace memory, or &lt;code&gt;Nothing&lt;/code&gt;, at
most one per cycle. Keeping them separate is a readability decision the
top pays back for; when the engine is finally lifted, a four-line adapter
re-bundles the pins and the write into the single output &lt;code&gt;mealy&lt;/code&gt; expects,
and &lt;code&gt;step&lt;/code&gt; itself is never touched.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-stepm-1&quot;&gt;&lt;a href=&quot;#fn-stepm&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;And note what the body of &lt;code&gt;step&lt;/code&gt; actually &lt;em&gt;is&lt;/em&gt;: a &lt;code&gt;case&lt;/code&gt; on &lt;code&gt;phase s&lt;/code&gt; —
one field of the state — dispatching to eight helpers, one per phase,
and doing nothing else. &lt;code&gt;step&lt;/code&gt; does not compute; it &lt;em&gt;routes&lt;/em&gt;. The real
work lives in the eight &lt;code&gt;step*&lt;/code&gt; functions, and the machine’s entire
behaviour is “look at which phase you are in, and hand the cycle to the
helper that owns it.” That is the skeleton. The rest of this post reads
the state the &lt;code&gt;case&lt;/code&gt; inspects, the phases it dispatches on, and the four
simplest helpers; the next post opens &lt;code&gt;stepExec&lt;/code&gt;, and the one after that
opens the three bus-and-trace phases.&lt;/p&gt;
&lt;p&gt;One more thing the signature says by saying nothing: there is no &lt;code&gt;mealy&lt;/code&gt;
here, no &lt;code&gt;register&lt;/code&gt;, no &lt;code&gt;Signal&lt;/code&gt; anywhere in the type. &lt;code&gt;step&lt;/code&gt; is a
&lt;em&gt;pure&lt;/em&gt; function — same state and input, same three outputs, every
time. That is not an aesthetic preference; it is the reason the engine is
testable at all. Because the whole transition, bus timing and trace
emission included, is one pure function, &lt;a rel=&quot;external&quot; href=&quot;https://hedgehog.qa&quot;&gt;hedgehog&lt;/a&gt; can drive it by
folding it over a list of cycles and check the results against a
reference model — no simulator, no waveform, no clock. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;intro&lt;/a&gt;
promised a design where the &lt;em&gt;entire&lt;/em&gt; machine runs in a sub-second test
suite. This signature, pure to its bones, is where that promise is kept.&lt;/p&gt;
&lt;h2 id=&quot;eight-phases&quot;&gt;Eight phases&lt;/h2&gt;
&lt;p&gt;The field the &lt;code&gt;case&lt;/code&gt; inspects is &lt;code&gt;phase&lt;/code&gt;, and its type is the machine’s
spine:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Phase&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Idle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Preamble&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Fetch&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Exec&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; BusBeat&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TraceEmit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; WaitAlert&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Halted&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eight constructors, no fields — a plain enumeration, the hardware
equivalent of a state register wide enough to hold eight states. This is
the lifecycle of the whole engine, and every one of the &lt;code&gt;step*&lt;/code&gt; helpers
is named for exactly one of these. Read them as the stages of a life:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Idle&lt;/code&gt;&lt;/strong&gt; — powered up, pins safe, waiting for a start trigger. The
resting state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Preamble&lt;/code&gt;&lt;/strong&gt; — the first cycle of a run: write the &lt;code&gt;REVISION&lt;/code&gt; word
into the top of the trace ring so the host can confirm what bitstream
it is talking to.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Fetch&lt;/code&gt;&lt;/strong&gt; — the one-cycle bubble while the instruction memory
hands back the word at the program counter. We will see exactly why
it costs a cycle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Exec&lt;/code&gt;&lt;/strong&gt; — the hub: decode the fetched word and dispatch on what it
is. Every instruction passes through here.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;BusBeat&lt;/code&gt;&lt;/strong&gt; — sequencing an SCK-timed transfer on the eSPI bus:
the multi-cycle drive-and-sample of a &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;GET&lt;/code&gt;, or turnaround.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;TraceEmit&lt;/code&gt;&lt;/strong&gt; — the second cycle of a two-word trace record (a
&lt;code&gt;MARK&lt;/code&gt;), spilling its payload word after its header.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;WaitAlert&lt;/code&gt;&lt;/strong&gt; — blocked inside a &lt;code&gt;WAIT_ON&lt;/code&gt;, watching the alert
line and counting down a timeout.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Halted&lt;/code&gt;&lt;/strong&gt; — stopped, after a &lt;code&gt;HALT&lt;/code&gt; or a trap, holding the pins
safe and asserting a flag that tells the top to drain the ring.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;deriving&lt;/code&gt; block is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;same four-class refrain&lt;/a&gt; every
stateful block in the series has carried, and I will be quick with it:
&lt;code&gt;Generic&lt;/code&gt; lets Clash derive the rest structurally, &lt;code&gt;Show&lt;/code&gt; and &lt;code&gt;Eq&lt;/code&gt; are
for the tests, and &lt;code&gt;NFDataX&lt;/code&gt; is the one that earns its place — it is
Clash’s promise that a value of this type can be the contents of a
register, with a defined-enough representation to sit in a
flip-flop.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-nfdatax-1&quot;&gt;&lt;a href=&quot;#fn-nfdatax&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt; A &lt;code&gt;Phase&lt;/code&gt; is going to &lt;em&gt;be&lt;/em&gt; a register on the fabric;
&lt;code&gt;NFDataX&lt;/code&gt; is what lets it.&lt;/p&gt;
&lt;h2 id=&quot;the-state-it-carries&quot;&gt;The state it carries&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Phase&lt;/code&gt; says &lt;em&gt;where&lt;/em&gt; the machine is. The &lt;code&gt;State&lt;/code&gt; record says everything
else the machine remembers — and at seventeen fields it is the largest
record in the project. Do not read it as a list; read it as six groups,
which is how it is written:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; State&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; phase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Phase&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; pc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; AW&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; regs&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Regs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; cfg&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Config&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxCrc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ringPtr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RW&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ovf&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; csN&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; sck&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rstN&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lanes&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Lanes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; busPhase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; beatIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; beatTot&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; shifter&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; pending&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Pending&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; waitTimer&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 9&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The lifecycle&lt;/strong&gt; is one field: &lt;code&gt;phase&lt;/code&gt;, the enum we just read, the thing
&lt;code&gt;step&lt;/code&gt; dispatches on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The architectural state&lt;/strong&gt; — four fields — is what a program can
see and change, the machine’s equivalent of a CPU’s visible registers:
&lt;code&gt;pc&lt;/code&gt;, the program counter, an &lt;code&gt;Unsigned AW&lt;/code&gt; indexing the instruction
store; &lt;code&gt;regs&lt;/code&gt;, the sixteen general-purpose registers (a &lt;code&gt;Regs&lt;/code&gt;, from the
still-shut &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;register file&lt;/a&gt;); &lt;code&gt;cfg&lt;/code&gt;, the current configuration (role,
IO width, clock, alert source); and &lt;code&gt;rxCrc&lt;/code&gt;, the running CRC-8 residue
over bytes read from the bus — the accumulator the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC block&lt;/a&gt; was
built to advance, living here as engine state.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The trace state&lt;/strong&gt; — two fields — is the outflow bookkeeping:
&lt;code&gt;ringPtr&lt;/code&gt;, the next free slot in the trace ring (an &lt;code&gt;Unsigned RW&lt;/code&gt;), and
&lt;code&gt;ovf&lt;/code&gt;, a sticky boolean that latches high the first time a record is
dropped because the ring filled. Together they are the “drop on overflow,
never stall the bus” rule the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;intro&lt;/a&gt; set out, reduced to a pointer and a
flag.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The pin latches&lt;/strong&gt; — four fields — are the outputs, &lt;em&gt;stored&lt;/em&gt;:
&lt;code&gt;csN&lt;/code&gt;, &lt;code&gt;sck&lt;/code&gt;, &lt;code&gt;rstN&lt;/code&gt;, and &lt;code&gt;lanes&lt;/code&gt;. This is a deliberate choice worth
naming now and cashing later. The engine does not compute its pins fresh
each cycle from its inputs; it &lt;em&gt;holds&lt;/em&gt; them in the state and updates them
on transitions. That makes them registered outputs — glitch-free, and
depending only on where the machine is, not on what arrived this
cycle.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-moore-1&quot;&gt;&lt;a href=&quot;#fn-moore&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; &lt;code&gt;lanes&lt;/code&gt; is a &lt;code&gt;Lanes&lt;/code&gt;, the per-lane drive-and-enable bundle
from the still-shut &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;serialiser&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The bus scratch&lt;/strong&gt; — four fields — is the working memory of a
transfer, meaningless outside a &lt;code&gt;BusBeat&lt;/code&gt;: &lt;code&gt;busPhase&lt;/code&gt;, an &lt;code&gt;Index 5&lt;/code&gt; that
counts the five fabric cycles of one SCK period; &lt;code&gt;beatIx&lt;/code&gt; and &lt;code&gt;beatTot&lt;/code&gt;,
which bit of how many; and &lt;code&gt;shifter&lt;/code&gt;, the eight-bit register that bits
are driven out of or sampled into. We will read every one of these in the
bus post; here they are just named.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The deferred work&lt;/strong&gt; — two fields — is how a single &lt;code&gt;Exec&lt;/code&gt; cycle
arranges for something to finish &lt;em&gt;later&lt;/em&gt;: &lt;code&gt;pending&lt;/code&gt;, a small sum type we
will meet in a moment, and &lt;code&gt;waitTimer&lt;/code&gt;, the countdown a &lt;code&gt;WAIT_ON&lt;/code&gt; arms.
When decoding an instruction needs an after-effect — a register to
write once a &lt;code&gt;GET&lt;/code&gt; completes, a second word to spill, an alert to wait
for — &lt;code&gt;Exec&lt;/code&gt; records it here and a later phase carries it out.&lt;/p&gt;
&lt;p&gt;Seventeen fields, six jobs. And the shape of &lt;code&gt;Pending&lt;/code&gt;, exported beside
&lt;code&gt;State&lt;/code&gt;, is the catalogue of those deferred after-effects:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Pending&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; PendNone&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; PendGet&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; PendMark&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; PendTar&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; PendWait&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Reg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;PendNone&lt;/code&gt; is the resting value: nothing owed. The other four each
name a piece of work an &lt;code&gt;Exec&lt;/code&gt; decode sets up and a later cycle
discharges — a register write-back waiting on a completed &lt;code&gt;GET&lt;/code&gt;, a
payload word waiting to be spilled, an in-flight turnaround, a register
waiting on an alert. Reading &lt;code&gt;Pending&lt;/code&gt; in full is reading the multi-cycle
instructions in full, so it belongs to the posts that open those; for now
it is enough to see that “deferred work” has exactly five shapes, and
that the machine is never owed more than one thing at a time.&lt;/p&gt;
&lt;h2 id=&quot;what-goes-in-what-comes-out&quot;&gt;What goes in, what comes out&lt;/h2&gt;
&lt;p&gt;Three types remain, and together they are the engine’s boundary — the
plugs the exported nouns exist to let the top wire. First, what the top
feeds in every cycle:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; BusIn&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; instrWord&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ioIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Vec&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; alertIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; startIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;instrWord&lt;/code&gt; is the word the instruction memory hands back — and here
the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories post&lt;/a&gt; pays off exactly. That post spent its length
pinning down a one-cycle read latency: ask a block RAM for the word at an
address this cycle, get it &lt;em&gt;next&lt;/em&gt; cycle. &lt;code&gt;instrWord&lt;/code&gt; is that word,
arriving one cycle after the engine offered its &lt;code&gt;pc&lt;/code&gt; — which is the
whole reason &lt;code&gt;Fetch&lt;/code&gt; exists as a phase, as we are about to see. &lt;code&gt;ioIn&lt;/code&gt; is
the four IO lanes sampled off the pads; &lt;code&gt;alertIn&lt;/code&gt; is the synchronised
alert pin; &lt;code&gt;startIn&lt;/code&gt; is the control-plane trigger the top holds high once
a program is loaded.&lt;/p&gt;
&lt;p&gt;Then what the engine drives out, every cycle:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; BusOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; pcOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; AW&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; csOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; sckOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rstOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lanesOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Lanes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; haltedOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ringPtrOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RW&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every one of these is something the top wires to a pin or a memory.
&lt;code&gt;pcOut&lt;/code&gt; is the fetch address handed to the instruction RAM — the other
half of the round trip &lt;code&gt;instrWord&lt;/code&gt; completes. &lt;code&gt;csOut&lt;/code&gt;, &lt;code&gt;sckOut&lt;/code&gt;,
&lt;code&gt;rstOut&lt;/code&gt;, &lt;code&gt;lanesOut&lt;/code&gt; are the eSPI pins. &lt;code&gt;haltedOut&lt;/code&gt; is the flag the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader&lt;/a&gt; watches to know when to start draining. And
&lt;code&gt;ringPtrOut&lt;/code&gt; is how deep the trace ring filled, so the drain knows how
much to sweep. If you read the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader&lt;/a&gt; and wondered where
&lt;code&gt;startIn&lt;/code&gt;, &lt;code&gt;haltedOut&lt;/code&gt;, and &lt;code&gt;ringPtrOut&lt;/code&gt; reached to — this is their
other end.&lt;/p&gt;
&lt;p&gt;And last, the write itself:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Ring&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rAddr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RW&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rData&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;code&gt;Ring&lt;/code&gt; is one word &lt;code&gt;rData&lt;/code&gt; bound for one address &lt;code&gt;rAddr&lt;/code&gt; in the trace
memory. It is the third element of &lt;code&gt;step&lt;/code&gt;’s output wrapped in &lt;code&gt;Maybe&lt;/code&gt;,
because a cycle writes at most one trace word — and most cycles write
none.&lt;/p&gt;
&lt;h2 id=&quot;the-pins-are-a-projection-not-a-computation&quot;&gt;The pins are a projection, not a computation&lt;/h2&gt;
&lt;p&gt;We said the pins live &lt;em&gt;in&lt;/em&gt; the state. &lt;code&gt;busOut&lt;/code&gt; is the function that reads
them back out, and it is deliberately the dullest function in the file:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;busOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;busOut s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  BusOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; pcOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; pc s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; csOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; csN s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; sckOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; sck s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rstOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; rstN s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lanesOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; lanes s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; haltedOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; phase s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Halted&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ringPtrOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; ringPtr s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read it and there is nothing to compute: five of the seven outputs are a
field of &lt;code&gt;State&lt;/code&gt; copied straight across, &lt;code&gt;ringPtrOut&lt;/code&gt; is one more, and
the only field that is not a bare copy is &lt;code&gt;haltedOut&lt;/code&gt;, which is the
single comparison &lt;code&gt;phase s == Halted&lt;/code&gt; — the flag is not stored, it is
&lt;em&gt;derived&lt;/em&gt;, true exactly when the machine is in its stopped phase. That is
the whole function.&lt;/p&gt;
&lt;p&gt;That dullness is the point, and it is the same point the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;UART
umbrella&lt;/a&gt; made about wiring: &lt;code&gt;busOut&lt;/code&gt; adds no behaviour. It is a
&lt;strong&gt;projection&lt;/strong&gt; — a lens that shows the outside world the fields of the
state it is allowed to see, and nothing more. Because the pins were
already decided when the state was built, presenting them costs no logic;
the outputs are a &lt;em&gt;view&lt;/em&gt; of the machine, computed once, the same whether
you look this cycle or leave it be. Every &lt;code&gt;step*&lt;/code&gt; helper ends by calling
&lt;code&gt;busOut&lt;/code&gt; on the state it produced, and so the pins are always exactly the
state’s own pins. There is no second place where an output could
disagree with the state that owns it.&lt;/p&gt;
&lt;h2 id=&quot;power-up&quot;&gt;Power-up&lt;/h2&gt;
&lt;p&gt;Three small definitions set the machine’s power-up contents. The first
two are the constants a fresh run starts from:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;powerUpDefault&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Config&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;powerUpDefault &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Config&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Controller&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; X1&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Sck20&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; AlertPin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;revisionWord&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;revisionWord &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x00_01_0000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;powerUpDefault&lt;/code&gt; is the configuration the engine assumes before any
&lt;code&gt;SET_CONFIG&lt;/code&gt; runs: &lt;strong&gt;controller&lt;/strong&gt; role, &lt;strong&gt;x1&lt;/strong&gt; IO width, &lt;strong&gt;20 MHz&lt;/strong&gt;
clock, alerts on the &lt;strong&gt;pin&lt;/strong&gt;. These four are the design’s default posture
— the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;intro&lt;/a&gt;’s “controller only, single IO, one clock rate” v1,
written as a value. &lt;code&gt;revisionWord&lt;/code&gt; is the version stamp &lt;code&gt;0x0001_0000&lt;/code&gt; —
major 0, minor 1, patch 0 — that &lt;code&gt;Preamble&lt;/code&gt; lays down as the first word
of every trace, so a host reading the drain can confirm the gateware
matches the tool talking to it.&lt;/p&gt;
&lt;p&gt;Then &lt;code&gt;initState&lt;/code&gt;, the whole machine at rest:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;initState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;initState &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  State&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; phase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Idle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; pc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; regs&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; initRegs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; cfg&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; powerUpDefault&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxCrc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ringPtr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ovf&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; csN&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; sck&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rstN&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lanes&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; hiZ&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; busPhase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; beatIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; beatTot&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; shifter&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; pending&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; PendNone&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; waitTimer&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read it as the answer to “what is every field before anything happens.”
&lt;code&gt;phase = Idle&lt;/code&gt;, waiting. The pins &lt;strong&gt;safe&lt;/strong&gt;: &lt;code&gt;csN = 1&lt;/code&gt; and &lt;code&gt;rstN = 1&lt;/code&gt;
(both active-low, so both deasserted), &lt;code&gt;sck = 0&lt;/code&gt;, and &lt;code&gt;lanes = hiZ&lt;/code&gt; —
all IO lanes released, driving nothing, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;serialiser&lt;/a&gt;’s name
for “hands off the bus.” The architectural state cleared: &lt;code&gt;pc&lt;/code&gt; at zero,
&lt;code&gt;regs&lt;/code&gt; at &lt;code&gt;initRegs&lt;/code&gt;, &lt;code&gt;cfg&lt;/code&gt; at the power-up default, &lt;code&gt;rxCrc&lt;/code&gt; at zero. And
one field that is &lt;em&gt;not&lt;/em&gt; zero: &lt;code&gt;ringPtr = 1&lt;/code&gt;, not &lt;code&gt;0&lt;/code&gt;, because slot zero
of the ring is reserved for the &lt;code&gt;REVISION&lt;/code&gt; word &lt;code&gt;Preamble&lt;/code&gt; writes —
the pointer starts one past it, at the first slot a program’s own records
may use.&lt;/p&gt;
&lt;p&gt;There is a larger design decision folded into &lt;code&gt;initState&lt;/code&gt; existing at
all, and it is one the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;intro&lt;/a&gt; flagged: &lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/tamal&quot;&gt;Tamal&lt;/a&gt; has &lt;strong&gt;no reset port.&lt;/strong&gt;
The top ties reset permanently deasserted and leans on power-up &lt;code&gt;init&lt;/code&gt;,
the way the sibling &lt;a rel=&quot;external&quot; href=&quot;https://clash-lang.org&quot;&gt;Clash&lt;/a&gt; examples do. So &lt;code&gt;initState&lt;/code&gt; is not what a
reset line loads — there is no reset line — it is what the registers
&lt;em&gt;power up&lt;/em&gt; holding. Which raises the obvious question: if there is no
reset, how does a &lt;em&gt;second&lt;/em&gt; program run after the first one halts? The
answer is the next section.&lt;/p&gt;
&lt;h2 id=&quot;the-four-trivial-phases&quot;&gt;The four trivial phases&lt;/h2&gt;
&lt;p&gt;Four of the eight &lt;code&gt;step*&lt;/code&gt; helpers do so little that we can read them all
here and leave the substantial four — &lt;code&gt;Exec&lt;/code&gt;, and the three bus phases
— for the posts that own them. Start with the two resting states,
&lt;code&gt;Idle&lt;/code&gt; and &lt;code&gt;Halted&lt;/code&gt;, which are almost the same function written twice:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;stepIdle&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;State&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;stepIdle s inp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; startIn inp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;softInit&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;stepHalted&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;State&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;stepHalted s inp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; startIn inp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;softInit&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both say the same thing: &lt;strong&gt;hold until told to start.&lt;/strong&gt; With &lt;code&gt;startIn&lt;/code&gt;
low, each returns the state it was handed, unchanged — &lt;code&gt;(s, busOut s, Nothing)&lt;/code&gt;, the machine standing still, driving its stored pins, writing
nothing. When &lt;code&gt;startIn&lt;/code&gt; goes high, both hand back &lt;code&gt;softInit&lt;/code&gt;, and that is
where a run begins. The only difference between the two is &lt;em&gt;which&lt;/em&gt; pins
they hold while waiting: &lt;code&gt;Idle&lt;/code&gt; holds the safe power-up pins, &lt;code&gt;Halted&lt;/code&gt;
holds the safe pins a halt left behind — but the response to &lt;code&gt;start&lt;/code&gt; is
identical, which is exactly why re-running works.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;softInit&lt;/code&gt; is that response, and it is one line:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;softInit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;softInit &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; initState&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;phase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Preamble&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is &lt;code&gt;initState&lt;/code&gt; — the entire power-up state, every field back to its
rest value — with the one change that the phase is &lt;code&gt;Preamble&lt;/code&gt; rather
than &lt;code&gt;Idle&lt;/code&gt;. So &lt;code&gt;start&lt;/code&gt;, from &lt;code&gt;Idle&lt;/code&gt; &lt;em&gt;or&lt;/em&gt; from &lt;code&gt;Halted&lt;/code&gt;, throws the whole
machine back to power-up and points it at the preamble. This is the
answer to the no-reset question: a second run does not need a reset line
because &lt;code&gt;softInit&lt;/code&gt; &lt;em&gt;is&lt;/em&gt; the reset, performed in the datapath, synchronous
and total. Every architectural field, the ring pointer, the overflow
flag, the config — all of it returns to the same values it powered up
with, which means a re-run is &lt;strong&gt;byte-for-byte identical&lt;/strong&gt; to a first run.
Load a program, run it, halt, pulse &lt;code&gt;start&lt;/code&gt;, and you get the same trace
you got the first time, with no power cycle and no reset pin. The
determinism the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;intro&lt;/a&gt; promised falls out of one record update.&lt;/p&gt;
&lt;p&gt;Then &lt;code&gt;Preamble&lt;/code&gt;, the first cycle of any run:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;stepPreamble&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;State&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;stepPreamble s _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;phase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Fetch&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Ring&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt; revisionWord&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It ignores its input (&lt;code&gt;_&lt;/code&gt;), advances the phase to &lt;code&gt;Fetch&lt;/code&gt;, and — the
one thing it does — emits &lt;code&gt;Just (Ring 0 revisionWord)&lt;/code&gt;: write the
revision stamp to slot &lt;code&gt;0&lt;/code&gt; of the trace ring. That is why &lt;code&gt;ringPtr&lt;/code&gt;
started at &lt;code&gt;1&lt;/code&gt;; slot &lt;code&gt;0&lt;/code&gt; is spoken for, here, on the first cycle, so that
the very first word a host reads back is always the version of the
machine that produced the trace. One cycle, one write, and on to
&lt;code&gt;Fetch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And &lt;code&gt;Fetch&lt;/code&gt; itself, the shortest of all:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;stepFetch&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;State&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BusOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;stepFetch s _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;phase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Exec&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; busOut s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It does &lt;em&gt;nothing&lt;/em&gt; but advance to &lt;code&gt;Exec&lt;/code&gt;. No write, no state change beyond
the phase. And this apparent do-nothing is the one-cycle bubble the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt; warned us to expect. The program counter &lt;code&gt;pc&lt;/code&gt; is offered
to the instruction RAM as &lt;code&gt;pcOut&lt;/code&gt;; the RAM’s read latency means the word
at that address — &lt;code&gt;instrWord&lt;/code&gt; — does not arrive until the &lt;em&gt;next&lt;/em&gt;
cycle. &lt;code&gt;Fetch&lt;/code&gt; is the machine spending that cycle on purpose: it steps to
&lt;code&gt;Exec&lt;/code&gt;, and by the time &lt;code&gt;Exec&lt;/code&gt; runs, &lt;code&gt;instrWord&lt;/code&gt; in its &lt;code&gt;BusIn&lt;/code&gt; is valid
and ready to decode. The bubble is not waste; it is the phase that lets a
synchronous memory be the program store. One cycle of patience, bought
knowingly, exactly as the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories post&lt;/a&gt; said it would be.&lt;/p&gt;
&lt;p&gt;Put the four together and the outer loop of the machine is already
visible: &lt;code&gt;Idle&lt;/code&gt; waits; &lt;code&gt;start&lt;/code&gt; soft-inits to &lt;code&gt;Preamble&lt;/code&gt;; &lt;code&gt;Preamble&lt;/code&gt;
stamps the revision and goes to &lt;code&gt;Fetch&lt;/code&gt;; &lt;code&gt;Fetch&lt;/code&gt; waits a cycle for the
RAM and goes to &lt;code&gt;Exec&lt;/code&gt;. What &lt;code&gt;Exec&lt;/code&gt; does — and how it loops back to
&lt;code&gt;Fetch&lt;/code&gt; for the next instruction, or detours through the bus phases, or
lands in &lt;code&gt;Halted&lt;/code&gt; — is the rest of the engine, and the rest of this
arc. Here is the shape it all hangs on:&lt;/p&gt;
&lt;figure class=&quot;engfsm-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;engfsm&quot; viewBox=&quot;0 -52 800 342&quot; role=&quot;img&quot; aria-labelledby=&quot;engfsm-t engfsm-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;engfsm-t&quot;&gt;The tamal engine lifecycle: eight phases and their transitions&lt;/title&gt;
&lt;desc id=&quot;engfsm-d&quot;&gt;Five phases sit on a horizontal spine: Idle, Preamble, Fetch, Exec, Halted, left to right. A start arrow leads from Idle to Preamble; Preamble writes the REVISION word and goes to Fetch; Fetch, the one-cycle memory bubble, goes to Exec; Exec goes right to Halted on HALT or TRAP. The main cycle is an accent-coloured arc from Exec back to Fetch, labelled op done, PC plus one or PC plus offset. Below Exec a dashed group holds three multi-cycle phases opened in a later post: BusBeat, TraceEmit, and WaitAlert; one arrow leads down from Exec into the group for PUT, GET, TAR, MARK, and WAIT_ON, and one arrow leads back up from the group to Fetch when the op is done. A long arc over the top returns from Halted to Preamble, labelled start, a soft-init re-run.&lt;/desc&gt;
&lt;style&gt;
.engfsm{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.engfsm .box{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.engfsm .boxA{fill:var(--bg-dim);stroke:var(--accent);stroke-width:2.5}
.engfsm .sub{fill:var(--bg-main);stroke:var(--fg-main);stroke-width:1.5}
.engfsm .mbox{fill:none;stroke:var(--fg-dim);stroke-width:1.5;stroke-dasharray:6 5}
.engfsm .wire{stroke:var(--fg-main);stroke-width:2;fill:none}
.engfsm .accw{stroke:var(--accent);stroke-width:2.5;fill:none}
.engfsm .name{fill:var(--fg-main);font-family:var(--mono);font-size:15px}
.engfsm .sub-name{fill:var(--fg-main);font-family:var(--mono);font-size:12.5px}
.engfsm .lab{fill:var(--fg-dim);font-family:var(--sans);font-size:12px}
.engfsm .labA{fill:var(--accent);font-family:var(--sans);font-size:12.5px}
.engfsm .mlab{fill:var(--fg-dim);font-family:var(--mono);font-size:11.5px}
.engfsm .ah{fill:var(--fg-main)}
.engfsm .aha{fill:var(--accent)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;ef-a&quot; markerWidth=&quot;9&quot; markerHeight=&quot;7&quot; refX=&quot;7&quot; refY=&quot;3.5&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L7,3.5 L0,7 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;ef-aa&quot; markerWidth=&quot;9&quot; markerHeight=&quot;7&quot; refX=&quot;7&quot; refY=&quot;3.5&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L7,3.5 L0,7 Z&quot; class=&quot;aha&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;!-- main cycle arc: Exec -&gt; Fetch --&gt;
&lt;path class=&quot;accw&quot; d=&quot;M556,70 C556,30 364,30 364,70&quot; marker-end=&quot;url(#ef-aa)&quot;/&gt;
&lt;text class=&quot;labA&quot; x=&quot;460&quot; y=&quot;24&quot; text-anchor=&quot;middle&quot;&gt;op done: PC + 1 / PC + off&lt;/text&gt;
&lt;!-- spine boxes --&gt;
&lt;rect class=&quot;box&quot; x=&quot;18&quot; y=&quot;70&quot; width=&quot;92&quot; height=&quot;44&quot; rx=&quot;6&quot;/&gt;
&lt;text class=&quot;name&quot; x=&quot;64&quot; y=&quot;97&quot; text-anchor=&quot;middle&quot;&gt;Idle&lt;/text&gt;
&lt;rect class=&quot;box&quot; x=&quot;158&quot; y=&quot;70&quot; width=&quot;92&quot; height=&quot;44&quot; rx=&quot;6&quot;/&gt;
&lt;text class=&quot;name&quot; x=&quot;204&quot; y=&quot;97&quot; text-anchor=&quot;middle&quot;&gt;Preamble&lt;/text&gt;
&lt;rect class=&quot;boxA&quot; x=&quot;318&quot; y=&quot;70&quot; width=&quot;92&quot; height=&quot;44&quot; rx=&quot;6&quot;/&gt;
&lt;text class=&quot;name&quot; x=&quot;364&quot; y=&quot;97&quot; text-anchor=&quot;middle&quot;&gt;Fetch&lt;/text&gt;
&lt;rect class=&quot;boxA&quot; x=&quot;510&quot; y=&quot;70&quot; width=&quot;92&quot; height=&quot;44&quot; rx=&quot;6&quot;/&gt;
&lt;text class=&quot;name&quot; x=&quot;556&quot; y=&quot;97&quot; text-anchor=&quot;middle&quot;&gt;Exec&lt;/text&gt;
&lt;rect class=&quot;box&quot; x=&quot;690&quot; y=&quot;70&quot; width=&quot;92&quot; height=&quot;44&quot; rx=&quot;6&quot;/&gt;
&lt;text class=&quot;name&quot; x=&quot;736&quot; y=&quot;97&quot; text-anchor=&quot;middle&quot;&gt;Halted&lt;/text&gt;
&lt;!-- spine arrows --&gt;
&lt;line class=&quot;wire&quot; x1=&quot;110&quot; y1=&quot;92&quot; x2=&quot;156&quot; y2=&quot;92&quot; marker-end=&quot;url(#ef-a)&quot;/&gt;
&lt;text class=&quot;lab&quot; x=&quot;133&quot; y=&quot;84&quot; text-anchor=&quot;middle&quot;&gt;start&lt;/text&gt;
&lt;line class=&quot;wire&quot; x1=&quot;250&quot; y1=&quot;92&quot; x2=&quot;316&quot; y2=&quot;92&quot; marker-end=&quot;url(#ef-a)&quot;/&gt;
&lt;text class=&quot;lab&quot; x=&quot;283&quot; y=&quot;84&quot; text-anchor=&quot;middle&quot;&gt;REVISION&lt;/text&gt;
&lt;line class=&quot;accw&quot; x1=&quot;410&quot; y1=&quot;92&quot; x2=&quot;508&quot; y2=&quot;92&quot; marker-end=&quot;url(#ef-aa)&quot;/&gt;
&lt;text class=&quot;lab&quot; x=&quot;459&quot; y=&quot;84&quot; text-anchor=&quot;middle&quot;&gt;instr valid&lt;/text&gt;
&lt;line class=&quot;wire&quot; x1=&quot;602&quot; y1=&quot;92&quot; x2=&quot;688&quot; y2=&quot;92&quot; marker-end=&quot;url(#ef-a)&quot;/&gt;
&lt;text class=&quot;lab&quot; x=&quot;645&quot; y=&quot;84&quot; text-anchor=&quot;middle&quot;&gt;HALT / TRAP&lt;/text&gt;
&lt;!-- multi-cycle group --&gt;
&lt;rect class=&quot;mbox&quot; x=&quot;292&quot; y=&quot;190&quot; width=&quot;352&quot; height=&quot;82&quot; rx=&quot;10&quot;/&gt;
&lt;text class=&quot;mlab&quot; x=&quot;302&quot; y=&quot;206&quot; text-anchor=&quot;start&quot;&gt;multi-cycle phases — opened in part 3&lt;/text&gt;
&lt;rect class=&quot;sub&quot; x=&quot;300&quot; y=&quot;214&quot; width=&quot;100&quot; height=&quot;42&quot; rx=&quot;5&quot;/&gt;
&lt;text class=&quot;sub-name&quot; x=&quot;350&quot; y=&quot;240&quot; text-anchor=&quot;middle&quot;&gt;BusBeat&lt;/text&gt;
&lt;rect class=&quot;sub&quot; x=&quot;418&quot; y=&quot;214&quot; width=&quot;104&quot; height=&quot;42&quot; rx=&quot;5&quot;/&gt;
&lt;text class=&quot;sub-name&quot; x=&quot;470&quot; y=&quot;240&quot; text-anchor=&quot;middle&quot;&gt;TraceEmit&lt;/text&gt;
&lt;rect class=&quot;sub&quot; x=&quot;538&quot; y=&quot;214&quot; width=&quot;100&quot; height=&quot;42&quot; rx=&quot;5&quot;/&gt;
&lt;text class=&quot;sub-name&quot; x=&quot;588&quot; y=&quot;240&quot; text-anchor=&quot;middle&quot;&gt;WaitAlert&lt;/text&gt;
&lt;!-- Exec down into group --&gt;
&lt;line class=&quot;wire&quot; x1=&quot;556&quot; y1=&quot;114&quot; x2=&quot;556&quot; y2=&quot;188&quot; marker-end=&quot;url(#ef-a)&quot;/&gt;
&lt;text class=&quot;lab&quot; x=&quot;548&quot; y=&quot;150&quot; text-anchor=&quot;end&quot;&gt;PUT / GET / TAR,&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;548&quot; y=&quot;166&quot; text-anchor=&quot;end&quot;&gt;MARK, WAIT_ON&lt;/text&gt;
&lt;!-- group back up to Fetch --&gt;
&lt;path class=&quot;wire&quot; d=&quot;M360,190 C360,150 364,150 364,116&quot; marker-end=&quot;url(#ef-a)&quot;/&gt;
&lt;text class=&quot;lab&quot; x=&quot;322&quot; y=&quot;150&quot; text-anchor=&quot;end&quot;&gt;done&lt;/text&gt;
&lt;!-- Halted re-run back to Preamble, routed over the top --&gt;
&lt;path class=&quot;wire&quot; d=&quot;M736,70 C736,-46 204,-46 204,70&quot; marker-end=&quot;url(#ef-a)&quot;/&gt;
&lt;text class=&quot;lab&quot; x=&quot;470&quot; y=&quot;-32&quot; text-anchor=&quot;middle&quot;&gt;start — soft-init re-run&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The whole engine as a lifecycle. The spine reads left to right: &lt;code&gt;Idle&lt;/code&gt; waits for &lt;code&gt;start&lt;/code&gt;; &lt;code&gt;Preamble&lt;/code&gt; stamps the &lt;code&gt;REVISION&lt;/code&gt; word and falls to &lt;code&gt;Fetch&lt;/code&gt;; &lt;code&gt;Fetch&lt;/code&gt; spends the one-cycle memory bubble and hands a valid instruction to &lt;code&gt;Exec&lt;/code&gt;. The accent arc is the main loop --- &lt;code&gt;Exec&lt;/code&gt; back to &lt;code&gt;Fetch&lt;/code&gt;, one instruction retired, &lt;code&gt;PC&lt;/code&gt; advanced. The multi-cycle instructions detour through the dashed group below (&lt;code&gt;BusBeat&lt;/code&gt;, &lt;code&gt;TraceEmit&lt;/code&gt;, &lt;code&gt;WaitAlert&lt;/code&gt;) and return to &lt;code&gt;Fetch&lt;/code&gt; when they finish; those three phases are the subject of a later post. &lt;code&gt;HALT&lt;/code&gt; or a trap leaves the spine for &lt;code&gt;Halted&lt;/code&gt;, and a fresh &lt;code&gt;start&lt;/code&gt; soft-inits all the way back to &lt;code&gt;Preamble&lt;/code&gt; for a byte-identical re-run. This post read the spine and the two resting states; the arc through &lt;code&gt;Exec&lt;/code&gt; and the detour through the group are what remains.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;what-we-read&quot;&gt;What we read&lt;/h2&gt;
&lt;p&gt;The last door in the series, opened just wide enough to see its plan. Not
the way we opened a leaf — whole, in a screenful — but the way you
open a machine too big to hold at once: shape first, rooms shut. We read
the widest export list yet and found six of its eleven names were
&lt;em&gt;types&lt;/em&gt;, because an engine assembled one level up has to publish the
shape of its own plugs. We read eight imports and opened only the one we
already knew, holding the other seven as named boxes — decode, ALU,
branch, registers, serialiser, config — to be spent now and opened
later, the exact inversion of the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;UART&lt;/a&gt;’s leaves-first climb.&lt;/p&gt;
&lt;p&gt;We read the one line that is the whole machine: &lt;code&gt;step&lt;/code&gt;, a pure Mealy
transition from state-and-input to state-and-pins-and-maybe-a-write,
routing each cycle to one of eight phase helpers and computing nothing
itself. We read the spine those helpers hang on — eight phases, &lt;code&gt;Idle&lt;/code&gt;
through &lt;code&gt;Halted&lt;/code&gt; — and the seventeen-field &lt;code&gt;State&lt;/code&gt; they thread, in its
six groups, its pins deliberately &lt;em&gt;stored&lt;/em&gt; so that &lt;code&gt;busOut&lt;/code&gt; can be a dull
and honest projection rather than a computation. We read the boundary
types that plug into the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-mem/&quot;&gt;memories&lt;/a&gt; and the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader&lt;/a&gt;, and
saw the one-cycle fetch latency reappear as a phase spent on purpose. And
we read the four trivial helpers: two resting states that hold until
&lt;code&gt;start&lt;/code&gt;, a preamble that stamps a revision, and a fetch that waits a beat
for a block RAM — and, hiding in &lt;code&gt;softInit&lt;/code&gt;, the reset that a machine
with no reset port performs in its own datapath, byte-for-byte repeatable.&lt;/p&gt;
&lt;p&gt;What we did &lt;em&gt;not&lt;/em&gt; read is the machine’s actual work. &lt;code&gt;Exec&lt;/code&gt; is still a
name in a &lt;code&gt;case&lt;/code&gt;; the seven boxes are still shut; the bus scratch fields
have been named and not used. That is on purpose. The shape is drawn now
— the loop from &lt;code&gt;Fetch&lt;/code&gt; to &lt;code&gt;Exec&lt;/code&gt; and back, the detours below it, the
exits to &lt;code&gt;Halted&lt;/code&gt; — and against that shape the work will make sense.&lt;/p&gt;
&lt;p&gt;Next we stand in &lt;code&gt;Exec&lt;/code&gt; and watch a single instruction go by: one word,
one cycle, decoded and dispatched — the DATA compute, the branch and
its PC arithmetic, the pin and config pokes — with the leaves it calls
still treated as the black boxes we promised to open after. The map is
drawn. Now we walk the first road on it.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-stepm&quot;&gt;
&lt;p&gt;The adapter is the four lines the &lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/tamal&quot;&gt;PLAN&lt;/a&gt; calls &lt;code&gt;stepM&lt;/code&gt;:
&lt;code&gt;stepM s i = let (s&#39;, bo, mr) = step s i in (s&#39;, (bo, mr))&lt;/code&gt;, and then
&lt;code&gt;engine = mealy stepM initState&lt;/code&gt;. All it does is glue the second and
third elements of &lt;code&gt;step&lt;/code&gt;’s output into one pair, because &lt;code&gt;mealy&lt;/code&gt; wants a
single output value, not two. Splitting them in &lt;code&gt;step&lt;/code&gt; keeps the pure
function readable — pins here, trace write there — and paying a
four-line tax at the one place it is lifted is cheaper than blurring the
two everywhere they are produced. It is the same “keep the core pure, let
the shell adapt” seam the whole design is built on. &lt;a href=&quot;#fr-stepm-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-nfdatax&quot;&gt;
&lt;p&gt;&lt;code&gt;NFDataX&lt;/code&gt; is &lt;a rel=&quot;external&quot; href=&quot;https://clash-lang.org&quot;&gt;Clash&lt;/a&gt;’s “can be stored in a register” class —
roughly, a type whose values have a normal form Clash can reason about,
including how they behave when partly undefined at power-up. Every type
that lands in a flip-flop needs it: &lt;code&gt;Phase&lt;/code&gt;, &lt;code&gt;Pending&lt;/code&gt;, &lt;code&gt;State&lt;/code&gt;, all of
them. It is derived &lt;code&gt;anyclass&lt;/code&gt; — the compiler writes the instance from
the type’s structure — while &lt;code&gt;Generic&lt;/code&gt;, &lt;code&gt;Show&lt;/code&gt;, and &lt;code&gt;Eq&lt;/code&gt; are derived
&lt;code&gt;stock&lt;/code&gt;, the built-in strategy. The split into two &lt;code&gt;deriving&lt;/code&gt; clauses is
just Haskell keeping the two derivation mechanisms apart; the four
classes together are the standard kit every stateful &lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/tamal&quot;&gt;Tamal&lt;/a&gt; block wears. &lt;a href=&quot;#fr-nfdatax-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-moore&quot;&gt;
&lt;p&gt;In the strict vocabulary this makes the &lt;em&gt;pins&lt;/em&gt; a Moore output —
a function of the state alone, &lt;code&gt;busOut s&lt;/code&gt;, with no path from this cycle’s
&lt;code&gt;BusIn&lt;/code&gt; to this cycle’s pins — while the next state and the &lt;code&gt;Maybe Ring&lt;/code&gt;
write remain Mealy, since they do depend on the input. Driving the pins
from stored state rather than combinationally from the input is what
keeps them glitch-free: they change only on a clock edge, when the state
updates, never in the middle of a cycle because an input wiggled. &lt;a href=&quot;#fr-moore-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Tamal: Four Lines and Three Promises</title>
    <published>2026-07-29T09:00:00+00:00</published>
    <updated>2026-07-29T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-mem/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-mem/</id>
    <summary type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader&lt;/a&gt; spent its entire length driving two memories. It
wrote instruction words into one of them, four bytes at a time, straight
through as they came off the wire. It swept the other one out after
&lt;code&gt;HALT&lt;/code&gt; in seven patient phases, dancing with a one-cycle read latency
that it never stopped to explain. Both memories were named, used, and
leaned on — and neither was ever opened.&lt;/p&gt;
&lt;p&gt;This post opens them. &lt;code&gt;Tamal.Mem&lt;/code&gt; is forty-one lines including the
licence header and the doc-comments, and the part that becomes gateware
is four. You can read it in less time than it takes to read this
paragraph, and you would be forgiven for wondering why it gets a post at
all.&lt;/p&gt;
&lt;p&gt;It gets one because the four lines are not the module. The module is a
&lt;strong&gt;contract&lt;/strong&gt; — a set of promises about latency, about ordering, and
about what a memory says before it has been told anything — and the
four lines are only where those promises get pinned to a width and given
a name. The file that holds them down is a hundred and forty-seven lines
of tests, three and a half times the module it checks. That ratio is not
an over-test. It is the honest shape of a module whose entire content is
contract.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader&lt;/a&gt; spent its entire length driving two memories. It
wrote instruction words into one of them, four bytes at a time, straight
through as they came off the wire. It swept the other one out after
&lt;code&gt;HALT&lt;/code&gt; in seven patient phases, dancing with a one-cycle read latency
that it never stopped to explain. Both memories were named, used, and
leaned on — and neither was ever opened.&lt;/p&gt;
&lt;p&gt;This post opens them. &lt;code&gt;Tamal.Mem&lt;/code&gt; is forty-one lines including the
licence header and the doc-comments, and the part that becomes gateware
is four. You can read it in less time than it takes to read this
paragraph, and you would be forgiven for wondering why it gets a post at
all.&lt;/p&gt;
&lt;p&gt;It gets one because the four lines are not the module. The module is a
&lt;strong&gt;contract&lt;/strong&gt; — a set of promises about latency, about ordering, and
about what a memory says before it has been told anything — and the
four lines are only where those promises get pinned to a width and given
a name. The file that holds them down is a hundred and forty-seven lines
of tests, three and a half times the module it checks. That ratio is not
an over-test. It is the honest shape of a module whose entire content is
contract.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;the-entire-source&quot;&gt;The entire source&lt;/h2&gt;
&lt;p&gt;Minus the SPDX header and the doc-comments:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Mem&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; instrRam&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ringRam&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Clash.Prelude&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Params&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;AW&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RW&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;instrRam&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  forall&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;HiddenClockResetEnable&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; AW&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; AW&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;instrRam &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; blockRamPow2 &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;repeat &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;ringRam&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;HiddenClockResetEnable&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RW&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RW&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ringRam &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; blockRamPow2 &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;repeat &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two signatures, two identical bodies, one import that is a pair of type
aliases. The instruction store and the trace ring — the two halves of
the machine’s memory, one that the engine reads from and one that it
writes to — are the same expression twice, differing only in the width
of the number you address them with.&lt;/p&gt;
&lt;h2 id=&quot;what-isn-t-here&quot;&gt;What isn’t here&lt;/h2&gt;
&lt;p&gt;There is no &lt;code&gt;data&lt;/code&gt; declaration. There is no state record, no &lt;code&gt;where&lt;/code&gt;
clause, no &lt;code&gt;mealy&lt;/code&gt;. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; computed a residue; the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader/&quot;&gt;loader&lt;/a&gt; remembered where it stood in a frame. Nothing in this
file computes and nothing in this file remembers.&lt;/p&gt;
&lt;p&gt;That is worth sitting with, because it makes &lt;code&gt;Tamal.Mem&lt;/code&gt; the second kind
of nothing the series has met. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;UART top&lt;/a&gt; was the first: a
module with no behaviour of its own, whose whole content was
&lt;em&gt;composition&lt;/em&gt; — three blocks that each own an idea, wired into one
block that owns none. &lt;code&gt;Tamal.Mem&lt;/code&gt; does not even compose. It applies a
library function to a constant, twice. Its content is neither
computation nor wiring but &lt;strong&gt;naming&lt;/strong&gt;: it takes a primitive that already
exists, fixes the two parameters that make it &lt;em&gt;this machine’s&lt;/em&gt; memory,
and gives the result a name that the rest of the design can say out
loud.&lt;/p&gt;
&lt;p&gt;So if the code is four lines, what is the module for? The answer is the
rest of this post, and it comes in two halves that say the same thing
twice — once in English, once in a language a machine can check.&lt;/p&gt;
&lt;h2 id=&quot;one-decision-made-once-and-reused&quot;&gt;One decision, made once and reused&lt;/h2&gt;
&lt;p&gt;There is exactly one engineering decision in the file, and it is
&lt;em&gt;which primitive&lt;/em&gt;, not &lt;em&gt;which code&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://clash-lang.org&quot;&gt;Clash&lt;/a&gt; offers several block-RAM shapes. &lt;code&gt;blockRam&lt;/code&gt; takes an
&lt;code&gt;Enum&lt;/code&gt;-addressed vector and wants an &lt;code&gt;Index n&lt;/code&gt;. &lt;code&gt;blockRamU&lt;/code&gt; starts with
undefined contents; &lt;code&gt;blockRam1&lt;/code&gt; starts with one repeated value.
&lt;code&gt;blockRamPow2&lt;/code&gt; takes a &lt;code&gt;Vec (2^n)&lt;/code&gt; and addresses it with an
&lt;code&gt;Unsigned n&lt;/code&gt;. The engine already carries its program counter as an
&lt;code&gt;Unsigned AW&lt;/code&gt; and its ring pointer as an &lt;code&gt;Unsigned RW&lt;/code&gt;, and both depths
are exact powers of two, so &lt;code&gt;blockRamPow2&lt;/code&gt; maps onto them &lt;strong&gt;one to one&lt;/strong&gt;
— no &lt;code&gt;Unsigned&lt;/code&gt;-to-&lt;code&gt;Index&lt;/code&gt; conversion at the call site, and no
out-of-range case to guard, because at these widths there are no
out-of-range addresses to have — the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt;’s point about widths
living in the type, cashed in as a branch that does not need to
exist.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-reset-1&quot;&gt;&lt;a href=&quot;#fn-reset&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The initial contents, &lt;code&gt;repeat 0&lt;/code&gt;, do double duty. On hardware Clash
lowers the vector into the block RAM’s &lt;code&gt;INIT&lt;/code&gt; strings, which is what
makes Vivado infer a real BRAM rather than scattering the array across
LUTs. In simulation it means every address is &lt;em&gt;defined&lt;/em&gt; from the first
cycle onward, which is what makes randomised testing possible at all —
a generator that reads an address nobody has written yet gets a zero
rather than an exception.&lt;/p&gt;
&lt;p&gt;The widths themselves live somewhere else. &lt;code&gt;AW&lt;/code&gt; and &lt;code&gt;RW&lt;/code&gt; come from
&lt;code&gt;Tamal.Params&lt;/code&gt;, a leaf that imports nothing but &lt;code&gt;Clash.Prelude&lt;/code&gt; and
exists so that the engine, the memories, the loader, and the trace model
can all name the same number without importing one another. Resizing the
instruction space becomes one edit in one file instead of a hunt for
hand-copied &lt;code&gt;Unsigned 10&lt;/code&gt; literals.&lt;/p&gt;
&lt;p&gt;The write port is the last piece of the decision, and the most
deliberate. The engine emits its trace writes as &lt;code&gt;Maybe Ring&lt;/code&gt;, a proper
record with named fields. The obvious signature would take that type.
This one takes a bare &lt;code&gt;Maybe (Unsigned n, BitVector 32)&lt;/code&gt; instead —
because accepting &lt;code&gt;Ring&lt;/code&gt; would mean importing &lt;code&gt;Tamal.Engine&lt;/code&gt;, and a
memory that imports the engine is a memory that knows what a trace
record is. The unpacking is one line, and it lives up in the topEntity
where the two are already being wired together:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;ringWrite&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Ring&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RW&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ringWrite &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; fmap &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;\&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Ring&lt;/span&gt;&lt;span&gt; a d&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; d&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One &lt;code&gt;fmap&lt;/code&gt; is the entire price of keeping the memory ignorant. It is the
same discipline that produced &lt;code&gt;Tamal.RegFile&lt;/code&gt;, built before &lt;code&gt;Engine.step&lt;/code&gt;
existed to use it and never importing the engine whose registers it
holds. We have not opened that one yet — it is waiting for a later post
— but by the time we do, the shape will be familiar.&lt;/p&gt;
&lt;p&gt;What falls out of the two widths is the geometry:&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Memory&lt;/th&gt;&lt;th&gt;Depth&lt;/th&gt;&lt;th&gt;Address&lt;/th&gt;&lt;th&gt;Data&lt;/th&gt;&lt;th&gt;≈ BRAM36&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;instruction&lt;/td&gt;&lt;td&gt;1024 = 2^10&lt;/td&gt;&lt;td&gt;&lt;code&gt;Unsigned AW&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;BitVector 32&lt;/code&gt;&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;trace ring&lt;/td&gt;&lt;td&gt;4096 = 2^12&lt;/td&gt;&lt;td&gt;&lt;code&gt;Unsigned RW&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;BitVector 32&lt;/code&gt;&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Five block RAMs out of the 135 an xc7a100t carries, which is a rounding
error. And one small gift: with the ring at 4096 words, the engine’s
reserved terminator slot at &lt;code&gt;maxBound :: Unsigned RW&lt;/code&gt; is exactly the last
address that exists. &lt;code&gt;termAddr = maxBound&lt;/code&gt; and &lt;code&gt;termAddr = D - 1&lt;/code&gt; are the
same constant, so choosing the full width of the address type made the
overflow-proof terminator free. What that slot is &lt;em&gt;for&lt;/em&gt; is the engine’s
business, and stays behind its door.&lt;/p&gt;
&lt;h2 id=&quot;three-promises&quot;&gt;Three promises&lt;/h2&gt;
&lt;p&gt;Here is the part that actually matters. &lt;code&gt;blockRamPow2&lt;/code&gt; comes with three
semantics, and &lt;code&gt;Tamal.Mem&lt;/code&gt; implements none of them — it &lt;strong&gt;inherits&lt;/strong&gt;
all three, and its contribution is to pin them at a width where the rest
of the design can rely on them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One cycle, not zero and not two.&lt;/strong&gt; The output at cycle &lt;em&gt;t&lt;/em&gt; is the value
read at cycle &lt;em&gt;t-1&lt;/em&gt;. Write &lt;code&gt;0xDEAD_BEEF&lt;/code&gt; to address 5 on cycle 0 and read
address 5 from then on:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;let&lt;/span&gt;&lt;span&gt; addrs    &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 5&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 5&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 5&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    writes   &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;5&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0xDEAD_BEEF&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    expected &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0xDEAD_BEEF&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0xDEAD_BEEF&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0xDEAD_BEEF&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The value appears one output later than the read that asked for it, and
then stays. The leading zero is the load-bearing part of that
expectation: it is the cycle-0 read of address 0, surfacing on cycle 1
while the write to address 5 is still in flight. Drop it and the very
same test would pass against a memory with no latency at all, because
everything after it would still line up. Pinning a delay means pinning
where the trace &lt;em&gt;starts&lt;/em&gt;, not only what it settles to.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reads see the memory as it was.&lt;/strong&gt; Read and write the same address in
the same cycle and the read wins; the new value shows up on the cycle
after. Write &lt;code&gt;0x1111&lt;/code&gt; to address 3, then on the very next cycle write
&lt;code&gt;0x2222&lt;/code&gt; to address 3 while reading it:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;simInstr&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 3&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x1111&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x2222&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  @?=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x1111&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x2222&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x2222&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This one is not a convention that Clash picked and could change its mind
about. It is a fact about the silicon.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-rbw-1&quot;&gt;&lt;a href=&quot;#fn-rbw&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The first output is a lie you must not read.&lt;/strong&gt; At cycle 0 the memory
has not yet been asked anything, and &lt;code&gt;blockRamPow2&lt;/code&gt; reports
&lt;code&gt;deepErrorX&lt;/code&gt; — an undefined value that explodes if you force it. Every
sampler in the test file therefore drops sample 0 before comparing
anything. That is not defensive tidying; it is the third promise, and
ignoring it is how you get a test suite that fails with a stack trace
instead of a diff.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-outzero-1&quot;&gt;&lt;a href=&quot;#fn-outzero&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;None of those three sentences is about code that lives in &lt;code&gt;Tamal.Mem&lt;/code&gt;.
All three are about code that lives in &lt;code&gt;Clash.Prelude&lt;/code&gt;. What
&lt;code&gt;Tamal.Mem&lt;/code&gt; does is make them &lt;em&gt;this machine’s&lt;/em&gt; promises, at 10 bits and
at 12 bits, so that the loader and the engine can be written against
them.&lt;/p&gt;
&lt;h2 id=&quot;the-same-three-promises-in-haskell&quot;&gt;The same three promises, in Haskell&lt;/h2&gt;
&lt;p&gt;Prose promises do not fail a build. So the test file states the same
three things again, as an executable oracle — a pure model of a block
RAM in eight lines of ordinary &lt;a rel=&quot;external&quot; href=&quot;https://www.haskell.org/&quot;&gt;Haskell&lt;/a&gt;, with no &lt;code&gt;Signal&lt;/code&gt; and no clock
anywhere in sight:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;refRam&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;KnownNat&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; n&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; n&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; n&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;refRam addrs writes &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; go &lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;]&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;L.&lt;/span&gt;&lt;span&gt;zip addrs writes&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  go _ &lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  go mem &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; w&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; :&lt;/span&gt;&lt;span&gt; zs&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; fromMaybe &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;L.&lt;/span&gt;&lt;span&gt;lookup a mem&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; :&lt;/span&gt;&lt;span&gt; go &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;push w mem&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; zs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  push &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt; m &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; m&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  push &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;wa&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; wd&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; m &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;wa&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; wd&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; :&lt;/span&gt;&lt;span&gt; m&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read it against the list above and the three promises are all sitting
there, one per detail.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;fromMaybe 0&lt;/code&gt; is the &lt;strong&gt;zero-initialisation&lt;/strong&gt;: an address that appears in
no prior write reads as zero, exactly as &lt;code&gt;repeat 0&lt;/code&gt; guarantees on the
hardware side.&lt;/p&gt;
&lt;p&gt;The order of the two expressions in &lt;code&gt;go&lt;/code&gt; is the &lt;strong&gt;read-before-write&lt;/strong&gt;
rule. The cons cell emits &lt;code&gt;L.lookup a mem&lt;/code&gt; — a lookup in the memory as
it stands &lt;em&gt;before&lt;/em&gt; this cycle — and only then recurses on &lt;code&gt;push w mem&lt;/code&gt;,
the memory with this cycle’s write applied. Swap those and the model
would describe a write-first RAM, and every collision test would fail.
The rule is not commented; it is structural, which is the better place
for it.&lt;/p&gt;
&lt;p&gt;The shape of &lt;code&gt;go&lt;/code&gt; is the &lt;strong&gt;one-cycle latency&lt;/strong&gt;. Each step consumes one
&lt;code&gt;(address, write)&lt;/code&gt; pair and produces one output, but the output it
produces is the read taken before that step’s write landed — so the
list &lt;code&gt;refRam&lt;/code&gt; returns is &lt;code&gt;[out 1, out 2, ...]&lt;/code&gt;, offset by exactly one
from the stimulus that produced it. The undefined &lt;code&gt;out 0&lt;/code&gt; is not in the
list because the model never had a reason to invent it.&lt;/p&gt;
&lt;p&gt;Two smaller things earn their place. The assoc list is kept
most-recent-first, so &lt;code&gt;L.lookup&lt;/code&gt; — which returns the first match —
naturally yields the newest write to an address, and a slot that has been
written five times reads back the fifth value with no bookkeeping. And
the whole thing is polymorphic in the address width &lt;code&gt;n&lt;/code&gt;, so a single
oracle serves a 1024-word instruction store and a 4096-word ring without
a line of duplication. One model, both memories.&lt;/p&gt;
&lt;h2 id=&quot;getting-a-clock-into-a-test&quot;&gt;Getting a clock into a test&lt;/h2&gt;
&lt;p&gt;The oracle is pure; the thing it is checked against is not. Bridging them
takes a sampler:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;simInstr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 10&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;simInstr addrs writes &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  L.&lt;/span&gt;&lt;span&gt;drop &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    $&lt;/span&gt;&lt;span&gt; sampleN&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;L.&lt;/span&gt;&lt;span&gt;length addrs &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      (&lt;/span&gt;&lt;span&gt; instrRam &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fromList &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;addrs &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;repeat &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;fromList &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;writes &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;repeat &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;          Signal&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Dom100&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three things are load-bearing here, and each of them is the kind of
detail that costs an afternoon the first time.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sampleN&lt;/code&gt; is what supplies the clock, reset, and enable that
&lt;code&gt;HiddenClockResetEnable&lt;/code&gt; is asking for. Crucially, the RAM has to be
applied &lt;strong&gt;directly inside&lt;/strong&gt; &lt;code&gt;sampleN&lt;/code&gt;’s argument. You cannot factor
&lt;code&gt;instrRam&lt;/code&gt; out into a parameter and write one generic sampler for both
memories, because the constraint can only be discharged inside that
rank-2 position. Hence two nearly identical samplers — &lt;code&gt;simInstr&lt;/code&gt; here,
&lt;code&gt;simRing&lt;/code&gt; the same shape at width 12 — rather than one clever one. The
duplication is not a failure of imagination; it is where the type system
draws the line.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;fromList (addrs &amp;lt;&amp;gt; L.repeat 0)&lt;/code&gt; makes the stimulus infinite. A &lt;code&gt;Signal&lt;/code&gt;
has no end, so a finite input list would run out and force an exception
on some cycle you never meant to look at. Padding with a harmless tail
means the signal is total and only the prefix is ever compared.&lt;/p&gt;
&lt;p&gt;And &lt;code&gt;sampleN (n + 1)&lt;/code&gt; followed by &lt;code&gt;L.drop 1&lt;/code&gt; is the third promise,
operationalised. Take one more sample than you have stimulus for, throw
the undefined one away, and what remains is &lt;code&gt;[out 1 .. out n]&lt;/code&gt; — the
same length as &lt;code&gt;refRam&lt;/code&gt;’s output, index-aligned with it. That alignment
is what earns the right to write &lt;code&gt;===&lt;/code&gt; between them. Get it wrong by one
and the property still typechecks; it just compares the wrong cycles and
tells you a lie.&lt;/p&gt;
&lt;h2 id=&quot;what-the-properties-buy&quot;&gt;What the properties buy&lt;/h2&gt;
&lt;p&gt;With a model and a sampler, the &lt;a rel=&quot;external&quot; href=&quot;https://hedgehog.qa&quot;&gt;hedgehog&lt;/a&gt; properties are short. The main
one feeds random interleaved sequences of read addresses and optional
writes to both, at both widths, and demands they agree. Addresses are
drawn from a deliberately tiny window — 0 to 15 — so that random
reads frequently land on addresses that were recently written. A
generator ranging over all 4096 ring slots would produce a beautiful
stream of zeros and test nothing; the small window is what makes
collisions and read-after-write hits common enough to matter.&lt;/p&gt;
&lt;p&gt;The second property is more interesting, because it declines to reuse
&lt;code&gt;refRam&lt;/code&gt;. It writes a random pile of values into the window, sweeps the
whole window, and checks the result against a &lt;em&gt;different&lt;/em&gt; oracle
computed on the spot — a lookup over the reversed write list:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;oracle &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; fmap &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;\&lt;/span&gt;&lt;span&gt;a &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; fromMaybe &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;L.&lt;/span&gt;&lt;span&gt;lookup a &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;L.&lt;/span&gt;&lt;span&gt;reverse ws&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; win&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same answer, arrived at independently: last write wins, everything else
is zero. Checking &lt;code&gt;refRam&lt;/code&gt; against the hardware proves they agree, which
is worth a lot — but two implementations that share an idea can share a
misunderstanding of it. A second oracle written a different way is a
second opinion. It is also the same two-implementations habit the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader-cobs/&quot;&gt;COBS&lt;/a&gt; post named as the house style, applied to a model rather
than a codec.&lt;/p&gt;
&lt;h2 id=&quot;why-test-a-primitive-you-didn-t-write&quot;&gt;Why test a primitive you didn’t write&lt;/h2&gt;
&lt;p&gt;The obvious objection is that all of this tests Clash. It does not.
&lt;code&gt;blockRamPow2&lt;/code&gt; is correct, and its correctness was never the question.&lt;/p&gt;
&lt;p&gt;What is under test is &lt;strong&gt;the reading&lt;/strong&gt; of it — the three promises, at
these two widths, in this domain, written down somewhere that fails
loudly rather than somewhere that fails quietly. If a Clash release ever
changes the collision behaviour, one unit test goes red with a two-value
diff instead of the engine mysteriously fetching a stale instruction on
one path in one program. If someone swaps &lt;code&gt;blockRamPow2&lt;/code&gt; for &lt;code&gt;blockRamU&lt;/code&gt;
to save an &lt;code&gt;INIT&lt;/code&gt; string, the zero-initialisation property catches it
immediately, rather than the loader catching it three modules later as an
intermittent bad word after power-up.&lt;/p&gt;
&lt;p&gt;That is why a hundred and forty-seven lines of test sit on top of four
lines of module without any embarrassment. The four lines are not the
deliverable. The contract is the deliverable, and the tests are the only
form in which a contract can be enforced rather than merely intended.&lt;/p&gt;
&lt;h2 id=&quot;the-door&quot;&gt;The door&lt;/h2&gt;
&lt;p&gt;There is one promise left to spend.&lt;/p&gt;
&lt;p&gt;The one-cycle read latency has been, for this entire post, a nuisance —
something to drop a sample for, to align a list around, to pin with a
test so it does not drift. On the other side of the door it stops being a
nuisance and becomes a design.&lt;/p&gt;
&lt;p&gt;The engine’s program counter is a register inside its state, projected
out as &lt;code&gt;pcOut&lt;/code&gt;. That signal feeds &lt;code&gt;instrRam&lt;/code&gt;, whose output feeds the
instruction word back into the engine, which computes the next PC. A
loop — but a loop containing &lt;em&gt;two&lt;/em&gt; registers, the PC and the block
RAM’s own output register, so there is no combinational path anywhere in
it and nothing for a synthesiser to complain about. The memory’s one-cycle
delay does not have to be worked around, hidden, or paid for out of the
clock period. It is absorbed whole, as a phase, and the engine’s own
source calls that phase by name: &lt;code&gt;Fetch&lt;/code&gt; is documented as &lt;em&gt;the 1-cycle
instruction-BRAM bubble&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The cost this post spent eight sections testing around is, one level up,
a pipeline stage the engine spends on purpose.&lt;/p&gt;
&lt;p&gt;Both memories are known quantities now. The instruction store holds 1024
words and hands back what you asked for one cycle later. The ring holds
4096, the top one reserved, and behaves identically. Neither knows what a
program is, what a trace record means, or which of its neighbours is
writing. They are four lines and three promises, and everything above
them is now allowed to depend on that.&lt;/p&gt;
&lt;p&gt;Which leaves the engine. We have circled it since the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;first post&lt;/a&gt;
— the machine the loader loads, triggers, and drains; the thing that
turns the words in one memory into the records in the other. It is the
last door in the series, and there is nothing left standing in front of
it.&lt;/p&gt;
&lt;p&gt;Next, we walk through.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-reset&quot;&gt;
&lt;p&gt;&lt;code&gt;blockRam&lt;/code&gt; uses only the clock and the enable — it has no
content reset, and cannot have one, because a block RAM’s array is not
resettable silicon. The wrappers still carry the broader
&lt;code&gt;HiddenClockResetEnable&lt;/code&gt; constraint rather than the precise
&lt;code&gt;(HiddenClock dom, HiddenEnable dom)&lt;/code&gt;, which is technically looser than
necessary; GHC simply discharges the two it needs and ignores the third.
Uniformity with every other block in the codebase won that trade, and it
costs nothing here, because &lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/tamal&quot;&gt;Tamal&lt;/a&gt;’s top has no reset port anyway — the
no-reset power-up design is deliberate, and a memory that cannot be reset
fits it exactly rather than fighting it. &lt;a href=&quot;#fr-reset-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-rbw&quot;&gt;
&lt;p&gt;A synchronous RAM latches the read address at the clock edge, and
the array is read before that same edge’s write commits. Xilinx names the
three possible port behaviours &lt;code&gt;READ_FIRST&lt;/code&gt;, &lt;code&gt;WRITE_FIRST&lt;/code&gt;, and
&lt;code&gt;NO_CHANGE&lt;/code&gt;; Clash’s &lt;code&gt;blockRam&lt;/code&gt; models &lt;code&gt;READ_FIRST&lt;/code&gt;. That makes
read-before-write a property of the hardware you are going to get, not a
convention you are free to choose — which is precisely the sort of
thing that belongs in an assertion rather than a comment. A comment
describing the behaviour of silicon is a comment that will one day be
describing the behaviour of &lt;em&gt;different&lt;/em&gt; silicon. &lt;a href=&quot;#fr-rbw-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-outzero&quot;&gt;
&lt;p&gt;&lt;code&gt;deepErrorX&lt;/code&gt; is &lt;a rel=&quot;external&quot; href=&quot;https://clash-lang.org&quot;&gt;Clash&lt;/a&gt; declining to lie. A real block RAM’s
output register powers up holding whatever it powers up holding, and
there is no honest 32-bit value to report for the cycle before the first
read completes — so Clash reports a value that is fine to carry around
and fatal to inspect. This is why Clash’s own &lt;code&gt;blockRam&lt;/code&gt; doctests begin
with &lt;code&gt;L.tail&lt;/code&gt;, and why &lt;code&gt;L.drop 1&lt;/code&gt; appears in both samplers here. A test
that forces sample 0 has asked the simulator a question about hardware
that has not happened yet, and deserves the exception it gets. &lt;a href=&quot;#fr-outzero-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Agentic Gallo: Introducing gallo-mcp</title>
    <published>2026-07-28T20:00:00+00:00</published>
    <updated>2026-07-28T20:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/gallo-mcp-introducing/" type="text/html"/>
    <id>https://balbi.sh/posts/gallo-mcp-introducing/</id>
    <summary type="html">&lt;p&gt;&lt;a href=&quot;/pico-de-gallo/&quot;&gt;Pico de Gallo&lt;/a&gt; turns an RP2350 into a USB-attached bridge that
lets a host program drive real I²C, SPI, UART, GPIO, PWM, ADC, and
1-Wire straight from your laptop — so you can write and test device
drivers in &lt;code&gt;std&lt;/code&gt; Rust, C, or Python instead of cross-compiling for an
MCU every time. Today there’s a new way to reach that hardware, and it
isn’t for you: &lt;a rel=&quot;external&quot; href=&quot;https://crates.io/crates/gallo-mcp&quot;&gt;&lt;code&gt;gallo-mcp&lt;/code&gt;&lt;/a&gt; is a &lt;a rel=&quot;external&quot; href=&quot;https://modelcontextprotocol.io/&quot;&gt;Model Context Protocol&lt;/a&gt;
server that hands the board to the AI agent writing your driver.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;&lt;a href=&quot;/pico-de-gallo/&quot;&gt;Pico de Gallo&lt;/a&gt; turns an RP2350 into a USB-attached bridge that
lets a host program drive real I²C, SPI, UART, GPIO, PWM, ADC, and
1-Wire straight from your laptop — so you can write and test device
drivers in &lt;code&gt;std&lt;/code&gt; Rust, C, or Python instead of cross-compiling for an
MCU every time. Today there’s a new way to reach that hardware, and it
isn’t for you: &lt;a rel=&quot;external&quot; href=&quot;https://crates.io/crates/gallo-mcp&quot;&gt;&lt;code&gt;gallo-mcp&lt;/code&gt;&lt;/a&gt; is a &lt;a rel=&quot;external&quot; href=&quot;https://modelcontextprotocol.io/&quot;&gt;Model Context Protocol&lt;/a&gt;
server that hands the board to the AI agent writing your driver.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;one-tool-per-peripheral-over-stdio&quot;&gt;One tool per peripheral, over stdio&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;gallo-mcp&lt;/code&gt; runs an MCP server over stdio, wraps
&lt;a rel=&quot;external&quot; href=&quot;https://crates.io/crates/pico-de-gallo-lib&quot;&gt;&lt;code&gt;pico-de-gallo-lib&lt;/code&gt;&lt;/a&gt;, and exposes one tool for every peripheral
operation across all seven buses. Point an MCP-capable agent at it and
the model can probe and drive real silicon through the same board the
&lt;code&gt;gallo&lt;/code&gt; CLI talks to — read a sensor register, scan a bus, toggle a
pin — while it’s writing the driver, without cross-compiling or
flashing anything.&lt;/p&gt;
&lt;p&gt;That closes a loop that used to need a human in the middle. Instead of
you running &lt;code&gt;gallo i2c scan&lt;/code&gt;, pasting the output into the chat, and
asking “now what?”, the agent can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;explore an unfamiliar device interactively,&lt;/li&gt;
&lt;li&gt;generate register-level driver code and validate it against the
actual chip, and&lt;/li&gt;
&lt;li&gt;turn “what does this chip return?” into a tool call it makes itself.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;wiring-it-into-a-client&quot;&gt;Wiring it into a client&lt;/h2&gt;
&lt;p&gt;Install the server:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; cargo install gallo-mcp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;gallo-mcp&lt;/code&gt; speaks MCP on &lt;strong&gt;stdout&lt;/strong&gt; and logs to &lt;strong&gt;stderr&lt;/strong&gt;, so it’s
meant to be launched by a client, not run by hand. Add it as a local
(stdio) server. These config files are safe to commit per project, so
the tools show up only in the repos that opt in:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; opencode.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$schema&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;https://opencode.ai/config.json&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;mcp&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;pico-de-gallo&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;local&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;gallo-mcp&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt; true&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; Claude Code — .mcp.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;mcpServers&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;pico-de-gallo&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;gallo-mcp&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; Cursor — .cursor/mcp.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;mcpServers&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;pico-de-gallo&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;gallo-mcp&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One detail worth calling out: the server holds &lt;strong&gt;no persistent USB
claim&lt;/strong&gt;. Each tool call opens the board, runs, and releases it, so the
device stays free for the &lt;code&gt;gallo&lt;/code&gt; CLI or another host process between
calls. There’s a small fixed connection cost per call in exchange, but
it also means the server starts fine with nothing plugged in — you can
attach the board mid-session and the tools start working. If you have
more than one board attached, &lt;code&gt;--serial-number &amp;lt;SN&amp;gt;&lt;/code&gt; picks one.&lt;/p&gt;
&lt;h2 id=&quot;the-tool-surface&quot;&gt;The tool surface&lt;/h2&gt;
&lt;p&gt;Tools are grouped by peripheral, and every one is annotated: reads
carry MCP’s &lt;code&gt;readOnlyHint&lt;/code&gt;, writes and actuation carry
&lt;code&gt;destructiveHint&lt;/code&gt; (more on why that matters below).&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Peripheral&lt;/th&gt;&lt;th&gt;What the agent can do&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;device&lt;/td&gt;&lt;td&gt;list boards, check attach state, read firmware/schema version, ping&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;I²C&lt;/td&gt;&lt;td&gt;read, write, write-then-read, scan, batch, get/set frequency&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;SPI&lt;/td&gt;&lt;td&gt;read, write, full-duplex transfer, flush, batch, get/set config&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;UART&lt;/td&gt;&lt;td&gt;read, write, flush, get/set config&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;GPIO&lt;/td&gt;&lt;td&gt;read a pin, drive a pin, set direction/pull, bounded edge waits&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;PWM&lt;/td&gt;&lt;td&gt;get/set duty cycle, enable/disable, get/set config&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;ADC&lt;/td&gt;&lt;td&gt;read a sample, read config&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1-Wire&lt;/td&gt;&lt;td&gt;reset, read, write, parasitic-power write, ROM search&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The &lt;a href=&quot;/pico-de-gallo/crates/mcp.html&quot;&gt;full catalog is in the book&lt;/a&gt;. A couple of conventions
are worth knowing up front. Byte payloads go &lt;strong&gt;in&lt;/strong&gt; as hex strings —
comma-separated or bare, &lt;code&gt;&quot;0x00,0x10&quot;&lt;/code&gt; or &lt;code&gt;&quot;0010&quot;&lt;/code&gt; — and come &lt;strong&gt;back&lt;/strong&gt;
as both hex and a decimal array:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; i2c_write_read {&amp;quot;address&amp;quot;:72,&amp;quot;data&amp;quot;:&amp;quot;0x00&amp;quot;,&amp;quot;count&amp;quot;:2}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;hex&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;0x0B,0xCF&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;11&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 207&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the GPIO edge waits are &lt;strong&gt;timeout-bounded only&lt;/strong&gt;: each requires a
non-zero &lt;code&gt;timeout_ms&lt;/code&gt;. This first release deliberately leaves out
infinite waits and push-based edge subscriptions — a wait that never
returns would wedge the stdio session, and event streaming is out of
scope for now.&lt;/p&gt;
&lt;h2 id=&quot;seeing-the-same-truth-as-the-shell&quot;&gt;Seeing the same truth as the shell&lt;/h2&gt;
&lt;p&gt;The server was validated on real hardware: a Pico de Gallo (firmware
v0.10.0, schema v0.6.0 — the &lt;a href=&quot;/posts/pico-de-gallo-reliability-release/&quot;&gt;reliability release&lt;/a&gt;) with a
TMP108 temperature sensor on I²C.&lt;/p&gt;
&lt;p&gt;Over stdio, &lt;code&gt;status&lt;/code&gt; reports the attached board:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;attached&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;firmware_version&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;0.10.0&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;schema_major&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;schema_minor&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 6&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;i2c_scan&lt;/code&gt; finds the sensor at &lt;code&gt;0x48&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; i2c_scan {&amp;quot;include_reserved&amp;quot;:false}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;addresses&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;0x48&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;raw&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;72&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And &lt;code&gt;i2c_write_read&lt;/code&gt; pulls its two temperature bytes:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; i2c_write_read {&amp;quot;address&amp;quot;:72,&amp;quot;data&amp;quot;:&amp;quot;0x00&amp;quot;,&amp;quot;count&amp;quot;:2}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;hex&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;0x0B,0xCF&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;11&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 207&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Those bytes are byte-for-byte identical to what the CLI returns:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; gallo i2c write-read -a 0x48 -b 0x00 -c 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;0b cf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Turning &lt;code&gt;0x0BCF&lt;/code&gt; into degrees Celsius is between you and the TMP108
datasheet — the point is that the MCP round-trip returns exactly what
the shell does. An agent driving the board through &lt;code&gt;gallo-mcp&lt;/code&gt; sees the
same truth you would at the prompt.&lt;/p&gt;
&lt;h2 id=&quot;a-word-on-safety&quot;&gt;A word on safety&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;gallo-mcp&lt;/code&gt; does &lt;strong&gt;not&lt;/strong&gt; gate writes itself. It annotates them and
delegates approval to the MCP client: read tools are marked
&lt;code&gt;readOnlyHint&lt;/code&gt;, write and actuation tools are marked &lt;code&gt;destructiveHint&lt;/code&gt;.
A well-configured client uses those hints to prompt you before it
drives a pin or writes a bus.&lt;/p&gt;
&lt;p&gt;That delegation cuts both ways. Under a permission-less or
blanket-allow client, an agent can actuate hardware — toggle GPIOs,
write I²C/SPI, change bus configuration — with no confirmation at all.
If the board is wired to anything you care about, run the server behind
a client that honors &lt;code&gt;destructiveHint&lt;/code&gt;, and set your permissions
accordingly. The bytes are real, and so are the volts.&lt;/p&gt;
&lt;h2 id=&quot;the-plumbing-pico-de-gallo-lib-0-7-1&quot;&gt;The plumbing: &lt;code&gt;pico-de-gallo-lib&lt;/code&gt; 0.7.1&lt;/h2&gt;
&lt;p&gt;Handing the board to an agent turned up two rough edges in the host
library, and smoothing them out is the other half of today’s release.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;try_new()&lt;/code&gt;.&lt;/strong&gt; The existing &lt;code&gt;PicoDeGallo::new()&lt;/code&gt; (and
&lt;code&gt;new_with_serial_number()&lt;/code&gt;) enumerate USB at construction and &lt;strong&gt;panic&lt;/strong&gt;
when no matching board is present or the interface can’t be claimed —
the old “constructing never fails” doc comment was simply wrong. That’s
fine for a CLI that’s about to exit anyway, but a long-lived MCP server
that connects per call can’t panic just because you haven’t plugged the
board in yet. So 0.7.1 adds fallible &lt;code&gt;try_new()&lt;/code&gt; and
&lt;code&gt;try_new_with_serial_number()&lt;/code&gt;, returning &lt;code&gt;Result&amp;lt;PicoDeGallo, String&amp;gt;&lt;/code&gt;
so the caller can report “no device attached” or retry a transient
claim failure. The panicking constructors stay exactly as they were.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nameable comms errors.&lt;/strong&gt; 0.7.0 re-exports &lt;code&gt;HostErr&lt;/code&gt; and &lt;code&gt;WireError&lt;/code&gt;
(and makes the &lt;code&gt;host_client&lt;/code&gt; path public), so a downstream crate can
name the transport error types when it maps &lt;code&gt;PicoDeGalloError&lt;/code&gt; into its
own representation — without pulling in &lt;code&gt;postcard-rpc&lt;/code&gt; directly. That’s
what lets &lt;code&gt;gallo-mcp&lt;/code&gt; match on &lt;code&gt;PicoDeGalloError::Comms(HostErr::Closed)&lt;/code&gt;
and turn a dropped USB connection into a clean “no device attached”
message instead of an opaque error.&lt;/p&gt;
&lt;p&gt;Both changes are additive and non-breaking; &lt;code&gt;hal&lt;/code&gt;, &lt;code&gt;ffi&lt;/code&gt;, &lt;code&gt;gallo&lt;/code&gt;, and
the Python bindings resolve 0.7.1 without any changes of their own. If
you’re building your own host tooling on the library, &lt;code&gt;try_new()&lt;/code&gt; is
the constructor you want.&lt;/p&gt;
&lt;h2 id=&quot;get-it&quot;&gt;Get it&lt;/h2&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; cargo install gallo-mcp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;crate: &lt;a rel=&quot;external&quot; href=&quot;https://crates.io/crates/gallo-mcp&quot;&gt;&lt;code&gt;gallo-mcp&lt;/code&gt;&lt;/a&gt; · docs: &lt;a rel=&quot;external&quot; href=&quot;https://docs.rs/gallo-mcp&quot;&gt;docs.rs/gallo-mcp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;library: &lt;a rel=&quot;external&quot; href=&quot;https://crates.io/crates/pico-de-gallo-lib&quot;&gt;&lt;code&gt;pico-de-gallo-lib&lt;/code&gt; 0.7.1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;everything else: the &lt;a href=&quot;/pico-de-gallo/&quot;&gt;Pico de Gallo book&lt;/a&gt; and the &lt;a rel=&quot;external&quot; href=&quot;https://github.com/OpenDevicePartnership/pico-de-gallo&quot;&gt;repo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Wire up a board, point your agent at it, and let it read the chip
itself.&lt;/p&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Hi, I&#39;m Norbert</title>
    <published>2026-07-27T12:00:00+00:00</published>
    <updated>2026-07-27T12:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/norbert-introducing/" type="text/html"/>
    <id>https://balbi.sh/posts/norbert-introducing/</id>
    <summary type="html">&lt;p&gt;Meet &lt;strong&gt;Norbert&lt;/strong&gt; — a small command-line tool I wrote to program SPI
NOR flash chips. Its defining trait is patience: it reads and parses
the SFDP tables, verifies its work, and would rather wait a second
than guess.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;Meet &lt;strong&gt;Norbert&lt;/strong&gt; — a small command-line tool I wrote to program SPI
NOR flash chips. Its defining trait is patience: it reads and parses
the SFDP tables, verifies its work, and would rather wait a second
than guess.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;
&lt;p&gt;Hand Norbert a chip and it works out what the part is — reads the
JEDEC ID, parses the SFDP tables, or falls back to a table of chips it
already knows. From there it can erase the device, program an image,
verify every byte it wrote, and read the flash back. It drives a bare
flash chip over a &lt;a href=&quot;/pico-de-gallo/&quot;&gt;Pico de Gallo&lt;/a&gt; bridge, so there’s no adapter
board to wire up.&lt;/p&gt;
&lt;p&gt;A normal session looks like this:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; norbert detect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Hmm... let&amp;#39;s see what we&amp;#39;ve got here.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Found Winbond W25Q128JV.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; norbert program firmware.bin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Programming firmware.bin — 512 KiB at 0x000000.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  erase    [██████████████████████████]  8/8 blocks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  program  [██████████████████████████]  512 KiB/512 KiB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  verify   [██████████████████████████]  512 KiB/512 KiB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Done. Have a nice boot.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That chattiness is the point of the name: Norbert has a personality,
but only in its output. Underneath, the engineering is deliberately
dull — programming flash should be predictable, transparent, reliable,
and, above all, boring. It won’t skip verification because a write is
&lt;em&gt;probably fine&lt;/em&gt;, and it won’t assume every chip behaves the same.&lt;/p&gt;
&lt;p&gt;It’s early — version 0.1 — but it already recognizes a fair number of
chips — anything supporting SFDP should just work —, and &lt;code&gt;--quiet&lt;/code&gt;
gives scripts machine-friendly output when you don’t want the
commentary. The source is &lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/norbert&quot;&gt;on GitHub&lt;/a&gt;; if Norbert doesn’t know
your chip yet, teaching it one is the easiest contribution to make.&lt;/p&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Tamal: The Loader&#39;s Three Lives</title>
    <published>2026-07-27T09:00:00+00:00</published>
    <updated>2026-07-27T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-loader/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-loader/</id>
    <summary type="html">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader-cobs/&quot;&gt;Yesterday&lt;/a&gt; we read two pure step functions and made the same
promise about each of them, the promise we have been making all series:
the codec describes &lt;em&gt;what one byte does to the state&lt;/em&gt;, and someone else
owns the register that makes it sequential. Someone else holds the
clock. We made that promise for the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt;’s &lt;code&gt;step&lt;/code&gt; a month of posts
ago, and again for the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; and the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt;, and one
last time yesterday for &lt;code&gt;cobsDecodeStep&lt;/code&gt; and &lt;code&gt;cobsEncodeStep&lt;/code&gt;. Five pure
&lt;code&gt;s -&amp;gt; i -&amp;gt; (s, o)&lt;/code&gt; machines, each admired precisely for &lt;em&gt;not&lt;/em&gt; having a
clock, each handing the clock off to a someone we kept naming and never
met.&lt;/p&gt;
&lt;p&gt;Today we meet the someone. The loader is where the clock finally lives.&lt;/p&gt;
&lt;p&gt;It is also where everything else we have built finally meets. The
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; gave us a byte pipe; the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;top&lt;/a&gt;
sealed it and left four wires hanging at the loader’s door. Behind that
door is a block that does not compute a residue or drive a line or
recover a bit — it &lt;em&gt;conducts&lt;/em&gt;. It embeds the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader-cobs/&quot;&gt;COBS&lt;/a&gt; codec and
threads it through its own clock; it folds the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; over every byte
in both directions; it drives the two block RAMs on the ports the engine
leaves free; and it pulses the engine awake and waits for it to halt. The
whole series has been building leaves. The loader is the first branch.&lt;/p&gt;
&lt;p&gt;Yesterday’s post promised this one in as many words: the loader “wears
the silhouette we have now read five times — a sum for its phase, a
record for the rest, a step function beside a &lt;code&gt;mealy&lt;/code&gt;.” That is exactly
right, and exactly the shape of what follows — only scaled up. The sum
is now &lt;em&gt;two&lt;/em&gt; sums. The record is &lt;em&gt;eighteen&lt;/em&gt; fields. And the step is the
longest in the series. Same silhouette, far more mass. The mass is worth
it, because it buys the loader three distinct &lt;strong&gt;lives&lt;/strong&gt; — listen, run,
drain — lived over and over in a loop, and a rig you can load, trigger,
read, and load again.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader-cobs/&quot;&gt;Yesterday&lt;/a&gt; we read two pure step functions and made the same
promise about each of them, the promise we have been making all series:
the codec describes &lt;em&gt;what one byte does to the state&lt;/em&gt;, and someone else
owns the register that makes it sequential. Someone else holds the
clock. We made that promise for the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt;’s &lt;code&gt;step&lt;/code&gt; a month of posts
ago, and again for the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; and the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt;, and one
last time yesterday for &lt;code&gt;cobsDecodeStep&lt;/code&gt; and &lt;code&gt;cobsEncodeStep&lt;/code&gt;. Five pure
&lt;code&gt;s -&amp;gt; i -&amp;gt; (s, o)&lt;/code&gt; machines, each admired precisely for &lt;em&gt;not&lt;/em&gt; having a
clock, each handing the clock off to a someone we kept naming and never
met.&lt;/p&gt;
&lt;p&gt;Today we meet the someone. The loader is where the clock finally lives.&lt;/p&gt;
&lt;p&gt;It is also where everything else we have built finally meets. The
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; gave us a byte pipe; the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;top&lt;/a&gt;
sealed it and left four wires hanging at the loader’s door. Behind that
door is a block that does not compute a residue or drive a line or
recover a bit — it &lt;em&gt;conducts&lt;/em&gt;. It embeds the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader-cobs/&quot;&gt;COBS&lt;/a&gt; codec and
threads it through its own clock; it folds the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; over every byte
in both directions; it drives the two block RAMs on the ports the engine
leaves free; and it pulses the engine awake and waits for it to halt. The
whole series has been building leaves. The loader is the first branch.&lt;/p&gt;
&lt;p&gt;Yesterday’s post promised this one in as many words: the loader “wears
the silhouette we have now read five times — a sum for its phase, a
record for the rest, a step function beside a &lt;code&gt;mealy&lt;/code&gt;.” That is exactly
right, and exactly the shape of what follows — only scaled up. The sum
is now &lt;em&gt;two&lt;/em&gt; sums. The record is &lt;em&gt;eighteen&lt;/em&gt; fields. And the step is the
longest in the series. Same silhouette, far more mass. The mass is worth
it, because it buys the loader three distinct &lt;strong&gt;lives&lt;/strong&gt; — listen, run,
drain — lived over and over in a loop, and a rig you can load, trigger,
read, and load again.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;the-entire-source&quot;&gt;The entire source&lt;/h2&gt;
&lt;p&gt;Minus the SPDX header and the doc-comments — but this time &lt;em&gt;keeping&lt;/em&gt; the
inline notes on the state fields, which earn their place — here is
&lt;code&gt;src/Tamal/Loader.hs&lt;/code&gt;, the longest module in the series:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Loader&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderIn&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderOut&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;..&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; loader&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Clash.Prelude&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Data.Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;fromMaybe&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; isJust&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Crc&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;crc8Update&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Loader.Cobs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Params&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;AW&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RW&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; LoaderIn&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txReady&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; halted&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ringPtrIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RW&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ringData&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; LoaderOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; instrWr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; AW&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ringAddr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RW&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; startOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Lifecycle&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxControl&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Run&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Drain&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; DrainPhase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrOpcode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrFetch&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrLatch&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrWordByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrCrcByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrDrainOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrDelim&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; LoaderSt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lPhase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Lifecycle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lDec&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; DecSt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lEnc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; EncSt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lHeld&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; one-byte holdback (separates the trailing CRC)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lCrcRx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; running CRC over confirmed bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lHaveOp&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; opcode confirmed yet?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lOpcode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lByteIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; payload byte within the current word (0..3)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lWordAcc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; LE word being assembled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lHadPay&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; any payload byte seen (TRIGGER must have none)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lAddr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; AW&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; next instr write slot&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lFull&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; instr store overflowed (&amp;gt; 2^AW words)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lDrn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; DrainPhase&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lWord&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; ring word being emitted&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lWIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; LE byte of lWord (0..3)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lCrcTx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; running CRC over the drain&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lDrCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RW&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; ring record index being fetched&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lTerm&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; fetching/emitting the terminator word&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;initLoader&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderSt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;initLoader &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  LoaderSt&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; RxControl, both codecs seeded, every counter 0, every flag False&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lPhase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxControl&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lDec&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; initDec&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lEnc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; initEnc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; {-&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; … the remaining fifteen fields, all at their zero/False resting value … &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;-}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;idleOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;idleOut &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; LoaderOut&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; instrWr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ringAddr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; startOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;loader&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;HiddenClockResetEnable&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;loader &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; mealy loaderStep initLoader&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;loaderStep&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;LoaderSt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderOut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;loaderStep s inp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; lPhase s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  RxControl&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; rxStep s inp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Run&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; runStep s inp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Drain&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; drainStep s inp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;runStep&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;LoaderSt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderOut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;runStep s inp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; halted inp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      (&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lPhase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Drain&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lEnc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; initEnc&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lDrn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrOpcode&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lCrcTx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lDrCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lWIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lTerm&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      ,&lt;/span&gt;&lt;span&gt; idleOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; idleOut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxStep&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;LoaderSt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderOut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;rxStep s inp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; din &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; rxByte inp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;        Just&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; delimiter =&amp;gt; frame end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;        Just&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Just&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;        Nothing&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      (&lt;/span&gt;&lt;span&gt;dec&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;mDec&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; done&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; bad&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; cobsDecodeStep &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lDec s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; din&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      s1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lDec&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; dec&amp;#39;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;   in&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; done&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        then&lt;/span&gt;&lt;span&gt; finalize s1 bad&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        else&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; mDec &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;          Nothing&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; idleOut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;          Just&lt;/span&gt;&lt;span&gt; d &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            let&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; mw&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; lHeld s1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;                  Just&lt;/span&gt;&lt;span&gt; h &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; confirm s1 h&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;                  Nothing&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;             in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s2&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lHeld&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; d&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; idleOut&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;instrWr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; mw&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;confirm&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;LoaderSt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; AW&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;confirm s h&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; not &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lHaveOp s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      (&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lHaveOp&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lOpcode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; h&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lCrcRx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; crc8Update &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lCrcRx s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; h&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lAddr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; h &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x01&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; then&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; else&lt;/span&gt;&lt;span&gt; lAddr s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lFull&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; h &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x01&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; then&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; else&lt;/span&gt;&lt;span&gt; lFull s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      ,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      let&lt;/span&gt;&lt;span&gt; crc&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; crc8Update &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lCrcRx s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; h&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          acc&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; lWordAcc s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.|.&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;zeroExtend h &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;shiftL&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; *&lt;/span&gt;&lt;span&gt; fromIntegral &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lByteIx s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          isLoad &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; lOpcode s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x01&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;       in&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; lByteIx s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;              let&lt;/span&gt;&lt;span&gt; doWrite &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; isLoad &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; not &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lFull s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                  (&lt;/span&gt;&lt;span&gt;addr&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; full&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; lAddr s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; maxBound &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;then&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;lAddr s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; else&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;lAddr s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; lFull s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;               in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                      {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lCrcRx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; crc&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                      ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lWordAcc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                      ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lByteIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                      ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lHadPay&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                      ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lAddr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; isLoad &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;then&lt;/span&gt;&lt;span&gt; addr&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;else&lt;/span&gt;&lt;span&gt; lAddr s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                      ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lFull&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; isLoad &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;then&lt;/span&gt;&lt;span&gt; full&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;else&lt;/span&gt;&lt;span&gt; lFull s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                  ,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; doWrite &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;lAddr s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; acc&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; else&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                  )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              (&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lCrcRx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; crc&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lWordAcc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; acc&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lByteIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; lByteIx s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lHadPay&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              ,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;finalize&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;LoaderSt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderOut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;finalize s bad &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; crcCand &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; fromMaybe &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;lHeld s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      crcGood &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; not bad &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; isJust &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lHeld s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; lHaveOp s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; lCrcRx s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; crcCand&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      trigOk &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; crcGood &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; lOpcode s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x02&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; not &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lHadPay s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      s0 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; resetFrame s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;   in&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; trigOk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        then&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s0&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lPhase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Run&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; idleOut&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;startOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        else&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; idleOut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;resetFrame&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderSt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;resetFrame s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lDec&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; initDec&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lHeld&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lCrcRx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lHaveOp&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lOpcode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lByteIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lWordAcc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lHadPay&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;drainStep&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;LoaderSt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderOut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;drainStep s inp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; lDrn s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  DrOpcode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    feedByte s inp &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0x81&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;\&lt;/span&gt;&lt;span&gt;s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; s&amp;#39;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lDrn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrFetch&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lDrCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lTerm&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  DrWordByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    feedByte s inp &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;leByte &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lWord s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;lWIx s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;afterWordByte inp&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  DrCrcByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    feedByte s inp &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lCrcTx s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;\&lt;/span&gt;&lt;span&gt;s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; s&amp;#39;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lDrn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrDrainOut&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  DrFetch&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; addr &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; lTerm s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;then&lt;/span&gt;&lt;span&gt; maxBound &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;else&lt;/span&gt;&lt;span&gt; lDrCnt s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        (&lt;/span&gt;&lt;span&gt;enc&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; mOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; _&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; cobsEncodeStep &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lEnc s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; txReady inp&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lEnc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; enc&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lDrn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrLatch&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; idleOut&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; mOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ringAddr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; addr&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  DrLatch&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; addr &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; lTerm s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;then&lt;/span&gt;&lt;span&gt; maxBound &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;else&lt;/span&gt;&lt;span&gt; lDrCnt s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        (&lt;/span&gt;&lt;span&gt;enc&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; mOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; _&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; cobsEncodeStep &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lEnc s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; txReady inp&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lEnc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; enc&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lWord&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; ringData inp&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lWIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lDrn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrWordByte&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ,&lt;/span&gt;&lt;span&gt; idleOut&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; mOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ringAddr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; addr&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  DrDrainOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;enc&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; mOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; encDone&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; cobsEncodeStep &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lEnc s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; txReady inp&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lEnc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; enc&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lDrn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; encDone &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrDelim&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; else&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrDrainOut&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; idleOut&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; mOut&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  DrDelim&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    if&lt;/span&gt;&lt;span&gt; txReady inp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      then&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;resetFrame s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lPhase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxControl&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; idleOut&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      else&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; idleOut&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  afterWordByte i s&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; lWIx s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;/=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 3&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; s&amp;#39;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lWIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; lWIx s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; lTerm s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&amp;#39;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lDrn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrCrcByte&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; lDrCnt s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;gt;=&lt;/span&gt;&lt;span&gt; ringPtrIn i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&amp;#39;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lTerm&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lDrn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrFetch&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&amp;#39;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lDrCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; lDrCnt s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lDrn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrFetch&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;feedByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  LoaderSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;LoaderSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderSt&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;LoaderSt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderOut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;feedByte s inp b lst advance &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;enc&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;readyIn&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; mOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; _&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; cobsEncodeStep &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lEnc s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; txReady inp&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      s1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lEnc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; enc&amp;#39;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      s2 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        if&lt;/span&gt;&lt;span&gt; readyIn&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          then&lt;/span&gt;&lt;span&gt; advance s1&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lCrcTx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; lst &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;then&lt;/span&gt;&lt;span&gt; lCrcTx s1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;else&lt;/span&gt;&lt;span&gt; crc8Update &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lCrcTx s1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          else&lt;/span&gt;&lt;span&gt; s1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;   in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; idleOut&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; mOut&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;leByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;leByte w i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; slice d7 d0 w&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; slice d15 d8 w&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  2&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; slice d23 d16 w&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; slice d31 d24 w&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A dozen things, top to bottom: a three-name export list; the two I/O
records &lt;code&gt;LoaderIn&lt;/code&gt; and &lt;code&gt;LoaderOut&lt;/code&gt;; two little sums, &lt;code&gt;Lifecycle&lt;/code&gt; and
&lt;code&gt;DrainPhase&lt;/code&gt;; the eighteen-field &lt;code&gt;LoaderSt&lt;/code&gt; that is the machine’s whole
memory; two initialisers; the &lt;code&gt;mealy&lt;/code&gt; one-liner and the &lt;code&gt;loaderStep&lt;/code&gt; that
dispatches on phase; and then the three step functions with their helpers.
We wave past the export list and imports, slow right down for the types,
and walk the machine one life at a time.&lt;/p&gt;
&lt;h2 id=&quot;the-ritual-skipped&quot;&gt;The ritual, skipped&lt;/h2&gt;
&lt;p&gt;You know this opening beat cold. A module header with its export list; the
&lt;code&gt;import Clash.Prelude&lt;/code&gt; prelude swap; the &lt;code&gt;deriving stock (Generic, Show, Eq)&lt;/code&gt; and &lt;code&gt;deriving anyclass (NFDataX)&lt;/code&gt; refrain stamped on every type so
Clash can put it in a register. Six posts have paid that toll in full —
the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt; posts derived it at length — and I
will not charge it a seventh time. Everyone already knows what those are
about.&lt;/p&gt;
&lt;p&gt;The one line worth stopping on is not ritual at all. It is the three
imports in the middle:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Crc&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;crc8Update&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Loader.Cobs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Params&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;AW&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RW&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; fold, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader-cobs/&quot;&gt;COBS&lt;/a&gt; codec, and the shared address
widths. The loader imports the very blocks the last six posts built and
puts them all to work at once. This is the confluence in three lines: read
them and you already know the loader will fold a CRC, stream a COBS codec,
and address two memories sized by &lt;code&gt;AW&lt;/code&gt; and &lt;code&gt;RW&lt;/code&gt;. Everything we have read
plugs in right here.&lt;/p&gt;
&lt;h2 id=&quot;params-since-it-has-nowhere-else-to-live&quot;&gt;Params, since it has nowhere else to live&lt;/h2&gt;
&lt;p&gt;One of those imports has no post of its own, and never will: &lt;code&gt;Tamal.Params&lt;/code&gt;
is two type aliases and a doc-comment, and two lines do not earn a post of
their own. So we settle the small debt here, once, where the loader first
leans on them. In its entirety, minus the ritual:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; AW&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 10&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Nat&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; instruction-address width: 2^AW = 1024 words&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RW&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 12&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Nat&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; ring/trace-address width: 2^RW = 4096 words&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is the whole module. &lt;code&gt;AW&lt;/code&gt; is the instruction-address width — &lt;code&gt;2^AW = 1024&lt;/code&gt; words in the instruction store, and the program counter’s width to
match. &lt;code&gt;RW&lt;/code&gt; is the ring/trace-address width — &lt;code&gt;2^RW = 4096&lt;/code&gt; words in the
result ring, whose top slot is the reserved HALT terminator the drain
fetches last. The point is not the two numbers but the &lt;em&gt;sharing&lt;/em&gt;:
&lt;code&gt;Tamal.Params&lt;/code&gt; is a dependency-free leaf, importing only &lt;code&gt;Clash.Prelude&lt;/code&gt;,
so the engine, the two BRAM wrappers, the trace model, and the loader can
all name the same widths without importing one another — and widening the
instruction space or resizing the ring becomes a single edit here instead
of a hunt for hand-copied &lt;code&gt;Unsigned 10&lt;/code&gt; and &lt;code&gt;Unsigned 12&lt;/code&gt; literals
scattered across the tree. The loader meets both in a moment: &lt;code&gt;lAddr :: Unsigned AW&lt;/code&gt; counts instruction slots, &lt;code&gt;ringAddr :: Unsigned RW&lt;/code&gt; sweeps the
ring. Debt paid; on to the types that do earn their keep.&lt;/p&gt;
&lt;h2 id=&quot;the-types-are-the-design&quot;&gt;The types are the design&lt;/h2&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt;’s sum-and-product story, told one final time — and
told carefully, because with the loader the &lt;strong&gt;types are the design&lt;/strong&gt;. Read
the five of them and the machine is most of the way explained; the step
functions largely just honour what the records already promise. We take
them in the order they appear.&lt;/p&gt;
&lt;h3 id=&quot;the-ports-loaderin-and-loaderout&quot;&gt;The ports: &lt;code&gt;LoaderIn&lt;/code&gt; and &lt;code&gt;LoaderOut&lt;/code&gt;&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; LoaderIn&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txReady&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; halted&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ringPtrIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RW&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ringData&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; LoaderOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; instrWr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; AW&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ringAddr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RW&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; startOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These two records &lt;em&gt;are&lt;/em&gt; the seam. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;top&lt;/a&gt; left four wires hanging
at the loader’s door; here they are, named and typed, sitting beside the
wires that reach the other way, to the engine and the two memories. Read
&lt;code&gt;LoaderIn&lt;/code&gt; as “everything the world hands the loader each cycle”:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;rxByte&lt;/code&gt;&lt;/strong&gt; — a byte from the UART receiver, or &lt;code&gt;Nothing&lt;/code&gt;. A
one-cycle strobe: &lt;code&gt;Just b&lt;/code&gt; on the cycle a byte lands, &lt;code&gt;Nothing&lt;/code&gt; the
~500 cycles in between.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;txReady&lt;/code&gt;&lt;/strong&gt; — the UART transmitter is idle and can take a byte.
Back-pressure for the drain.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;halted&lt;/code&gt;&lt;/strong&gt; — the engine’s &lt;code&gt;haltedOut&lt;/code&gt;, a &lt;em&gt;level&lt;/em&gt;: high for as long
as the engine sits in its &lt;code&gt;Halted&lt;/code&gt; phase.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ringPtrIn&lt;/code&gt;&lt;/strong&gt; — how many trace records the engine wrote. The drain’s
upper bound, read straight off the halted engine.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ringData&lt;/code&gt;&lt;/strong&gt; — the ring BRAM’s read data, arriving one cycle after
the loader drives an address.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And &lt;code&gt;LoaderOut&lt;/code&gt; is “everything the loader drives back”:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;txByte&lt;/code&gt;&lt;/strong&gt; — a byte for the UART transmitter, or &lt;code&gt;Nothing&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;instrWr&lt;/code&gt;&lt;/strong&gt; — a write to the instruction store: &lt;code&gt;Just (addr, word)&lt;/code&gt;
or nothing this cycle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ringAddr&lt;/code&gt;&lt;/strong&gt; — the ring BRAM read address the drain sweeps.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;startOut&lt;/code&gt;&lt;/strong&gt; — the one-cycle pulse that wakes the engine.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two details hide in the signatures. First, the &lt;code&gt;Unsigned AW&lt;/code&gt; and &lt;code&gt;Unsigned RW&lt;/code&gt; we just met surface here as concrete port widths — &lt;code&gt;instrWr&lt;/code&gt;’s
address into the &lt;code&gt;1024&lt;/code&gt;-word instruction store, and &lt;code&gt;ringAddr&lt;/code&gt;/&lt;code&gt;ringPtrIn&lt;/code&gt;
into the &lt;code&gt;4096&lt;/code&gt;-slot ring. Second, and quietly load-bearing: the loader
drives exactly &lt;strong&gt;two&lt;/strong&gt; memory ports, the instruction &lt;em&gt;write&lt;/em&gt; and the ring
&lt;em&gt;read&lt;/em&gt;, and never the two the engine owns. It writes only while loading and
reads only while draining, never overlapping the engine’s run — so the two
machines share two BRAMs with no arbiter at all, collision-free by
construction.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-ports-1&quot;&gt;&lt;a href=&quot;#fn-ports&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h3 id=&quot;lifecycle-the-three-lives&quot;&gt;&lt;code&gt;Lifecycle&lt;/code&gt;: the three lives&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Lifecycle&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxControl&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Run&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Drain&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three constructors, and the whole post’s title. The loader is never doing
more than one of these at a time, and it moves between them in a fixed
loop:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;RxControl&lt;/code&gt;&lt;/strong&gt; — &lt;em&gt;listen.&lt;/em&gt; The only life that consumes the UART’s
receive strobe. Decode each incoming control frame, load programs into
the instruction store as they arrive, and watch for a trigger.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Run&lt;/code&gt;&lt;/strong&gt; — &lt;em&gt;stand aside.&lt;/em&gt; The engine is executing. The loader drives
nothing, listens to nothing, and watches a single bit: &lt;code&gt;halted&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Drain&lt;/code&gt;&lt;/strong&gt; — &lt;em&gt;report.&lt;/em&gt; The engine has halted; sweep its trace ring
out the transmitter as one frame, then go back to listening.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The entire top of the machine is a &lt;code&gt;case&lt;/code&gt; on this type. Load, run, drain,
and back to load — three lives lived in a ring, re-runnable forever: a
later trigger re-runs the same program, a later load replaces it.&lt;/p&gt;
&lt;figure class=&quot;life-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;life&quot; viewBox=&quot;0 0 760 300&quot; role=&quot;img&quot; aria-labelledby=&quot;life-t life-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;life-t&quot;&gt;The loader&#39;s three-state lifecycle FSM&lt;/title&gt;
&lt;desc id=&quot;life-d&quot;&gt;Three state boxes left to right: RxControl (listen for frames), Run (engine executes), and Drain (sweep the ring). An accent arrow from RxControl to Run is labelled valid TRIGGER, pulse startOut. A plain arrow from Run to Drain is labelled halted, a level. A long accent arc returns from Drain all the way back to RxControl, labelled drain complete, listen again. RxControl also carries a self-loop labelled valid LOAD_PROGRAM, words to the instruction BRAM, showing that loading a program leaves the machine in RxControl.&lt;/desc&gt;
&lt;style&gt;
.life{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.life .st{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.life .wire{stroke:var(--fg-main);stroke-width:2;fill:none}
.life .hot{stroke:var(--accent);stroke-width:2.5;fill:none}
.life .loop{stroke:var(--fg-main);stroke-width:2;fill:none}
.life text{font-family:var(--sans)}
.life .name{fill:var(--fg-main);font-family:var(--mono);font-size:16px}
.life .sub{fill:var(--fg-dim);font-size:12px}
.life .lab{fill:var(--fg-main);font-size:12.5px}
.life .labA{fill:var(--accent);font-size:12.5px}
.life .ah{fill:var(--fg-main)}
.life .ahA{fill:var(--accent)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;life-a&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;life-aa&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ahA&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;rect class=&quot;st&quot; x=&quot;55&quot;  y=&quot;120&quot; width=&quot;180&quot; height=&quot;70&quot; rx=&quot;9&quot;/&gt;
&lt;rect class=&quot;st&quot; x=&quot;340&quot; y=&quot;120&quot; width=&quot;120&quot; height=&quot;70&quot; rx=&quot;9&quot;/&gt;
&lt;rect class=&quot;st&quot; x=&quot;545&quot; y=&quot;120&quot; width=&quot;175&quot; height=&quot;70&quot; rx=&quot;9&quot;/&gt;
&lt;path class=&quot;loop&quot; d=&quot;M95,120 C88,72 202,72 195,120&quot; marker-end=&quot;url(#life-a)&quot;/&gt;
&lt;line class=&quot;hot&quot;  x1=&quot;235&quot; y1=&quot;155&quot; x2=&quot;336&quot; y2=&quot;155&quot; marker-end=&quot;url(#life-aa)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;460&quot; y1=&quot;155&quot; x2=&quot;541&quot; y2=&quot;155&quot; marker-end=&quot;url(#life-a)&quot;/&gt;
&lt;path class=&quot;hot&quot; d=&quot;M632,190 C632,258 145,258 145,192&quot; marker-end=&quot;url(#life-aa)&quot;/&gt;
&lt;text class=&quot;name&quot; x=&quot;145&quot; y=&quot;151&quot; text-anchor=&quot;middle&quot;&gt;RxControl&lt;/text&gt;
&lt;text class=&quot;sub&quot;  x=&quot;145&quot; y=&quot;172&quot; text-anchor=&quot;middle&quot;&gt;listen for frames&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;400&quot; y=&quot;151&quot; text-anchor=&quot;middle&quot;&gt;Run&lt;/text&gt;
&lt;text class=&quot;sub&quot;  x=&quot;400&quot; y=&quot;172&quot; text-anchor=&quot;middle&quot;&gt;engine executes&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;632&quot; y=&quot;151&quot; text-anchor=&quot;middle&quot;&gt;Drain&lt;/text&gt;
&lt;text class=&quot;sub&quot;  x=&quot;632&quot; y=&quot;172&quot; text-anchor=&quot;middle&quot;&gt;sweep the ring&lt;/text&gt;
&lt;text class=&quot;lab&quot;  x=&quot;145&quot; y=&quot;60&quot;  text-anchor=&quot;middle&quot;&gt;valid LOAD_PROGRAM · words → instr BRAM&lt;/text&gt;
&lt;text class=&quot;labA&quot; x=&quot;286&quot; y=&quot;112&quot; text-anchor=&quot;middle&quot;&gt;valid TRIGGER&lt;/text&gt;
&lt;text class=&quot;labA&quot; x=&quot;286&quot; y=&quot;178&quot; text-anchor=&quot;middle&quot;&gt;→ startOut&lt;/text&gt;
&lt;text class=&quot;lab&quot;  x=&quot;500&quot; y=&quot;112&quot; text-anchor=&quot;middle&quot;&gt;halted&lt;/text&gt;
&lt;text class=&quot;lab&quot;  x=&quot;500&quot; y=&quot;178&quot; text-anchor=&quot;middle&quot;&gt;(a level)&lt;/text&gt;
&lt;text class=&quot;labA&quot; x=&quot;388&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot;&gt;drain complete → listen again&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The three lives. &lt;code&gt;RxControl&lt;/code&gt; is the resting state and the only one that consumes the UART receiver; a valid &lt;code&gt;LOAD_PROGRAM&lt;/code&gt; writes its words and stays put (self-loop), while a valid &lt;code&gt;TRIGGER&lt;/code&gt; pulses &lt;code&gt;startOut&lt;/code&gt; and steps to &lt;code&gt;Run&lt;/code&gt;. &lt;code&gt;Run&lt;/code&gt; watches the engine&#39;s &lt;code&gt;halted&lt;/code&gt; level and, when it goes high, arms &lt;code&gt;Drain&lt;/code&gt;. &lt;code&gt;Drain&lt;/code&gt; sweeps the trace ring out the UART and returns to &lt;code&gt;RxControl&lt;/code&gt; — so the rig is re-runnable, load/trigger/drain, round and round.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;drainphase-a-program-counter-for-the-sweep&quot;&gt;&lt;code&gt;DrainPhase&lt;/code&gt;: a program counter for the sweep&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; DrainPhase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrOpcode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrFetch&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrLatch&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrWordByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrCrcByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrDrainOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrDelim&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Lifecycle&lt;/code&gt; has three states because the loader has three jobs. &lt;code&gt;Drain&lt;/code&gt;,
though, is not one action but a little &lt;em&gt;sequence&lt;/em&gt; — emit an opcode, then
fetch a ring word, then wait a cycle for the memory to answer, then emit
its four bytes, then loop, then emit a CRC, then flush, then cap the frame
— and this seven-constructor sum is that sequence’s program counter. The
&lt;code&gt;Drain&lt;/code&gt; life runs a tiny program, and &lt;code&gt;lDrn&lt;/code&gt; is where in the program it
is.&lt;/p&gt;
&lt;p&gt;Why spell a sequence out as seven named states instead of a loop? Because
in hardware a loop &lt;em&gt;is&lt;/em&gt; a state machine, and every step of this one is a
place the loader might have to &lt;strong&gt;wait&lt;/strong&gt;: a BRAM read does not answer until
the next cycle, the UART will not take a byte unless &lt;code&gt;txReady&lt;/code&gt;, and the
COBS encoder buffers a whole group before it emits. Each stall needs a
named state to hold in and come back to. We walk the seven in the drain
section; for now note the shape — a fetch/latch pair for the memory’s
latency, a per-byte emit state, and three states to close out the frame.&lt;/p&gt;
&lt;h3 id=&quot;loaderst-eighteen-fields-four-records-in-a-trench-coat&quot;&gt;&lt;code&gt;LoaderSt&lt;/code&gt;: eighteen fields, four records in a trench coat&lt;/h3&gt;
&lt;p&gt;Here is the field that everyone flinches at:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; LoaderSt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lPhase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Lifecycle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lDec&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; DecSt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lEnc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; EncSt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lHeld&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; one-byte holdback (separates the trailing CRC)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lCrcRx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; running CRC over confirmed bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lHaveOp&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; opcode confirmed yet?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lOpcode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lByteIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; payload byte within the current word (0..3)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lWordAcc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; LE word being assembled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lHadPay&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; any payload byte seen (TRIGGER must have none)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lAddr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; AW&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; next instr write slot&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lFull&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; instr store overflowed (&amp;gt; 2^AW words)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lDrn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; DrainPhase&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lWord&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; ring word being emitted&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lWIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; LE byte of lWord (0..3)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lCrcTx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; running CRC over the drain&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lDrCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RW&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; ring record index being fetched&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lTerm&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; fetching/emitting the terminator word&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eighteen fields is a lot, and the honest reaction is a wince. But it is
not eighteen unrelated things; it is &lt;strong&gt;four small records wearing one
constructor&lt;/strong&gt;, because a &lt;code&gt;mealy&lt;/code&gt; carries exactly one state value and the
loader would rather be one machine with one memory than four machines
haggling over turns. Sort the fields by which job they serve and the
wince goes away:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Phase (1 field).&lt;/strong&gt; &lt;code&gt;lPhase&lt;/code&gt; — which of the three lives we are living.
The one field every cycle reads first.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embedded codecs (2 fields).&lt;/strong&gt; &lt;code&gt;lDec&lt;/code&gt; and &lt;code&gt;lEnc&lt;/code&gt; — the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader-cobs/&quot;&gt;COBS&lt;/a&gt;
decoder’s &lt;code&gt;DecSt&lt;/code&gt; and the encoder’s &lt;code&gt;EncSt&lt;/code&gt;, held &lt;em&gt;inside&lt;/em&gt; the loader’s
state. This is the promise the last post made, kept to the letter: the
codec exported its two opaque state types and their &lt;code&gt;initDec&lt;/code&gt;/&lt;code&gt;initEnc&lt;/code&gt;
seeds precisely so the loader could carry them here and thread them
through its own clock. “The loader is the machine that clocks them” —
these two fields are where.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RX / frame-parse (9 fields).&lt;/strong&gt; The load path’s scratchpad: &lt;code&gt;lHeld&lt;/code&gt;
(the one-byte holdback), &lt;code&gt;lCrcRx&lt;/code&gt; (running CRC over confirmed bytes),
&lt;code&gt;lHaveOp&lt;/code&gt; and &lt;code&gt;lOpcode&lt;/code&gt; (have we seen the opcode, and what was it),
&lt;code&gt;lByteIx&lt;/code&gt; and &lt;code&gt;lWordAcc&lt;/code&gt; (which of a word’s four bytes we are on, and
the little-endian word taking shape), &lt;code&gt;lHadPay&lt;/code&gt; (did any payload arrive
— a &lt;code&gt;TRIGGER&lt;/code&gt; must have none), and &lt;code&gt;lAddr&lt;/code&gt; and &lt;code&gt;lFull&lt;/code&gt; (the next
instruction slot, and whether the store overflowed).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Drain (6 fields).&lt;/strong&gt; The report path’s scratchpad: &lt;code&gt;lDrn&lt;/code&gt; (the
&lt;code&gt;DrainPhase&lt;/code&gt; program counter), &lt;code&gt;lWord&lt;/code&gt; and &lt;code&gt;lWIx&lt;/code&gt; (the ring word being
emitted and which of its bytes), &lt;code&gt;lCrcTx&lt;/code&gt; (running CRC over the drain),
&lt;code&gt;lDrCnt&lt;/code&gt; (which ring record we are fetching), and &lt;code&gt;lTerm&lt;/code&gt; (are we on the
terminator word yet).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Read that way it is four modest records, and the two big ones — RX-parse
and Drain — are never live at once, since &lt;code&gt;RxControl&lt;/code&gt; and &lt;code&gt;Drain&lt;/code&gt; are
different lives. At any instant half these fields are asleep. They cohabit
one record for the reason the whole loader is one &lt;code&gt;mealy&lt;/code&gt;: one machine
with one memory is simpler to reason about, and to register, than three
sharing a bus.&lt;/p&gt;
&lt;h3 id=&quot;the-two-one-liners&quot;&gt;The two one-liners&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;initLoader&lt;/code&gt; is the machine at power-on — &lt;code&gt;RxControl&lt;/code&gt;, both codecs seeded
with &lt;code&gt;initDec&lt;/code&gt;/&lt;code&gt;initEnc&lt;/code&gt;, every counter zero, every flag false: listening,
nothing held, nothing owed. &lt;code&gt;idleOut&lt;/code&gt; is the do-nothing output — no byte,
no write, no pulse — and it earns a name because the loader returns it on
the overwhelming majority of cycles. Naming it once lets every step
function say “nothing happens this cycle” in five characters, and lets your
eye skip to the cycles that matter.&lt;/p&gt;
&lt;h2 id=&quot;the-machine-that-owns-the-clock&quot;&gt;The machine that owns the clock&lt;/h2&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;loader&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;HiddenClockResetEnable&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;loader &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; mealy loaderStep initLoader&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;loaderStep&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;LoaderSt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderOut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;loaderStep s inp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; lPhase s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  RxControl&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; rxStep s inp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Run&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; runStep s inp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Drain&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; drainStep s inp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is the promise kept. Six posts of pure steps insisting &lt;em&gt;someone else
owns the register&lt;/em&gt;, and this is the someone. &lt;code&gt;mealy&lt;/code&gt; is the &lt;a rel=&quot;external&quot; href=&quot;https://clash-lang.org&quot;&gt;Clash&lt;/a&gt;
primitive the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; introduced: hand it a pure &lt;code&gt;s -&amp;gt; i -&amp;gt; (s, o)&lt;/code&gt; and a seed, and it wraps exactly one register around the state and
lifts the pure function into &lt;code&gt;Signal dom i -&amp;gt; Signal dom o&lt;/code&gt; — a clocked
thing. &lt;code&gt;loader&lt;/code&gt; hands &lt;code&gt;mealy&lt;/code&gt; its &lt;code&gt;loaderStep&lt;/code&gt; and &lt;code&gt;initLoader&lt;/code&gt;, and out
comes a signal function with a clock threaded through it. Every pure step
we praised for having no clock — the CRC’s, the codec’s two — gets its
clock &lt;em&gt;here&lt;/em&gt;, because the loader clocks itself and carries them along in
&lt;code&gt;lDec&lt;/code&gt;, &lt;code&gt;lEnc&lt;/code&gt;, &lt;code&gt;lCrcRx&lt;/code&gt;, &lt;code&gt;lCrcTx&lt;/code&gt;.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-mealys-1&quot;&gt;&lt;a href=&quot;#fn-mealys&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;And &lt;code&gt;loaderStep&lt;/code&gt; is almost nothing: read the current life off &lt;code&gt;lPhase&lt;/code&gt;
and delegate. That &lt;code&gt;case&lt;/code&gt; is the whole top-level control flow — the
three lives are three functions, and the machine is whichever the phase
names. So the rest of the post is three smaller ones; we take the easiest
first.&lt;/p&gt;
&lt;h2 id=&quot;run-the-life-that-waits&quot;&gt;Run: the life that waits&lt;/h2&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;runStep&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;LoaderSt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderOut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;runStep s inp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; halted inp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      (&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lPhase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Drain&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lEnc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; initEnc&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lDrn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrOpcode&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lCrcTx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lDrCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lWIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lTerm&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      ,&lt;/span&gt;&lt;span&gt; idleOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; idleOut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The shortest of the three, and the only one that does essentially
nothing. While the engine runs, the loader has exactly one job: watch one
bit. &lt;code&gt;halted&lt;/code&gt; is a &lt;em&gt;level&lt;/em&gt;, not a pulse — the engine’s &lt;code&gt;Halted&lt;/code&gt; phase is
stable, it does not blink — so the guard just tests it every cycle. Low:
hold the state, emit &lt;code&gt;idleOut&lt;/code&gt;. High: the run is over, so &lt;strong&gt;arm the
drain&lt;/strong&gt; and switch lives.&lt;/p&gt;
&lt;p&gt;Arming the drain is that record update, worth reading for what it spares.
It seeds the encoder fresh, rewinds the program counter to &lt;code&gt;DrOpcode&lt;/code&gt;, and
zeroes the drain counters and CRC — but pointedly does &lt;em&gt;not&lt;/em&gt; touch
&lt;code&gt;lAddr&lt;/code&gt;, &lt;code&gt;lFull&lt;/code&gt;, or the program in the instruction BRAM. A drain must not
disturb the loaded code, because the rig is re-runnable: a later &lt;code&gt;TRIGGER&lt;/code&gt;
with no new &lt;code&gt;LOAD&lt;/code&gt; re-runs it. &lt;code&gt;Run&lt;/code&gt; is a turnstile — it spins until
&lt;code&gt;halted&lt;/code&gt;, then clicks one notch into &lt;code&gt;Drain&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;rxcontrol-the-load-path&quot;&gt;RxControl: the load path&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;RxControl&lt;/code&gt; is the loader listening. A control frame arrives on the UART
one byte at a time, and the job is to turn that trickle back into a
&lt;em&gt;message&lt;/em&gt; — an opcode and its payload — verify it, and act. Before the
code, the thing being parsed. A frame on the wire looks like this, and so
does the frame the drain will later build in the other direction:&lt;/p&gt;
&lt;figure class=&quot;frm-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;frm&quot; viewBox=&quot;0 0 760 300&quot; role=&quot;img&quot; aria-labelledby=&quot;frm-t frm-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;frm-t&quot;&gt;The control frame and the result frame, layer by layer&lt;/title&gt;
&lt;desc id=&quot;frm-d&quot;&gt;Two frames of identical shape. The control frame (host to FPGA) is an opcode byte (0x01 LOAD or 0x02 TRIGGER), a payload of little-endian words or none, and a CRC-8 byte — that trio COBS-encoded so it holds no interior zero — followed by a single 0x00 delimiter. The result frame (FPGA to host) is the same shape: opcode 0x81, the ring&#39;s trace words (REVISION, records, terminator), a CRC-8, COBS-encoded, then 0x00. In both, the message (opcode plus payload) is the Tamal.Wire layer; the CRC and the delimiter are the frame layer; COBS is the byte-stuffing layer from the previous post.&lt;/desc&gt;
&lt;style&gt;
.frm{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.frm .box{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.frm .delim{fill:none;stroke:var(--accent);stroke-width:2;stroke-dasharray:5 4}
.frm .brace{stroke:var(--fg-dim);stroke-width:1.5;fill:none}
.frm text{font-family:var(--sans)}
.frm .val{fill:var(--fg-main);font-family:var(--mono);font-size:14px}
.frm .valA{fill:var(--accent);font-family:var(--mono);font-size:14px}
.frm .lab{fill:var(--fg-dim);font-size:12px}
.frm .labA{fill:var(--accent);font-size:12.5px}
.frm .sub{fill:var(--fg-dim);font-size:11.5px}
&lt;/style&gt;
&lt;text class=&quot;lab&quot; x=&quot;24&quot; y=&quot;103&quot; text-anchor=&quot;start&quot;&gt;control&lt;/text&gt;
&lt;rect class=&quot;box&quot;   x=&quot;95&quot;  y=&quot;76&quot; width=&quot;95&quot;  height=&quot;46&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;box&quot;   x=&quot;190&quot; y=&quot;76&quot; width=&quot;205&quot; height=&quot;46&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;box&quot;   x=&quot;395&quot; y=&quot;76&quot; width=&quot;85&quot;  height=&quot;46&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;delim&quot; x=&quot;560&quot; y=&quot;76&quot; width=&quot;70&quot;  height=&quot;46&quot; rx=&quot;5&quot;/&gt;
&lt;path class=&quot;brace&quot; d=&quot;M95,68 V58 H480 V68&quot;/&gt;
&lt;path class=&quot;brace&quot; d=&quot;M95,130 V140 H395 V130&quot;/&gt;
&lt;text class=&quot;valA&quot; x=&quot;142&quot; y=&quot;98&quot;  text-anchor=&quot;middle&quot;&gt;opcode&lt;/text&gt;
&lt;text class=&quot;sub&quot;  x=&quot;142&quot; y=&quot;114&quot; text-anchor=&quot;middle&quot;&gt;0x01 / 0x02&lt;/text&gt;
&lt;text class=&quot;val&quot;  x=&quot;292&quot; y=&quot;98&quot;  text-anchor=&quot;middle&quot;&gt;payload&lt;/text&gt;
&lt;text class=&quot;sub&quot;  x=&quot;292&quot; y=&quot;114&quot; text-anchor=&quot;middle&quot;&gt;LE words · or none&lt;/text&gt;
&lt;text class=&quot;val&quot;  x=&quot;437&quot; y=&quot;104&quot; text-anchor=&quot;middle&quot;&gt;crc8&lt;/text&gt;
&lt;text class=&quot;valA&quot; x=&quot;595&quot; y=&quot;104&quot; text-anchor=&quot;middle&quot;&gt;00&lt;/text&gt;
&lt;text class=&quot;labA&quot; x=&quot;287&quot; y=&quot;50&quot;  text-anchor=&quot;middle&quot;&gt;COBS-encoded — no interior 0x00 (last post)&lt;/text&gt;
&lt;text class=&quot;lab&quot;  x=&quot;245&quot; y=&quot;156&quot; text-anchor=&quot;middle&quot;&gt;message: opcode ++ payload  (Tamal.Wire)&lt;/text&gt;
&lt;text class=&quot;lab&quot;  x=&quot;437&quot; y=&quot;156&quot; text-anchor=&quot;middle&quot;&gt;+ CRC-8&lt;/text&gt;
&lt;text class=&quot;lab&quot;  x=&quot;595&quot; y=&quot;156&quot; text-anchor=&quot;middle&quot;&gt;+ delimiter&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;24&quot; y=&quot;237&quot; text-anchor=&quot;start&quot;&gt;result&lt;/text&gt;
&lt;rect class=&quot;box&quot;   x=&quot;95&quot;  y=&quot;210&quot; width=&quot;95&quot;  height=&quot;46&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;box&quot;   x=&quot;190&quot; y=&quot;210&quot; width=&quot;205&quot; height=&quot;46&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;box&quot;   x=&quot;395&quot; y=&quot;210&quot; width=&quot;85&quot;  height=&quot;46&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;delim&quot; x=&quot;560&quot; y=&quot;210&quot; width=&quot;70&quot;  height=&quot;46&quot; rx=&quot;5&quot;/&gt;
&lt;path class=&quot;brace&quot; d=&quot;M95,202 V192 H480 V202&quot;/&gt;
&lt;text class=&quot;valA&quot; x=&quot;142&quot; y=&quot;232&quot; text-anchor=&quot;middle&quot;&gt;opcode&lt;/text&gt;
&lt;text class=&quot;sub&quot;  x=&quot;142&quot; y=&quot;248&quot; text-anchor=&quot;middle&quot;&gt;0x81&lt;/text&gt;
&lt;text class=&quot;val&quot;  x=&quot;292&quot; y=&quot;232&quot; text-anchor=&quot;middle&quot;&gt;trace words&lt;/text&gt;
&lt;text class=&quot;sub&quot;  x=&quot;292&quot; y=&quot;248&quot; text-anchor=&quot;middle&quot;&gt;REV · records · term&lt;/text&gt;
&lt;text class=&quot;val&quot;  x=&quot;437&quot; y=&quot;238&quot; text-anchor=&quot;middle&quot;&gt;crc8&lt;/text&gt;
&lt;text class=&quot;valA&quot; x=&quot;595&quot; y=&quot;238&quot; text-anchor=&quot;middle&quot;&gt;00&lt;/text&gt;
&lt;text class=&quot;labA&quot; x=&quot;287&quot; y=&quot;184&quot; text-anchor=&quot;middle&quot;&gt;COBS-encoded&lt;/text&gt;
&lt;text class=&quot;lab&quot;  x=&quot;388&quot; y=&quot;286&quot; text-anchor=&quot;middle&quot;&gt;both directions, one shape:  COBS( opcode ++ payload ++ crc8 ) ++ 0x00&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;One frame shape, both directions. Read it from the inside out: the &lt;em&gt;message&lt;/em&gt; — an opcode and its payload — is the &lt;a href=&quot;https://github.com/felipebalbi/tamal&quot;&gt;Tamal.Wire&lt;/a&gt; layer; a &lt;code&gt;CRC-8&lt;/code&gt; byte guards it; &lt;a href=&quot;https://balbi.sh/posts/tamal-loader-cobs/&quot;&gt;COBS&lt;/a&gt; stuffs every zero out of the opcode-payload-CRC run; and a lone &lt;code&gt;0x00&lt;/code&gt; delimiter closes the frame. The last post built the COBS layer. This post builds the two layers outside it — and peels them, on the way in.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Read from the inside out. The innermost thing is the &lt;em&gt;message&lt;/em&gt; — an
opcode byte and its payload, the logical frame &lt;code&gt;Tamal.Wire&lt;/code&gt;’s
&lt;code&gt;encodeControl&lt;/code&gt; builds before it wraps it. Around it, a &lt;code&gt;CRC-8&lt;/code&gt; byte guards
the lot. Around &lt;em&gt;that&lt;/em&gt;, &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader-cobs/&quot;&gt;COBS&lt;/a&gt; stuffs out every zero, so the run
holds no interior &lt;code&gt;0x00&lt;/code&gt;. And around everything, a single &lt;code&gt;0x00&lt;/code&gt; delimiter.
The loader’s receive job is to peel those layers in reverse: watch for the
delimiter, un-COBS the interior, check the CRC, and read the message. Here
is the whole of it.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxStep&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;LoaderSt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderOut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;rxStep s inp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; din &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; rxByte inp &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;        Just&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; delimiter =&amp;gt; frame end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;        Just&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Just&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;        Nothing&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      (&lt;/span&gt;&lt;span&gt;dec&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;mDec&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; done&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; bad&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; cobsDecodeStep &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lDec s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; din&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      s1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lDec&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; dec&amp;#39;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;   in&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; done&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        then&lt;/span&gt;&lt;span&gt; finalize s1 bad&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        else&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; mDec &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;          Nothing&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; idleOut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;          Just&lt;/span&gt;&lt;span&gt; d &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            let&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; mw&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; lHeld s1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;                  Just&lt;/span&gt;&lt;span&gt; h &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; confirm s1 h&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;                  Nothing&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;             in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s2&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lHeld&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; d&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; idleOut&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;instrWr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; mw&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Four moves, top to bottom. The &lt;strong&gt;delimiter watch&lt;/strong&gt; comes first: that
&lt;code&gt;case rxByte&lt;/code&gt;. A received &lt;code&gt;0x00&lt;/code&gt; is not data — COBS guarantees no interior
zeros, so a zero on the wire can only be the frame boundary — so the
loader translates it into the codec’s vocabulary: &lt;code&gt;Just 0&lt;/code&gt; becomes
&lt;code&gt;(Nothing, frameEnd = True)&lt;/code&gt;, any other byte &lt;code&gt;(Just b, False)&lt;/code&gt;, no byte
&lt;code&gt;(Nothing, False)&lt;/code&gt;. That &lt;code&gt;frameEnd&lt;/code&gt; pulse is exactly the one
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader-cobs/&quot;&gt;&lt;code&gt;cobsDecodeStep&lt;/code&gt;&lt;/a&gt; expects, and here is the thing that pulses it. The
delimiter belongs to the frame layer, not the codec, and this &lt;code&gt;case&lt;/code&gt; is
where that line is drawn.&lt;/p&gt;
&lt;p&gt;Second, &lt;strong&gt;feed the codec.&lt;/strong&gt; &lt;code&gt;cobsDecodeStep&lt;/code&gt; runs, threaded through
&lt;code&gt;lDec&lt;/code&gt;, and hands back the triple we read last post: a maybe-decoded byte
&lt;code&gt;mDec&lt;/code&gt;, a &lt;code&gt;done&lt;/code&gt; pulse, a &lt;code&gt;bad&lt;/code&gt; flag.&lt;/p&gt;
&lt;p&gt;Third, if &lt;code&gt;done&lt;/code&gt; — the delimiter fired — the frame is over; hand off to
&lt;code&gt;finalize&lt;/code&gt;. If instead the codec produced nothing this cycle (&lt;code&gt;Nothing&lt;/code&gt;),
idle; that is the common case, a byte mid-flight or a code byte that only
armed a counter.&lt;/p&gt;
&lt;p&gt;And fourth, the move that deserves its own name. When a decoded byte &lt;code&gt;d&lt;/code&gt;
&lt;em&gt;does&lt;/em&gt; arrive, the loader does &lt;strong&gt;not&lt;/strong&gt; process it. It processes the byte
it was already holding, &lt;code&gt;lHeld&lt;/code&gt;, and stashes &lt;code&gt;d&lt;/code&gt; in its place. That is the
&lt;strong&gt;holdback&lt;/strong&gt;, and it exists to solve one specific problem:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The last byte of the logical stream is the CRC. But nothing marks it as
the CRC while it streams — it looks like any other byte, right up until
the delimiter proves it was the last one. So the loader always lags one
byte behind: it holds the newest decoded byte and only &lt;em&gt;confirms&lt;/em&gt; the
previous one, because only a byte with another byte behind it is
provably payload, not the trailing check.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure class=&quot;rxf-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;rxf&quot; viewBox=&quot;0 0 760 312&quot; role=&quot;img&quot; aria-labelledby=&quot;rxf-t rxf-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;rxf-t&quot;&gt;The RX pipeline and the one-byte holdback&lt;/title&gt;
&lt;desc id=&quot;rxf-d&quot;&gt;Top: a pipeline. rxByte flows into a delimiter-watch box, then a COBS decode box, then an accent-highlighted holdback box (lHeld), then a confirm box, then out to instrWr and startOut. Bottom: the decoded logical stream drawn as five boxes — op, p0, p1, p2, and a dashed accent crc. A bracket under the first four says each byte is confirmed — CRC-folded and routed — only when the next byte lands. The final crc box is marked as the candidate, never confirmed, checked at the 0x00 delimiter instead.&lt;/desc&gt;
&lt;style&gt;
.rxf{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.rxf .box{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.rxf .hot{fill:var(--bg-dim);stroke:var(--accent);stroke-width:2.5}
.rxf .cand{fill:none;stroke:var(--accent);stroke-width:2;stroke-dasharray:5 4}
.rxf .wire{stroke:var(--fg-main);stroke-width:2;fill:none}
.rxf .brace{stroke:var(--accent);stroke-width:2;fill:none}
.rxf .div{stroke:var(--fg-dim);stroke-width:1;stroke-dasharray:4 4}
.rxf text{font-family:var(--sans)}
.rxf .val{fill:var(--fg-main);font-family:var(--mono);font-size:15px}
.rxf .valA{fill:var(--accent);font-family:var(--mono);font-size:15px}
.rxf .lab{fill:var(--fg-main);font-size:12.5px}
.rxf .sub{fill:var(--fg-dim);font-size:11.5px}
.rxf .note{fill:var(--accent);font-size:12px}
.rxf .ah{fill:var(--fg-main)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;rxf-a&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;text class=&quot;sub&quot; x=&quot;40&quot; y=&quot;75&quot; text-anchor=&quot;start&quot;&gt;rxByte&lt;/text&gt;
&lt;rect class=&quot;box&quot; x=&quot;110&quot; y=&quot;48&quot; width=&quot;100&quot; height=&quot;46&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;box&quot; x=&quot;250&quot; y=&quot;48&quot; width=&quot;100&quot; height=&quot;46&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;hot&quot; x=&quot;390&quot; y=&quot;48&quot; width=&quot;100&quot; height=&quot;46&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;box&quot; x=&quot;530&quot; y=&quot;48&quot; width=&quot;95&quot;  height=&quot;46&quot; rx=&quot;5&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;80&quot;  y1=&quot;71&quot; x2=&quot;108&quot; y2=&quot;71&quot; marker-end=&quot;url(#rxf-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;210&quot; y1=&quot;71&quot; x2=&quot;248&quot; y2=&quot;71&quot; marker-end=&quot;url(#rxf-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;350&quot; y1=&quot;71&quot; x2=&quot;388&quot; y2=&quot;71&quot; marker-end=&quot;url(#rxf-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;490&quot; y1=&quot;71&quot; x2=&quot;528&quot; y2=&quot;71&quot; marker-end=&quot;url(#rxf-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;625&quot; y1=&quot;71&quot; x2=&quot;658&quot; y2=&quot;71&quot; marker-end=&quot;url(#rxf-a)&quot;/&gt;
&lt;text class=&quot;lab&quot; x=&quot;160&quot; y=&quot;67&quot; text-anchor=&quot;middle&quot;&gt;delimiter&lt;/text&gt;
&lt;text class=&quot;sub&quot; x=&quot;160&quot; y=&quot;83&quot; text-anchor=&quot;middle&quot;&gt;watch 0x00&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;300&quot; y=&quot;67&quot; text-anchor=&quot;middle&quot;&gt;COBS&lt;/text&gt;
&lt;text class=&quot;sub&quot; x=&quot;300&quot; y=&quot;83&quot; text-anchor=&quot;middle&quot;&gt;decode&lt;/text&gt;
&lt;text class=&quot;valA&quot; x=&quot;440&quot; y=&quot;67&quot; text-anchor=&quot;middle&quot;&gt;holdback&lt;/text&gt;
&lt;text class=&quot;sub&quot;  x=&quot;440&quot; y=&quot;83&quot; text-anchor=&quot;middle&quot;&gt;lHeld&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;577&quot; y=&quot;76&quot; text-anchor=&quot;middle&quot;&gt;confirm&lt;/text&gt;
&lt;text class=&quot;sub&quot; x=&quot;700&quot; y=&quot;67&quot; text-anchor=&quot;middle&quot;&gt;instrWr /&lt;/text&gt;
&lt;text class=&quot;sub&quot; x=&quot;700&quot; y=&quot;83&quot; text-anchor=&quot;middle&quot;&gt;startOut&lt;/text&gt;
&lt;line class=&quot;div&quot; x1=&quot;40&quot; y1=&quot;120&quot; x2=&quot;720&quot; y2=&quot;120&quot;/&gt;
&lt;text class=&quot;sub&quot; x=&quot;40&quot; y=&quot;150&quot; text-anchor=&quot;start&quot;&gt;decoded logical stream, always one byte behind:&lt;/text&gt;
&lt;rect class=&quot;box&quot;  x=&quot;120&quot; y=&quot;175&quot; width=&quot;68&quot; height=&quot;48&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;box&quot;  x=&quot;228&quot; y=&quot;175&quot; width=&quot;68&quot; height=&quot;48&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;box&quot;  x=&quot;336&quot; y=&quot;175&quot; width=&quot;68&quot; height=&quot;48&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;box&quot;  x=&quot;444&quot; y=&quot;175&quot; width=&quot;68&quot; height=&quot;48&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;cand&quot; x=&quot;552&quot; y=&quot;175&quot; width=&quot;68&quot; height=&quot;48&quot; rx=&quot;5&quot;/&gt;
&lt;text class=&quot;val&quot;  x=&quot;154&quot; y=&quot;205&quot; text-anchor=&quot;middle&quot;&gt;op&lt;/text&gt;
&lt;text class=&quot;val&quot;  x=&quot;262&quot; y=&quot;205&quot; text-anchor=&quot;middle&quot;&gt;p0&lt;/text&gt;
&lt;text class=&quot;val&quot;  x=&quot;370&quot; y=&quot;205&quot; text-anchor=&quot;middle&quot;&gt;p1&lt;/text&gt;
&lt;text class=&quot;val&quot;  x=&quot;478&quot; y=&quot;205&quot; text-anchor=&quot;middle&quot;&gt;p2&lt;/text&gt;
&lt;text class=&quot;valA&quot; x=&quot;586&quot; y=&quot;205&quot; text-anchor=&quot;middle&quot;&gt;crc&lt;/text&gt;
&lt;path class=&quot;brace&quot; d=&quot;M120,233 V243 H512 V233&quot;/&gt;
&lt;text class=&quot;note&quot; x=&quot;316&quot; y=&quot;262&quot; text-anchor=&quot;middle&quot;&gt;confirmed — CRC-folded, routed — only when the next byte lands&lt;/text&gt;
&lt;line class=&quot;brace&quot; x1=&quot;586&quot; y1=&quot;225&quot; x2=&quot;586&quot; y2=&quot;248&quot; stroke-dasharray=&quot;5 4&quot;/&gt;
&lt;text class=&quot;note&quot; x=&quot;586&quot; y=&quot;266&quot; text-anchor=&quot;middle&quot;&gt;candidate&lt;/text&gt;
&lt;text class=&quot;sub&quot;  x=&quot;586&quot; y=&quot;281&quot; text-anchor=&quot;middle&quot;&gt;checked at 0x00&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The receive pipeline and its one-byte lag. Bytes flow &lt;code&gt;rxByte → delimiter-watch → COBS decode → holdback → confirm&lt;/code&gt;. Because the trailing byte of the logical stream is the CRC — indistinguishable from payload until the frame ends — the loader confirms each byte only when the &lt;em&gt;next&lt;/em&gt; one arrives. The final byte is never confirmed: it is the CRC candidate, held back and checked against the running CRC when the &lt;code&gt;0x00&lt;/code&gt; delimiter closes the frame.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Same field, two fates: when a new byte arrives, the held one is provably
not the last, so &lt;code&gt;confirm&lt;/code&gt; works on it; when the delimiter arrives, the
held byte &lt;em&gt;is&lt;/em&gt; the last, so &lt;code&gt;finalize&lt;/code&gt; treats it as the CRC. What comes
next decides.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-holdback-1&quot;&gt;&lt;a href=&quot;#fn-holdback&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h3 id=&quot;confirm-fold-route-assemble-write-through&quot;&gt;&lt;code&gt;confirm&lt;/code&gt;: fold, route, assemble, write through&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;confirm&lt;/code&gt; receives a byte &lt;code&gt;h&lt;/code&gt; that has just been &lt;em&gt;proven&lt;/em&gt; payload — a
byte arrived behind it — and does the frame’s real bookkeeping (its full
text is in the listing above). Two cases split on whether we have an
opcode yet.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;first confirmed byte is the opcode.&lt;/strong&gt; Record it in &lt;code&gt;lOpcode&lt;/code&gt;, set
&lt;code&gt;lHaveOp&lt;/code&gt;, and fold it into the running CRC. Then one special touch: if the
opcode is &lt;code&gt;0x01&lt;/code&gt;, &lt;code&gt;LOAD_PROGRAM&lt;/code&gt;, reset the write address to &lt;code&gt;0&lt;/code&gt; and clear
the overflow flag, so a load always writes from the top of the store. No
output byte — an opcode writes nothing to memory.&lt;/p&gt;
&lt;p&gt;Every &lt;strong&gt;later confirmed byte is payload.&lt;/strong&gt; Fold it into the CRC, then
shift it into &lt;code&gt;lWordAcc&lt;/code&gt; at the current byte position: &lt;code&gt;zeroExtend h `shiftL` (8 * byteIx)&lt;/code&gt;. That is little-endian assembly by construction —
byte 0 lands in bits 7:0, byte 1 in 15:8, and so on. When &lt;code&gt;lByteIx&lt;/code&gt; reaches
&lt;code&gt;3&lt;/code&gt; a full 32-bit word is complete, and &lt;em&gt;this&lt;/em&gt; is where the load actually
touches memory:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      if&lt;/span&gt;&lt;span&gt; lByteIx s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          let&lt;/span&gt;&lt;span&gt; doWrite &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; isLoad &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; not &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lFull s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              (&lt;/span&gt;&lt;span&gt;addr&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; full&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; lAddr s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; maxBound &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;then&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;lAddr s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; else&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;lAddr s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; lFull s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;           in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lCrcRx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; crc&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lWordAcc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lByteIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lHadPay&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                 ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lAddr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; isLoad &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;then&lt;/span&gt;&lt;span&gt; addr&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;else&lt;/span&gt;&lt;span&gt; lAddr s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                 ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lFull&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; isLoad &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;then&lt;/span&gt;&lt;span&gt; full&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;else&lt;/span&gt;&lt;span&gt; lFull s &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              ,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; doWrite &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;lAddr s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; acc&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; else&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt; )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          (&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lCrcRx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; crc&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lWordAcc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; acc&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lByteIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; lByteIx s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lHadPay&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt; )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The completed word is emitted as &lt;code&gt;Just (lAddr s, acc&#39;)&lt;/code&gt; — a write to the
instruction BRAM at the current slot — provided this is a &lt;code&gt;LOAD&lt;/code&gt; and the
store has not overflowed. Then the address bumps (saturating at &lt;code&gt;maxBound&lt;/code&gt;
— &lt;code&gt;1023&lt;/code&gt; for &lt;code&gt;Unsigned AW&lt;/code&gt; — and latching &lt;code&gt;lFull&lt;/code&gt; rather than wrapping),
the accumulator and byte index reset, and &lt;code&gt;lHadPay&lt;/code&gt; records that payload
was seen. A word that is not a load, or that lands past the 1024-word cap,
just advances the state and writes nothing.&lt;/p&gt;
&lt;p&gt;Notice that the write to the instruction BRAM happens here, &lt;em&gt;mid frame&lt;/em&gt;,
before the CRC is ever checked. The design calls it &lt;strong&gt;write-through&lt;/strong&gt;:
rather than buffer the program to commit it atomically, the loader
scribbles each word into the store the instant it assembles. A frame that
later fails its CRC has already dirtied memory — and that is fine,
because loading and triggering are &lt;em&gt;separate frames&lt;/em&gt;: nothing runs until a
good &lt;code&gt;TRIGGER&lt;/code&gt;, and a bad &lt;code&gt;LOAD&lt;/code&gt; is overwritten by the host’s retry before
any trigger ever fires.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-writethrough-1&quot;&gt;&lt;a href=&quot;#fn-writethrough&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h3 id=&quot;finalize-the-delimiter-s-verdict&quot;&gt;&lt;code&gt;finalize&lt;/code&gt;: the delimiter’s verdict&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;finalize&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;LoaderSt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderOut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;finalize s bad &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; crcCand &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; fromMaybe &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;lHeld s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      crcGood &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; not bad &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; isJust &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lHeld s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; lHaveOp s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; lCrcRx s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; crcCand&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      trigOk &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; crcGood &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; lOpcode s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x02&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; not &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lHadPay s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      s0 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; resetFrame s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;   in&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; trigOk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        then&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s0&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lPhase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Run&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; idleOut&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;startOut&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        else&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; idleOut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The delimiter fired, so the held byte is not payload — there is no byte
behind it — which makes it the &lt;strong&gt;CRC candidate&lt;/strong&gt;, &lt;code&gt;crcCand&lt;/code&gt;. Now the
verdict, in two conjunctions.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;crcGood&lt;/code&gt; demands four things at once: the codec did not report the frame
&lt;code&gt;bad&lt;/code&gt; (no truncated group, no overshoot); a byte was actually held (a
frame with nothing in it holds &lt;code&gt;Nothing&lt;/code&gt;); an opcode was seen; and the CRC
folded over every confirmed byte equals the candidate. Miss any one and
the frame is junk. &lt;code&gt;trigOk&lt;/code&gt; narrows further: a good frame whose opcode is
&lt;code&gt;0x02&lt;/code&gt;, &lt;code&gt;TRIGGER&lt;/code&gt;, carrying no payload. Only that pulses the engine.&lt;/p&gt;
&lt;p&gt;Then the machine resets its frame-parse state either way (&lt;code&gt;resetFrame&lt;/code&gt;
clears the codec, holdback, CRC, opcode, and word assembly — but &lt;em&gt;not&lt;/em&gt;
&lt;code&gt;lAddr&lt;/code&gt;/&lt;code&gt;lFull&lt;/code&gt;, already committed by the write-through) and branches. A
good trigger flips to &lt;code&gt;Run&lt;/code&gt; and pulses &lt;code&gt;startOut&lt;/code&gt; for one cycle. Anything
else — a good &lt;code&gt;LOAD&lt;/code&gt; (words already in memory), a bad frame, an unknown
opcode — resets and stays in &lt;code&gt;RxControl&lt;/code&gt;, silent. That silence is the
whole error policy: the loader never NAKs, never raises an error frame; a
bad frame simply has no effect, and the host, hearing nothing, times out
and re-sends.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-noflow-1&quot;&gt;&lt;a href=&quot;#fn-noflow&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h3 id=&quot;watching-a-trigger-land&quot;&gt;Watching a TRIGGER land&lt;/h3&gt;
&lt;p&gt;Take the smallest interesting frame — a &lt;code&gt;TRIGGER&lt;/code&gt; — and feed it byte by
byte. Its logical stream is one opcode, &lt;code&gt;0x02&lt;/code&gt;, plus its CRC-8, which for
the single byte &lt;code&gt;0x02&lt;/code&gt; works out to &lt;code&gt;0x0E&lt;/code&gt;. &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader-cobs/&quot;&gt;COBS&lt;/a&gt; wraps the pair
&lt;code&gt;02 0E&lt;/code&gt; (no zeros) as a single group &lt;code&gt;03 02 0E&lt;/code&gt;, and the frame layer caps
it with the delimiter. On the wire: &lt;code&gt;03 02 0E 00&lt;/code&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;03&lt;/code&gt;&lt;/strong&gt; — delimiter-watch passes &lt;code&gt;(Just 3, False)&lt;/code&gt;; &lt;code&gt;cobsDecodeStep&lt;/code&gt;
reads a code byte, arms its counter, emits nothing. &lt;code&gt;lHeld&lt;/code&gt; is still
&lt;code&gt;Nothing&lt;/code&gt;. Idle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;02&lt;/code&gt;&lt;/strong&gt; — the decoder emits the data byte &lt;code&gt;02&lt;/code&gt;. &lt;code&gt;lHeld&lt;/code&gt; is &lt;code&gt;Nothing&lt;/code&gt;,
so nothing is confirmed yet; stash &lt;code&gt;lHeld = Just 02&lt;/code&gt;. Idle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;0E&lt;/code&gt;&lt;/strong&gt; — the decoder emits &lt;code&gt;0E&lt;/code&gt;. Now &lt;code&gt;lHeld&lt;/code&gt; holds &lt;code&gt;02&lt;/code&gt;, so
&lt;code&gt;confirm&lt;/code&gt; runs on it: it is the first byte, so &lt;code&gt;lOpcode = 0x02&lt;/code&gt;,
&lt;code&gt;lHaveOp = True&lt;/code&gt;, and &lt;code&gt;lCrcRx = crc8Update 0 0x02 = 0x0E&lt;/code&gt;. Then stash
&lt;code&gt;lHeld = Just 0E&lt;/code&gt;. Idle. &lt;em&gt;The opcode is confirmed; the CRC byte is held.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;00&lt;/code&gt;&lt;/strong&gt; — delimiter. &lt;code&gt;cobsDecodeStep&lt;/code&gt; pulses &lt;code&gt;done&lt;/code&gt;, not &lt;code&gt;bad&lt;/code&gt;.
&lt;code&gt;finalize&lt;/code&gt;: &lt;code&gt;crcCand = 0E&lt;/code&gt;; &lt;code&gt;crcGood&lt;/code&gt; is &lt;code&gt;True&lt;/code&gt; because &lt;code&gt;lCrcRx = 0E&lt;/code&gt;
equals it, an opcode was seen, a byte was held, nothing was malformed.
&lt;code&gt;trigOk&lt;/code&gt; is &lt;code&gt;True&lt;/code&gt; — opcode &lt;code&gt;0x02&lt;/code&gt;, no payload. &lt;strong&gt;Pulse &lt;code&gt;startOut&lt;/code&gt;,
step to &lt;code&gt;Run&lt;/code&gt;.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Read the drama in the last two steps. The held &lt;code&gt;0E&lt;/code&gt; was &lt;em&gt;never&lt;/em&gt; confirmed
as payload — it had no byte behind it — so it stayed the candidate and
was checked, not folded. The opcode &lt;code&gt;02&lt;/code&gt; &lt;em&gt;was&lt;/em&gt; confirmed, at the exact
moment &lt;code&gt;0E&lt;/code&gt; arrived to prove it was not the last byte. The holdback did its
one job perfectly: the opcode got folded, the CRC got checked, and the
pulse fired after the delimiter, never a cycle before the frame was known
good. Corrupt any byte of &lt;code&gt;03 02 0E 00&lt;/code&gt; and step 4’s &lt;code&gt;crcGood&lt;/code&gt; collapses;
no pulse, no run — exactly what a fire-and-forget link wants.&lt;/p&gt;
&lt;h2 id=&quot;drain-the-result-path&quot;&gt;Drain: the result path&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Drain&lt;/code&gt; is the mirror image. Where &lt;code&gt;RxControl&lt;/code&gt; peeled a frame off the
wire, &lt;code&gt;Drain&lt;/code&gt; builds one and pushes it out — the result frame from the
diagram above: opcode &lt;code&gt;0x81&lt;/code&gt;, then the ring’s words as little-endian bytes
(the REVISION at word 0, the engine’s trace records, and the HALT
terminator at the very top of the ring), then a CRC, all COBS-encoded and
capped with a delimiter.&lt;/p&gt;
&lt;p&gt;But the drain is harder than the load, and for a nameable reason: three
different things can make it &lt;strong&gt;wait&lt;/strong&gt;. A BRAM read takes a cycle to
answer. The UART accepts a byte only when &lt;code&gt;txReady&lt;/code&gt;. The COBS encoder
buffers a whole group before it emits. Every one of those is a stall, and
a stall needs a state to wait in and return from. That is why &lt;code&gt;Drain&lt;/code&gt; is
not a loop but the seven-state sub-machine &lt;code&gt;DrainPhase&lt;/code&gt; named earlier —
one state per place the sweep might have to pause.&lt;/p&gt;
&lt;figure class=&quot;drn-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;drn&quot; viewBox=&quot;0 0 760 210&quot; role=&quot;img&quot; aria-labelledby=&quot;drn-t drn-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;drn-t&quot;&gt;The seven-phase drain sequence&lt;/title&gt;
&lt;desc id=&quot;drn-d&quot;&gt;Seven phases left to right: DrOpcode (emit 0x81), DrFetch (drive ring address) and DrLatch (capture ring data) highlighted as the one-cycle BRAM-latency pair, DrWordByte (emit four little-endian bytes), DrCrcByte (emit the CRC, flagged last), DrDrainOut (flush the encoder), and DrDelim (emit the 0x00 delimiter, then return to RxControl). An accent loop runs from DrWordByte back to DrFetch for each further record; a bracket marks DrFetch and DrLatch as the BRAM latency pair; a note says every byte is paced by txReady.&lt;/desc&gt;
&lt;style&gt;
.drn{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.drn .st{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.drn .pair{fill:var(--bg-dim);stroke:var(--accent);stroke-width:2.5}
.drn .wire{stroke:var(--fg-main);stroke-width:2;fill:none}
.drn .hot{stroke:var(--accent);stroke-width:2.5;fill:none}
.drn .brace{stroke:var(--fg-dim);stroke-width:1.5;fill:none}
.drn text{font-family:var(--sans)}
.drn .name{fill:var(--fg-main);font-family:var(--mono);font-size:12px}
.drn .sub{fill:var(--fg-dim);font-size:10.5px}
.drn .lab{fill:var(--fg-main);font-size:11.5px}
.drn .labA{fill:var(--accent);font-size:11.5px}
.drn .ah{fill:var(--fg-main)}
.drn .ahA{fill:var(--accent)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;drn-a&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;drn-aa&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ahA&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;rect class=&quot;st&quot;   x=&quot;20&quot;  y=&quot;95&quot; width=&quot;94&quot;  height=&quot;52&quot; rx=&quot;7&quot;/&gt;
&lt;rect class=&quot;pair&quot; x=&quot;126&quot; y=&quot;95&quot; width=&quot;78&quot;  height=&quot;52&quot; rx=&quot;7&quot;/&gt;
&lt;rect class=&quot;pair&quot; x=&quot;216&quot; y=&quot;95&quot; width=&quot;78&quot;  height=&quot;52&quot; rx=&quot;7&quot;/&gt;
&lt;rect class=&quot;st&quot;   x=&quot;306&quot; y=&quot;95&quot; width=&quot;106&quot; height=&quot;52&quot; rx=&quot;7&quot;/&gt;
&lt;rect class=&quot;st&quot;   x=&quot;424&quot; y=&quot;95&quot; width=&quot;92&quot;  height=&quot;52&quot; rx=&quot;7&quot;/&gt;
&lt;rect class=&quot;st&quot;   x=&quot;528&quot; y=&quot;95&quot; width=&quot;104&quot; height=&quot;52&quot; rx=&quot;7&quot;/&gt;
&lt;rect class=&quot;st&quot;   x=&quot;644&quot; y=&quot;95&quot; width=&quot;90&quot;  height=&quot;52&quot; rx=&quot;7&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;0&quot;   y1=&quot;121&quot; x2=&quot;18&quot;  y2=&quot;121&quot; marker-end=&quot;url(#drn-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;114&quot; y1=&quot;121&quot; x2=&quot;124&quot; y2=&quot;121&quot; marker-end=&quot;url(#drn-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;204&quot; y1=&quot;121&quot; x2=&quot;214&quot; y2=&quot;121&quot; marker-end=&quot;url(#drn-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;294&quot; y1=&quot;121&quot; x2=&quot;304&quot; y2=&quot;121&quot; marker-end=&quot;url(#drn-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;412&quot; y1=&quot;121&quot; x2=&quot;422&quot; y2=&quot;121&quot; marker-end=&quot;url(#drn-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;516&quot; y1=&quot;121&quot; x2=&quot;526&quot; y2=&quot;121&quot; marker-end=&quot;url(#drn-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;632&quot; y1=&quot;121&quot; x2=&quot;642&quot; y2=&quot;121&quot; marker-end=&quot;url(#drn-a)&quot;/&gt;
&lt;path class=&quot;hot&quot; d=&quot;M330,95 C330,50 185,50 185,93&quot; marker-end=&quot;url(#drn-aa)&quot;/&gt;
&lt;path class=&quot;brace&quot; d=&quot;M126,153 V163 H294 V153&quot;/&gt;
&lt;text class=&quot;name&quot; x=&quot;67&quot;  y=&quot;119&quot; text-anchor=&quot;middle&quot;&gt;DrOpcode&lt;/text&gt;
&lt;text class=&quot;sub&quot;  x=&quot;67&quot;  y=&quot;135&quot; text-anchor=&quot;middle&quot;&gt;0x81&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;165&quot; y=&quot;119&quot; text-anchor=&quot;middle&quot;&gt;DrFetch&lt;/text&gt;
&lt;text class=&quot;sub&quot;  x=&quot;165&quot; y=&quot;135&quot; text-anchor=&quot;middle&quot;&gt;addr&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;255&quot; y=&quot;119&quot; text-anchor=&quot;middle&quot;&gt;DrLatch&lt;/text&gt;
&lt;text class=&quot;sub&quot;  x=&quot;255&quot; y=&quot;135&quot; text-anchor=&quot;middle&quot;&gt;data&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;359&quot; y=&quot;119&quot; text-anchor=&quot;middle&quot;&gt;DrWordByte&lt;/text&gt;
&lt;text class=&quot;sub&quot;  x=&quot;359&quot; y=&quot;135&quot; text-anchor=&quot;middle&quot;&gt;×4 LE bytes&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;470&quot; y=&quot;119&quot; text-anchor=&quot;middle&quot;&gt;DrCrcByte&lt;/text&gt;
&lt;text class=&quot;sub&quot;  x=&quot;470&quot; y=&quot;135&quot; text-anchor=&quot;middle&quot;&gt;CRC · last&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;580&quot; y=&quot;119&quot; text-anchor=&quot;middle&quot;&gt;DrDrainOut&lt;/text&gt;
&lt;text class=&quot;sub&quot;  x=&quot;580&quot; y=&quot;135&quot; text-anchor=&quot;middle&quot;&gt;flush enc&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;689&quot; y=&quot;119&quot; text-anchor=&quot;middle&quot;&gt;DrDelim&lt;/text&gt;
&lt;text class=&quot;sub&quot;  x=&quot;689&quot; y=&quot;135&quot; text-anchor=&quot;middle&quot;&gt;0x00&lt;/text&gt;
&lt;text class=&quot;sub&quot;  x=&quot;67&quot;  y=&quot;82&quot;  text-anchor=&quot;middle&quot;&gt;from Run&lt;/text&gt;
&lt;text class=&quot;labA&quot; x=&quot;257&quot; y=&quot;42&quot;  text-anchor=&quot;middle&quot;&gt;more records → next word&lt;/text&gt;
&lt;text class=&quot;lab&quot;  x=&quot;210&quot; y=&quot;180&quot; text-anchor=&quot;middle&quot;&gt;1-cycle BRAM latency&lt;/text&gt;
&lt;text class=&quot;labA&quot; x=&quot;689&quot; y=&quot;166&quot; text-anchor=&quot;middle&quot;&gt;→ RxControl&lt;/text&gt;
&lt;text class=&quot;lab&quot;  x=&quot;560&quot; y=&quot;180&quot; text-anchor=&quot;middle&quot;&gt;every byte paced by txReady&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The drain as a seven-step program. &lt;code&gt;DrOpcode&lt;/code&gt; emits &lt;code&gt;0x81&lt;/code&gt;; the accent &lt;code&gt;DrFetch&lt;/code&gt;/&lt;code&gt;DrLatch&lt;/code&gt; pair drives a ring address and captures the data one cycle later; &lt;code&gt;DrWordByte&lt;/code&gt; emits the word&#39;s four little-endian bytes and loops back to &lt;code&gt;DrFetch&lt;/code&gt; for each further record; &lt;code&gt;DrCrcByte&lt;/code&gt; emits the drain CRC flagged &lt;em&gt;last&lt;/em&gt;; &lt;code&gt;DrDrainOut&lt;/code&gt; clocks the encoder until its final group is flushed; and &lt;code&gt;DrDelim&lt;/code&gt; appends the lone &lt;code&gt;0x00&lt;/code&gt; and returns to &lt;code&gt;RxControl&lt;/code&gt;. Every emitted byte waits for &lt;code&gt;txReady&lt;/code&gt; — the self-pacing a flow-control-less link leans on.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The sub-machine is one &lt;code&gt;case&lt;/code&gt; on &lt;code&gt;lDrn&lt;/code&gt;, shown in full in the listing
above. Three of its arms — &lt;code&gt;DrOpcode&lt;/code&gt;, &lt;code&gt;DrWordByte&lt;/code&gt;, &lt;code&gt;DrCrcByte&lt;/code&gt; — are
one-liners that hand a logical byte to &lt;code&gt;feedByte&lt;/code&gt; (next section); the
other four move memory and manage the encoder. Its only real branching is
the &lt;code&gt;where&lt;/code&gt;-helper that decides where to go after each emitted word byte:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  afterWordByte i s&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; lWIx s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;/=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 3&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; s&amp;#39;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lWIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; lWIx s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; lTerm s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&amp;#39;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lDrn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrCrcByte&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; lDrCnt s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;gt;=&lt;/span&gt;&lt;span&gt; ringPtrIn i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&amp;#39;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lTerm&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lDrn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrFetch&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&amp;#39;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lDrCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; lDrCnt s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; lDrn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DrFetch&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Walk the seven, remembering that the three feeding phases &lt;em&gt;produce&lt;/em&gt; the
logical stream while the other four move memory and manage the encoder.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DrOpcode&lt;/code&gt;&lt;/strong&gt; — feed the opcode &lt;code&gt;0x81&lt;/code&gt;; when the encoder takes it,
advance to &lt;code&gt;DrFetch&lt;/code&gt; and zero the record counter. The frame begins.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DrFetch&lt;/code&gt;&lt;/strong&gt; — drive the ring read address (&lt;code&gt;lDrCnt&lt;/code&gt;, or &lt;code&gt;maxBound&lt;/code&gt;
for the terminator) onto &lt;code&gt;ringAddr&lt;/code&gt;. The BRAM will answer &lt;em&gt;next&lt;/em&gt; cycle,
so this state does nothing else but keep the encoder turning (fed
&lt;code&gt;Nothing&lt;/code&gt;) in case it still has buffered bytes to emit. Advance to
&lt;code&gt;DrLatch&lt;/code&gt;. This is the &lt;em&gt;address&lt;/em&gt; half of a read.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DrLatch&lt;/code&gt;&lt;/strong&gt; — the BRAM’s answer is on &lt;code&gt;ringData&lt;/code&gt; now; latch it into
&lt;code&gt;lWord&lt;/code&gt;, reset the byte cursor &lt;code&gt;lWIx = 0&lt;/code&gt;, advance to &lt;code&gt;DrWordByte&lt;/code&gt;. The
&lt;em&gt;data&lt;/em&gt; half. Fetch-then-latch, two states, is exactly the block RAM’s
one-cycle read latency written out — a contract the two memories get
their own post next to explain, honoured here as a pair of phases.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DrWordByte&lt;/code&gt;&lt;/strong&gt; — feed the current little-endian byte of &lt;code&gt;lWord&lt;/code&gt;
(via &lt;code&gt;leByte&lt;/code&gt;) to the encoder; on consume, run &lt;code&gt;afterWordByte&lt;/code&gt;. That
helper is the drain’s only real branching: if bytes remain in this word
(&lt;code&gt;lWIx /= 3&lt;/code&gt;), bump the cursor; else if this was the terminator, go to
&lt;code&gt;DrCrcByte&lt;/code&gt;; else if the record just fetched was the last one
(&lt;code&gt;lDrCnt + 1 &amp;gt;= ringPtrIn&lt;/code&gt;), set &lt;code&gt;lTerm&lt;/code&gt; and loop to &lt;code&gt;DrFetch&lt;/code&gt; for the
terminator at &lt;code&gt;maxBound&lt;/code&gt;; else bump &lt;code&gt;lDrCnt&lt;/code&gt; and loop to &lt;code&gt;DrFetch&lt;/code&gt; for
the next record. That is the accent loop in the diagram.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DrCrcByte&lt;/code&gt;&lt;/strong&gt; — feed the accumulated drain CRC &lt;code&gt;lCrcTx&lt;/code&gt;, flagged
&lt;code&gt;last&lt;/code&gt; so the encoder knows the logical stream has ended and can flush
its final group. Advance to &lt;code&gt;DrDrainOut&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DrDrainOut&lt;/code&gt;&lt;/strong&gt; — no more input; just clock the encoder (&lt;code&gt;Nothing&lt;/code&gt;)
until it pulses &lt;code&gt;encDone&lt;/code&gt;, meaning its last buffered group is out. Then
&lt;code&gt;DrDelim&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DrDelim&lt;/code&gt;&lt;/strong&gt; — append the one &lt;code&gt;0x00&lt;/code&gt; frame delimiter, paced on
&lt;code&gt;txReady&lt;/code&gt;, reset the frame state, and return to &lt;code&gt;RxControl&lt;/code&gt;. The frame
is complete; the loader is listening again.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One quiet elegance is worth pausing on. &lt;code&gt;DrFetch&lt;/code&gt; and &lt;code&gt;DrLatch&lt;/code&gt; re-use
&lt;code&gt;lWord&lt;/code&gt; for every record, overwriting it each time — safe, because by the
time the machine leaves &lt;code&gt;DrWordByte&lt;/code&gt; all four of the old word’s bytes are
already &lt;em&gt;inside&lt;/em&gt; the encoder’s buffer. The word register is a one-word
window; the encoder is the real buffer, and its 254-byte look-ahead
decouples the fast BRAM sweep from the far slower UART emit.&lt;/p&gt;
&lt;h3 id=&quot;feedbyte-one-byte-to-the-encoder&quot;&gt;&lt;code&gt;feedByte&lt;/code&gt;: one byte to the encoder&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;feedByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  LoaderSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;LoaderSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderSt&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;LoaderSt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; LoaderOut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;feedByte s inp b lst advance &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;enc&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;readyIn&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; mOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; _&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; cobsEncodeStep &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lEnc s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; txReady inp&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      s1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lEnc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; enc&amp;#39;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      s2 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        if&lt;/span&gt;&lt;span&gt; readyIn&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          then&lt;/span&gt;&lt;span&gt; advance s1&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lCrcTx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; lst &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;then&lt;/span&gt;&lt;span&gt; lCrcTx s1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;else&lt;/span&gt;&lt;span&gt; crc8Update &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lCrcTx s1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          else&lt;/span&gt;&lt;span&gt; s1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;   in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; idleOut&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; mOut&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;feedByte&lt;/code&gt; is the drain’s workhorse, and the exact dual of the receive
side’s fold-and-route. It presents one &lt;em&gt;logical&lt;/em&gt; byte &lt;code&gt;b&lt;/code&gt; to
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader-cobs/&quot;&gt;&lt;code&gt;cobsEncodeStep&lt;/code&gt;&lt;/a&gt; along with its &lt;code&gt;last&lt;/code&gt; flag and the downstream-ready
line &lt;code&gt;txReady&lt;/code&gt;. The encoder answers with &lt;code&gt;readyIn&lt;/code&gt; (did it take the
byte?), &lt;code&gt;mOut&lt;/code&gt; (a COBS byte to send, maybe), and a done flag we ignore
here.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;readyIn&lt;/code&gt; — the byte was consumed — fold it into the drain CRC
(&lt;em&gt;unless&lt;/em&gt; it is the CRC byte itself, flagged &lt;code&gt;lst&lt;/code&gt;; you do not CRC the
CRC) and run the &lt;code&gt;advance&lt;/code&gt; continuation to step the phase. If not consumed
— the encoder is mid-group, or &lt;code&gt;txReady&lt;/code&gt; is low — hold: same byte, same
phase, next cycle. Either way, route &lt;code&gt;mOut&lt;/code&gt; to &lt;code&gt;txByte&lt;/code&gt;. So every output
byte is gated on &lt;code&gt;txReady&lt;/code&gt;, and the generator runs exactly as fast as the
encoder consumes, which runs exactly as fast as the UART drains.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-noflow-2&quot;&gt;&lt;a href=&quot;#fn-noflow&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt;
The separation is the pretty part: the three feeding phases decide &lt;em&gt;which
byte comes next&lt;/em&gt;; &lt;code&gt;feedByte&lt;/code&gt; owns &lt;em&gt;how to hand it over and when to
advance&lt;/em&gt;. Three call sites, one rule.&lt;/p&gt;
&lt;h3 id=&quot;lebyte-the-little-endian-tap&quot;&gt;&lt;code&gt;leByte&lt;/code&gt;: the little-endian tap&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;leByte&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;leByte w i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; slice d7 d0 w&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; slice d15 d8 w&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  2&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; slice d23 d16 w&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; slice d31 d24 w&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The trivial helper, listed for symmetry: byte &lt;code&gt;i&lt;/code&gt; of a 32-bit word,
little-endian — &lt;code&gt;slice d7 d0&lt;/code&gt; is the low byte, up to &lt;code&gt;slice d31 d24&lt;/code&gt; the
high. It is the exact inverse of the receive side’s &lt;code&gt;lWordAcc `shiftL` (8 * byteIx)&lt;/code&gt;: the load &lt;em&gt;packs&lt;/em&gt; four little-endian bytes into a word, the
drain &lt;em&gt;unpacks&lt;/em&gt; a word into four. The wire is little-endian in both
directions, and these two lines are the two ends of that one agreement.&lt;/p&gt;
&lt;h3 id=&quot;watching-the-minimal-drain&quot;&gt;Watching the minimal drain&lt;/h3&gt;
&lt;p&gt;The smallest possible drain is a ring with no records at all — just the
REVISION word and the terminator. Say &lt;code&gt;ringPtrIn = 1&lt;/code&gt;, &lt;code&gt;ring[0] = 0x0001_0000&lt;/code&gt; (REVISION), and the terminator at the top of the ring is
&lt;code&gt;0xC000_0000&lt;/code&gt;. The logical stream the phases produce should be &lt;code&gt;0x81&lt;/code&gt;, then
the four little-endian bytes of each word, then the CRC:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DrOpcode&lt;/code&gt;&lt;/strong&gt; — feed &lt;code&gt;0x81&lt;/code&gt;; consumed, so fold it into &lt;code&gt;lCrcTx&lt;/code&gt;, go
to &lt;code&gt;DrFetch&lt;/code&gt;, &lt;code&gt;lDrCnt = 0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DrFetch&lt;/code&gt;&lt;/strong&gt; — drive &lt;code&gt;ringAddr = 0&lt;/code&gt;. → &lt;code&gt;DrLatch&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DrLatch&lt;/code&gt;&lt;/strong&gt; — latch &lt;code&gt;lWord = 0x0001_0000&lt;/code&gt;, &lt;code&gt;lWIx = 0&lt;/code&gt;. → &lt;code&gt;DrWordByte&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DrWordByte&lt;/code&gt; ×4&lt;/strong&gt; — feed &lt;code&gt;00 00 01 00&lt;/code&gt;, the LE bytes of
&lt;code&gt;0x0001_0000&lt;/code&gt;. After the fourth, &lt;code&gt;lWIx&lt;/code&gt; was &lt;code&gt;3&lt;/code&gt;, not the terminator,
and &lt;code&gt;lDrCnt + 1 = 1 &amp;gt;= ringPtrIn = 1&lt;/code&gt;, so set &lt;code&gt;lTerm&lt;/code&gt; and loop to
&lt;code&gt;DrFetch&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DrFetch&lt;/code&gt;&lt;/strong&gt; (terminator) — drive &lt;code&gt;ringAddr = maxBound&lt;/code&gt;. → &lt;code&gt;DrLatch&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DrLatch&lt;/code&gt;&lt;/strong&gt; — latch &lt;code&gt;lWord = 0xC000_0000&lt;/code&gt;, &lt;code&gt;lWIx = 0&lt;/code&gt;. → &lt;code&gt;DrWordByte&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DrWordByte&lt;/code&gt; ×4&lt;/strong&gt; — feed &lt;code&gt;00 00 00 C0&lt;/code&gt;. After the fourth, &lt;code&gt;lWIx = 3&lt;/code&gt;
and &lt;code&gt;lTerm&lt;/code&gt; is set, so → &lt;code&gt;DrCrcByte&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DrCrcByte&lt;/code&gt;&lt;/strong&gt; — feed &lt;code&gt;lCrcTx&lt;/code&gt;, the CRC folded over &lt;code&gt;0x81&lt;/code&gt; and all
eight word bytes, flagged &lt;code&gt;last&lt;/code&gt;. → &lt;code&gt;DrDrainOut&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DrDrainOut&lt;/code&gt;&lt;/strong&gt; — clock the encoder until &lt;code&gt;encDone&lt;/code&gt;. → &lt;code&gt;DrDelim&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DrDelim&lt;/code&gt;&lt;/strong&gt; — emit &lt;code&gt;0x00&lt;/code&gt;, reset, → &lt;code&gt;RxControl&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The logical stream handed to the encoder is &lt;code&gt;81 · 00 00 01 00 · 00 00 00 C0 · crc&lt;/code&gt; — ten bytes, and notice how thoroughly zero-riddled it is. A
real ring word is full of &lt;code&gt;0x00&lt;/code&gt;, which is &lt;em&gt;exactly&lt;/em&gt; why COBS is in this
pipeline at all: the encoder stuffs every one of those zeros out, the loader
caps the result with a single &lt;code&gt;0x00&lt;/code&gt;, and the whole thing comes out equal,
byte for byte, to &lt;code&gt;Tamal.Wire&lt;/code&gt;’s pure &lt;code&gt;encodeResult [0x0001_0000, 0xC000_0000]&lt;/code&gt;. Which is precisely what the test suite asserts — so let us
turn to it.&lt;/p&gt;
&lt;h2 id=&quot;the-tests&quot;&gt;The tests&lt;/h2&gt;
&lt;p&gt;The loader is impure, so unlike the pure COBS steps you cannot apply it to
a value and read the answer — you build a &lt;code&gt;Signal&lt;/code&gt;, &lt;code&gt;sampleN&lt;/code&gt; it, and
compare. But the &lt;em&gt;discipline&lt;/em&gt; is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader-cobs/&quot;&gt;COBS post&lt;/a&gt;’s, up one layer: a
pure reference is the oracle, and the streaming machine is held to it byte
for byte. Where COBS checked itself against pure &lt;code&gt;Tamal.Wire.Cobs&lt;/code&gt;, the
loader checks its whole frame layer against pure &lt;code&gt;Tamal.Wire&lt;/code&gt; —
&lt;code&gt;encodeControl&lt;/code&gt; and &lt;code&gt;encodeResult&lt;/code&gt;, the list-to-list functions that &lt;em&gt;are&lt;/em&gt;
the wire format.&lt;/p&gt;
&lt;p&gt;The load path tests feed the exact bytes of &lt;code&gt;encodeControl (LoadProgram ws)&lt;/code&gt; and assert the write stream:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;LOAD_PROGRAM writes the exact words at 0,1,2,..&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ws &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Gen.&lt;/span&gt;&lt;span&gt;list &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Range.&lt;/span&gt;&lt;span&gt;linear &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 20&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; genWord&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; bytes &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; encodeControl &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;LoadProgram&lt;/span&gt;&lt;span&gt; ws&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  simInstrWr &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fmap &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Just&lt;/span&gt;&lt;span&gt; bytes&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ===&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fromIntegral i&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; w&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; w&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;-&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;zip &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Int&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ..&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; ws&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For random programs, the words land at &lt;code&gt;0, 1, 2, …&lt;/code&gt; with the right
values — the write-through, the little-endian assembly, and the holdback
all proven in one line. A &lt;code&gt;TRIGGER&lt;/code&gt; pulses &lt;code&gt;startOut&lt;/code&gt; exactly once and only
after the frame; a &lt;code&gt;LOAD&lt;/code&gt; pulses it never. There is one lovely detail in
the harness: every stimulus is led by an idle cycle, because &lt;code&gt;sampleN&lt;/code&gt;
asserts the reset on cycle 0 and a byte fed then would be lost — &lt;em&gt;exactly
as it would be lost in hardware&lt;/em&gt;, where the line idles before the first
frame. The test models the reset hazard rather than papering over it.&lt;/p&gt;
&lt;p&gt;The drain tests are the clever ones, because the drain reads a memory the
test must supply. The rig closes the ring-BRAM loop with a &lt;code&gt;register&lt;/code&gt; for
the one-cycle latency and a pure &lt;code&gt;ringModel&lt;/code&gt; as the memory:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;drainRig lookupRing ringPtrV rxs txr hlt &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; txByte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; loaderOut&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  loaderOut &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; loader loaderIn&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ringDataS &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; register &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;lookupRing &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ringAddr &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; loaderOut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  loaderIn  &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; LoaderIn&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; rxs &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span&gt; txr &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span&gt; hlt &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span&gt; pure ringPtrV &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;*&amp;gt;&lt;/span&gt;&lt;span&gt; ringDataS&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;register 0 (lookupRing &amp;lt;$&amp;gt; ringAddr)&lt;/code&gt; &lt;em&gt;is&lt;/em&gt; the BRAM: the loader’s own
&lt;code&gt;ringAddr&lt;/code&gt; output, delayed one cycle and looked up in the model, becomes
its &lt;code&gt;ringData&lt;/code&gt; input — the fetch/latch dance closed into a feedback loop.
On top of it, one property drives a full lifecycle — &lt;code&gt;TRIGGER&lt;/code&gt;, &lt;code&gt;Run&lt;/code&gt;,
&lt;code&gt;halted&lt;/code&gt;, &lt;code&gt;Drain&lt;/code&gt; — and demands the drained bytes equal the oracle:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;drain stream == encodeResult (records ++ terminator)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  records &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Gen.&lt;/span&gt;&lt;span&gt;list &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Range.&lt;/span&gt;&lt;span&gt;linear &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 24&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; genWord&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  term &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genWord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  simDrain records term &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;True&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ===&lt;/span&gt;&lt;span&gt; encodeResult &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;records &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;term&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then the test that justifies the entire no-flow-control design: run the
same drain with &lt;code&gt;txReady&lt;/code&gt; chopped to a stuttering &lt;code&gt;[True, False, True, True, False]&lt;/code&gt; and assert the output is &lt;strong&gt;byte-identical&lt;/strong&gt;. If the
self-pacing has a leak — a dropped byte, a duplicated one — this goes
red. It stays green, which is the proof that the loader may share an
unpaced link with a host and never corrupt a frame.&lt;/p&gt;
&lt;p&gt;The robustness cases round it out, each one a design decision made
falsifiable: flip any single bit of a &lt;code&gt;TRIGGER&lt;/code&gt; and no run ever starts
(property over the flip position); over-load 1100 words and the addresses
saturate at &lt;code&gt;1023&lt;/code&gt; with exactly 1024 writes; send two &lt;code&gt;LOAD&lt;/code&gt;s and each
writes from address 0 (the overwrite that makes a failed load harmless);
trigger-halt twice and the ring drains twice (re-runnable). The shape is
the house style, now familiar: a pure model that is the &lt;em&gt;meaning&lt;/em&gt;, a
streaming machine that is the &lt;em&gt;silicon&lt;/em&gt;, and property tests that weld the
two together over a fresh shower of inputs every run.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-purestream-1&quot;&gt;&lt;a href=&quot;#fn-purestream&quot;&gt;[6]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-we-read&quot;&gt;What we read&lt;/h2&gt;
&lt;p&gt;Three lives, one clock. &lt;code&gt;RxControl&lt;/code&gt; listens, peeling a frame layer by
layer and holding one byte back so the trailing CRC never masquerades as
payload, writing each little-endian word straight through to the
instruction store and committing nothing until a separate &lt;code&gt;TRIGGER&lt;/code&gt; says
go. &lt;code&gt;Run&lt;/code&gt; waits on a single bit. &lt;code&gt;Drain&lt;/code&gt; sweeps the ring out in seven
patient phases, dancing with the memory’s latency, folding a CRC,
flushing the encoder, and pacing every byte off &lt;code&gt;txReady&lt;/code&gt; so an unpaced
link never overruns. One &lt;code&gt;mealy&lt;/code&gt;, one eighteen-field memory that is really
four small ones, one &lt;code&gt;case&lt;/code&gt; on the phase.&lt;/p&gt;
&lt;p&gt;And inside it, everything the series built. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader-cobs/&quot;&gt;COBS&lt;/a&gt; codec,
threaded through the loader’s clock in &lt;code&gt;lDec&lt;/code&gt; and &lt;code&gt;lEnc&lt;/code&gt; — the promise
that post made, kept. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt;, folded over both directions in
&lt;code&gt;lCrcRx&lt;/code&gt; and &lt;code&gt;lCrcTx&lt;/code&gt;. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;UART&lt;/a&gt;’s four wires, finally connected.
The two block RAMs, driven on the ports the engine leaves free. The
clock that six pure steps kept deferring to “someone else” lives here, and
the someone has a name.&lt;/p&gt;
&lt;p&gt;What the loader loads, triggers, and drains is a &lt;em&gt;program&lt;/em&gt; — run by a
machine we have circled for the entire series and never once opened. The
instruction store the loader fills, the trace ring it sweeps, the &lt;code&gt;halted&lt;/code&gt;
it waits on and the &lt;code&gt;startOut&lt;/code&gt; it pulses: those are all the engine’s, and
the engine is the last door. We open it soon — but first, a shorter
breath: the two memories the loader has spent this post driving, and that
the engine is about to live between, deserve a look of their own. Then, at
last, we walk through the door.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-ports&quot;&gt;
&lt;p&gt;The collision-free claim is the block-RAM design’s
port-ownership contract cashed in. &lt;code&gt;RxControl&lt;/code&gt;/&lt;code&gt;Drain&lt;/code&gt; and &lt;code&gt;Run&lt;/code&gt; are
different lives, never concurrent, so the loader (instruction write, ring
read) and the engine (instruction read, ring write) touch disjoint ports
at disjoint times. The &lt;em&gt;schedule&lt;/em&gt; is the arbitration — which is what
lets the loader be one more &lt;code&gt;mealy&lt;/code&gt; beside the engine rather than a bus
master negotiating for access. &lt;a href=&quot;#fr-ports-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-mealys&quot;&gt;
&lt;p&gt;The design doc reached for &lt;code&gt;mealyS&lt;/code&gt; — the State-monad flavour
of &lt;code&gt;mealy&lt;/code&gt;, where the transition is written in do-notation over &lt;code&gt;State s&lt;/code&gt;
rather than as an explicit &lt;code&gt;s -&amp;gt; i -&amp;gt; (s, o)&lt;/code&gt; — and flagged it as “the
idiom for exactly this long sequential FSM.” The shipped code uses plain
&lt;code&gt;mealy&lt;/code&gt; instead, “matching the engine lift.” An honest divergence: the
State-monad sugar would have read a shade cleaner in &lt;code&gt;drainStep&lt;/code&gt;, but
keeping every top-level block lifted the same way — engine and loader
both plain &lt;code&gt;mealy&lt;/code&gt; over an explicit step — won out: a codebase where
every machine wears the same silhouette reads easier than one where each
picks its favourite sugar. &lt;a href=&quot;#fr-mealys-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-holdback&quot;&gt;
&lt;p&gt;The one-byte holdback is a general streaming idiom worth
naming, because it recurs anywhere a stream’s last element is special and
unmarked. You cannot know an element is the last until the stream ends, so
if the last one needs different treatment — a checksum, a terminator, a
flush — you buffer exactly one and always act on the &lt;em&gt;previous&lt;/em&gt;, then
handle the straggler when the end arrives. It is the streaming dual of
&lt;code&gt;init&lt;/code&gt; and &lt;code&gt;last&lt;/code&gt; on a list: the holdback is &lt;code&gt;init&lt;/code&gt; (everything but the
last), and the delimiter handler is &lt;code&gt;last&lt;/code&gt;. One register buys a
one-element look-behind, which is all it takes. &lt;a href=&quot;#fr-holdback-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-writethrough&quot;&gt;
&lt;p&gt;Write-through trades atomicity for a BRAM. The
alternative — buffer the whole program, check its CRC, and commit only on
success — needs somewhere to hold up to 1024 words, a second
four-kilobyte block RAM, to guard against a failure the protocol already
handles. Because &lt;code&gt;LOAD_PROGRAM&lt;/code&gt; and &lt;code&gt;TRIGGER&lt;/code&gt; are separate frames (a
deliberate wire-format choice), a corrupted load never runs: nothing runs
until a good trigger, and a well-behaved host re-sends a failed load —
overwriting the garbage from address 0 — before it ever triggers. So the
loader gets atomic-&lt;em&gt;enough&lt;/em&gt; behaviour for free, and the “two LOADs each
write from 0” test proves the overwrite works. &lt;a href=&quot;#fr-writethrough-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-noflow&quot;&gt;
&lt;p&gt;The Arty A7’s FTDI USB-UART has no RTS/CTS wired to the FPGA —
a board fact, not a choice — so there is no hardware flow control in
either direction, and the loader carries none to match. It survives on
three things: the receive side cannot overrun (per-byte work is a handful
of cycles against a ~500-cycle byte period at 2 Mbaud); the drain
self-paces off &lt;code&gt;txReady&lt;/code&gt;; and the backstop for anything that slips is the
whole-frame &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; plus fire-and-forget — a bad frame is dropped, the
host times out and re-runs, byte-reproducibly. Lower the baud (a top-level
&lt;code&gt;SNat&lt;/code&gt;) if it ever bites. &lt;a href=&quot;#fr-noflow-1&quot;&gt;↩&lt;/a&gt; &lt;a href=&quot;#fr-noflow-2&quot;&gt;↩2&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-purestream&quot;&gt;
&lt;p&gt;The same two-implementations pattern the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-loader-cobs/&quot;&gt;COBS post&lt;/a&gt;
called the house style, now one layer up. Down there it was pure
&lt;code&gt;Tamal.Wire.Cobs&lt;/code&gt; beside streaming &lt;code&gt;Tamal.Loader.Cobs&lt;/code&gt;; up here it is pure
&lt;code&gt;Tamal.Wire&lt;/code&gt; — the frame format as list transforms — beside the
streaming loader. You write the frame codec twice: once as a fold over
lists, where you can &lt;em&gt;think&lt;/em&gt; without back-pressure or cycle timing, and
once as a clocked machine that must survive a stalling consumer and a
dribbling producer. The property tests weld them, so the gnarly clocked
version is never the only place the format is written down. &lt;a href=&quot;#fr-purestream-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Tamal: The COBS codec</title>
    <published>2026-07-25T09:00:00+00:00</published>
    <updated>2026-07-25T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-loader-cobs/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-loader-cobs/</id>
    <summary type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt; built a byte &lt;em&gt;pipe&lt;/em&gt;; &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;yesterday&lt;/a&gt;
we sealed the two halves into a single word — &lt;em&gt;the UART&lt;/em&gt; — and left four
wires hanging at the loader’s door. Today we walk through that door. But the
first thing behind it is not a state machine. It is an answer to a question the
pipe cannot answer for itself: a bare stream of bytes has no seams, so where
does one message end and the next begin?&lt;/p&gt;
&lt;p&gt;I have carried my answer to that question for a little while, and I
did not find it in a textbook. I found it in &lt;a rel=&quot;external&quot; href=&quot;https://github.com/jamesmunns/postcard-rpc&quot;&gt;postcard-rpc&lt;/a&gt;,
&lt;a rel=&quot;external&quot; href=&quot;https://www.linkedin.com/in/james-munns-8a42b429/&quot;&gt;James Munns’&lt;/a&gt; crate for speaking to embedded targets over a
wire. I liked it enough to become one of its maintainers, and I lean
on it — that is, I depend on it — hard on &lt;a rel=&quot;external&quot; href=&quot;https://github.com/OpenDevicePartnership/pico-de-gallo&quot;&gt;pico de gallo&lt;/a&gt; 🌶,
where it frames every request and every response that crosses between
host and firmware. So when Tamal’s loader needed a way to
tell one message from the next on a bare UART, I did not go looking. I
already knew what I wanted, and I reached for &lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing&quot;&gt;&lt;strong&gt;COBS&lt;/strong&gt;: Consistent
Overhead Byte
Stuffing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;COBS earns its keep with a property that sounds too good until you see how
plainly it is bought: it removes &lt;em&gt;every&lt;/em&gt; &lt;code&gt;0x00&lt;/code&gt; byte from a payload, at a cost
of at most one extra byte per 254, so that a single &lt;code&gt;0x00&lt;/code&gt; can be reserved —
unambiguously, forever — to mean &lt;em&gt;end of frame&lt;/em&gt;. The loader decodes the
inbound stream on the way in and encodes its replies on the way out, and both
directions are the subject of this post.&lt;/p&gt;
&lt;p&gt;Like every Tamal block before it the codec fits in a screenful and change — but
unlike the UART halves it holds no clock of its own. What follows are two &lt;em&gt;pure&lt;/em&gt;
step functions; the loader is the machine that clocks them. We will spend almost
all our words on the two that matter, &lt;code&gt;cobsDecodeStep&lt;/code&gt; and &lt;code&gt;cobsEncodeStep&lt;/code&gt;, and
wave past the scaffolding around them, which by now is a ceremony we have read
five times.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt; built a byte &lt;em&gt;pipe&lt;/em&gt;; &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;yesterday&lt;/a&gt;
we sealed the two halves into a single word — &lt;em&gt;the UART&lt;/em&gt; — and left four
wires hanging at the loader’s door. Today we walk through that door. But the
first thing behind it is not a state machine. It is an answer to a question the
pipe cannot answer for itself: a bare stream of bytes has no seams, so where
does one message end and the next begin?&lt;/p&gt;
&lt;p&gt;I have carried my answer to that question for a little while, and I
did not find it in a textbook. I found it in &lt;a rel=&quot;external&quot; href=&quot;https://github.com/jamesmunns/postcard-rpc&quot;&gt;postcard-rpc&lt;/a&gt;,
&lt;a rel=&quot;external&quot; href=&quot;https://www.linkedin.com/in/james-munns-8a42b429/&quot;&gt;James Munns’&lt;/a&gt; crate for speaking to embedded targets over a
wire. I liked it enough to become one of its maintainers, and I lean
on it — that is, I depend on it — hard on &lt;a rel=&quot;external&quot; href=&quot;https://github.com/OpenDevicePartnership/pico-de-gallo&quot;&gt;pico de gallo&lt;/a&gt; 🌶,
where it frames every request and every response that crosses between
host and firmware.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-postcard-1&quot;&gt;&lt;a href=&quot;#fn-postcard&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; So when Tamal’s loader needed a way to
tell one message from the next on a bare UART, I did not go looking. I
already knew what I wanted, and I reached for &lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing&quot;&gt;&lt;strong&gt;COBS&lt;/strong&gt;: Consistent
Overhead Byte
Stuffing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;COBS earns its keep with a property that sounds too good until you see how
plainly it is bought: it removes &lt;em&gt;every&lt;/em&gt; &lt;code&gt;0x00&lt;/code&gt; byte from a payload, at a cost
of at most one extra byte per 254, so that a single &lt;code&gt;0x00&lt;/code&gt; can be reserved —
unambiguously, forever — to mean &lt;em&gt;end of frame&lt;/em&gt;. The loader decodes the
inbound stream on the way in and encodes its replies on the way out, and both
directions are the subject of this post.&lt;/p&gt;
&lt;p&gt;Like every Tamal block before it the codec fits in a screenful and change — but
unlike the UART halves it holds no clock of its own. What follows are two &lt;em&gt;pure&lt;/em&gt;
step functions; the loader is the machine that clocks them. We will spend almost
all our words on the two that matter, &lt;code&gt;cobsDecodeStep&lt;/code&gt; and &lt;code&gt;cobsEncodeStep&lt;/code&gt;, and
wave past the scaffolding around them, which by now is a ceremony we have read
five times.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;the-entire-source&quot;&gt;The entire source&lt;/h2&gt;
&lt;p&gt;Minus the license header and the doc-comments — and, this once, minus the
longer inline notes on the encoder, which we will read in place where they
belong — here is &lt;code&gt;src/Tamal/Loader/Cobs.hs&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Loader.Cobs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; DecSt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; initDec&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; cobsDecodeStep&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; EncSt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; initEnc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; cobsEncodeStep&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Clash.Prelude&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; DecSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DecSt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dFull&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dPend&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dGot&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;initDec&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; DecSt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;initDec &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; DecSt&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; EncMode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; EFilling&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; EEmitting&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; EncSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; EncSt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eMode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; EncMode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eBuf&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Vec&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 254&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eFill&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ePend&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eFinal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eLast&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;initEnc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; EncSt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;initEnc &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; EncSt&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; EFilling&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;repeat &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;cobsDecodeStep&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; DecSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;DecSt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cobsDecodeStep s &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mIn&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; frameEnd&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; frameEnd &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;initDec&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; not &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dGot s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ||&lt;/span&gt;&lt;span&gt; dCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;/=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; mIn &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;      Nothing&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;      Just&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        |&lt;/span&gt;&lt;span&gt; dCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            let&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;out&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; s1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                  if&lt;/span&gt;&lt;span&gt; dPend s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                    then&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Just&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;dPend&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dGot&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                    else&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;dGot&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                s2 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; startGroup s1 b&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;             in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;out&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            let&lt;/span&gt;&lt;span&gt; cnt&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; dCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                  s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; cnt&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dGot&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dPend&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                        if&lt;/span&gt;&lt;span&gt; cnt&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                          then&lt;/span&gt;&lt;span&gt; not &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dFull s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                          else&lt;/span&gt;&lt;span&gt; dPend s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;             in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Just&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  startGroup st c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; full &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 255&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        n &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;unpack c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; c is 1..255 (never 0), so n is 0..254&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; n &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          then&lt;/span&gt;&lt;span&gt; st&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;dCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dFull&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; full&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dPend&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; not full&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          else&lt;/span&gt;&lt;span&gt; st&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;dCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dFull&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; full&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;cobsEncodeStep&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; EncSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;EncSt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Bool&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cobsEncodeStep s &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mIn&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; dsReady&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; eMode s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  EFilling&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; mIn &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    Nothing&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      |&lt;/span&gt;&lt;span&gt; eFill s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 254&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eMode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; EEmitting&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ePend&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      |&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eMode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; EEmitting&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eFinal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eLast&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      |&lt;/span&gt;&lt;span&gt; lst &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          (&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;store s b&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eMode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; EEmitting&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eLast&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eFinal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          (&lt;/span&gt;&lt;span&gt;store s b&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  EEmitting&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; not dsReady &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; eIx s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; eFill s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        let&lt;/span&gt;&lt;span&gt; out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; eIx s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; then&lt;/span&gt;&lt;span&gt; fromIntegral &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;eFill s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; +&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; else&lt;/span&gt;&lt;span&gt; eBuf s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;!!&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;eIx s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;         in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; eIx s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; out&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; eFinal s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eFill&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eFinal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; ePend s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;        Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;pb&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; pl&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          |&lt;/span&gt;&lt;span&gt; pb &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eMode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; EEmitting&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eFill&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ePend&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eFinal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; pl&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eLast&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; pl&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          |&lt;/span&gt;&lt;span&gt; pl &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              (&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;store s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eFill&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; pb&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eMode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; EEmitting&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ePend&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eLast&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              (&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;store s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eFill&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; pb&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eMode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; EFilling&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ePend&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;        Nothing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          |&lt;/span&gt;&lt;span&gt; eLast s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;initEnc&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eMode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; EFilling&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eFill&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  store st b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; st&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eBuf&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; replace &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;eFill st&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;eBuf st&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eFill&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; eFill st &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Six things, top to bottom: an export list that is wider than any we have seen; a
four-field &lt;code&gt;DecSt&lt;/code&gt; record; a two-constructor &lt;code&gt;EncMode&lt;/code&gt; sum and a seven-field
&lt;code&gt;EncSt&lt;/code&gt; record; two one-line initialisers; and the two step functions that are
the whole point. We will glance at the first four and then slow right down for
the last two.&lt;/p&gt;
&lt;h2 id=&quot;the-ritual-one-more-time&quot;&gt;The ritual, one more time&lt;/h2&gt;
&lt;p&gt;The opening beat is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; module’s, played a sixth time, so I will be
quick — but there is one genuinely new thing in it, and it is worth a sentence.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Loader.Cobs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; DecSt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; initDec&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; cobsDecodeStep&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; EncSt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; initEnc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; cobsEncodeStep&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Clash.Prelude&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every module so far has had exactly &lt;em&gt;one&lt;/em&gt; name on its export list — &lt;code&gt;crc8Update&lt;/code&gt;,
&lt;code&gt;uartTx&lt;/code&gt;, &lt;code&gt;uartRx&lt;/code&gt;, &lt;code&gt;uart&lt;/code&gt;. This one has six. The wall still has a door, but the
door is wider, and the reason is a real design fact rather than an oversight: the
two &lt;code&gt;St&lt;/code&gt; types and their initialisers leave the file &lt;em&gt;on purpose&lt;/em&gt;. This module is
not a self-contained machine you plug a clock into; it is a &lt;strong&gt;component the
loader embeds&lt;/strong&gt;. The loader holds a &lt;code&gt;DecSt&lt;/code&gt; and an &lt;code&gt;EncSt&lt;/code&gt; inside its &lt;em&gt;own&lt;/em&gt;
state record and threads them through its own &lt;code&gt;mealy&lt;/code&gt;, so it must be handed the
types to store and the &lt;code&gt;initDec&lt;/code&gt;/&lt;code&gt;initEnc&lt;/code&gt; values to seed them. The export list
is exactly as wide as that contract requires and no wider: two opaque state
types, two seeds, two steps. &lt;code&gt;EncMode&lt;/code&gt;, &lt;code&gt;dCnt&lt;/code&gt;, &lt;code&gt;store&lt;/code&gt;, &lt;code&gt;startGroup&lt;/code&gt; — the
genuinely internal machinery — all stay sealed behind the wall.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;import Clash.Prelude&lt;/code&gt; is the same prelude swap the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC post&lt;/a&gt; dwelt on —
out goes ordinary Haskell’s furniture, in comes the hardware vocabulary. Here it
is supplying &lt;code&gt;Vec 254&lt;/code&gt;, &lt;code&gt;Unsigned 8&lt;/code&gt;, &lt;code&gt;BitVector 8&lt;/code&gt;, &lt;code&gt;Maybe&lt;/code&gt;, and the &lt;code&gt;Vec&lt;/code&gt;
operations &lt;code&gt;repeat&lt;/code&gt;, &lt;code&gt;replace&lt;/code&gt;, and &lt;code&gt;!!&lt;/code&gt; that the encoder’s buffer leans on. I
will not re-derive it; the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt; posts did that at
length.&lt;/p&gt;
&lt;p&gt;The two state records are the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt;’s sum-and-product story once
more, and I will gloss them in a single pass because the step functions are
written entirely in terms of their fields. &lt;code&gt;DecSt&lt;/code&gt; is what the decoder carries
tick to tick:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;dCnt&lt;/code&gt;&lt;/strong&gt; — data bytes still owed in the current group; &lt;code&gt;0&lt;/code&gt; means &lt;em&gt;the next
byte is a code byte&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;dFull&lt;/code&gt;&lt;/strong&gt; — was this group’s code &lt;code&gt;255&lt;/code&gt;? A full group is special: it carries
&lt;em&gt;no&lt;/em&gt; implied zero.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;dPend&lt;/code&gt;&lt;/strong&gt; — do we owe an injected &lt;code&gt;0x00&lt;/code&gt; before the next code byte?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;dGot&lt;/code&gt;&lt;/strong&gt; — has any byte at all arrived this frame? (Only used to flag an empty
frame as malformed.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;EncSt&lt;/code&gt; carries more, because encoding buffers a whole group before it can know
the group’s length:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;eMode&lt;/code&gt;&lt;/strong&gt; — &lt;code&gt;EFilling&lt;/code&gt; (accumulating a group) or &lt;code&gt;EEmitting&lt;/code&gt; (driving
&lt;code&gt;code ++ group&lt;/code&gt; out).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;eBuf&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;eFill&lt;/code&gt;&lt;/strong&gt; — the up-to-254-byte group buffer and how many bytes are
in it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;eIx&lt;/code&gt;&lt;/strong&gt; — the emit cursor: &lt;code&gt;0&lt;/code&gt; is the code byte, &lt;code&gt;1..eFill&lt;/code&gt; the data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ePend&lt;/code&gt;&lt;/strong&gt; — a byte stashed when a full 254-group is flushed; it will start the
next group, and it remembers whether it was the stream’s last byte.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;eFinal&lt;/code&gt;&lt;/strong&gt; — is a final empty group still owed? (The last input byte was
&lt;code&gt;0x00&lt;/code&gt;.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;eLast&lt;/code&gt;&lt;/strong&gt; — has the input stream ended?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both initialisers are the resting state written out — &lt;code&gt;initDec = DecSt 0 False False False&lt;/code&gt;, &lt;code&gt;initEnc = EncSt EFilling (repeat 0) 0 0 Nothing False False&lt;/code&gt; —
and there is nothing in either worth a paragraph.&lt;/p&gt;
&lt;p&gt;The one structural thing worth saying out loud before we start: look at the
types of the two steps and notice what is &lt;strong&gt;absent&lt;/strong&gt;.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;cobsDecodeStep&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; DecSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;DecSt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;cobsEncodeStep&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; EncSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;EncSt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Bool&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No &lt;code&gt;Signal&lt;/code&gt;. No &lt;code&gt;HiddenClockResetEnable&lt;/code&gt;. No &lt;code&gt;mealy&lt;/code&gt;. These are &lt;code&gt;s -&amp;gt; i -&amp;gt; (s, o)&lt;/code&gt; in the flesh — the exact shape the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; taught us to read —
but &lt;em&gt;un-lifted&lt;/em&gt;, pure functions with no clock anywhere in sight. That is
deliberate, and it is the same division of labour the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt;’s &lt;code&gt;step&lt;/code&gt; had:
the codec describes &lt;em&gt;what one byte does to the state&lt;/em&gt;, and someone else owns the
register that makes it sequential. That someone is the loader, which wraps both
steps in its own &lt;code&gt;mealy&lt;/code&gt;. Everything below is combinational; the clock is a
floor above.&lt;/p&gt;
&lt;h2 id=&quot;what-cobs-actually-does&quot;&gt;What COBS actually does&lt;/h2&gt;
&lt;p&gt;Strip the acronym away and COBS is one idea: &lt;strong&gt;turn every zero into a distance&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Here is the whole trick. You want a byte that means “frame boundary,” and &lt;code&gt;0x00&lt;/code&gt;
is the natural pick — it is what an idle line and a cleared buffer are already
full of. The trouble is that real payloads contain &lt;code&gt;0x00&lt;/code&gt; too, so the delimiter
is ambiguous the moment you pick it. COBS removes the ambiguity by removing the
zeros: it chops the payload into &lt;strong&gt;groups&lt;/strong&gt; at each zero, and prefixes every
group with a single &lt;strong&gt;code byte&lt;/strong&gt; that says how long the group is. The zeros
themselves are never transmitted — they are &lt;em&gt;implied&lt;/em&gt; by the fact that one group
ended and the next began.&lt;/p&gt;
&lt;p&gt;Concretely, a group of &lt;code&gt;L&lt;/code&gt; non-zero bytes (with &lt;code&gt;0 ≤ L ≤ 254&lt;/code&gt;) is emitted as a
code byte &lt;code&gt;L + 1&lt;/code&gt; followed by the &lt;code&gt;L&lt;/code&gt; bytes. Because the code is &lt;code&gt;L + 1&lt;/code&gt; and &lt;code&gt;L&lt;/code&gt;
tops out at 254, the code ranges over &lt;code&gt;0x01..0xFF&lt;/code&gt; and is &lt;em&gt;never&lt;/em&gt; &lt;code&gt;0x00&lt;/code&gt;. Read
the code another way and it becomes a pointer: &lt;code&gt;L + 1&lt;/code&gt; is exactly the distance
from this code byte to the next one, i.e. to where the next zero used to be. The
decoder follows the chain — jump &lt;code&gt;code&lt;/code&gt; bytes, you land on the next code byte;
the byte that was there is a zero you re-insert — until the delimiter stops it.&lt;/p&gt;
&lt;figure class=&quot;cobs-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;cobs&quot; viewBox=&quot;0 0 760 244&quot; role=&quot;img&quot; aria-labelledby=&quot;cobs-t cobs-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;cobs-t&quot;&gt;How COBS turns a payload with zeros into a stream with none&lt;/title&gt;
&lt;desc id=&quot;cobs-d&quot;&gt;The payload 11 22 00 33 00 sits on top, its two zero bytes highlighted in accent. Below, the COBS encoding 03 11 22 02 33 01 followed by a single 00 frame delimiter drawn dashed. Each code byte — 03, 02 and 01 — is the distance to the next code byte, shown by accent arcs labelled 3, 2 and 1; the code byte stands in for the zero that used to sit at that distance. The encoded stream contains no 0x00, so the single trailing 0x00 delimits the frame unambiguously.&lt;/desc&gt;
&lt;style&gt;
.cobs{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.cobs .box{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.cobs .zero{fill:var(--accent);opacity:0.16;stroke:var(--accent);stroke-width:2}
.cobs .code{fill:var(--bg-dim);stroke:var(--accent);stroke-width:2.5}
.cobs .delim{fill:none;stroke:var(--accent);stroke-width:2;stroke-dasharray:5 4}
.cobs .arc{stroke:var(--accent);stroke-width:2;fill:none}
.cobs text{font-family:var(--sans)}
.cobs .val{fill:var(--fg-main);font-family:var(--mono);font-size:15px}
.cobs .valz{fill:var(--accent);font-family:var(--mono);font-size:15px}
.cobs .lab{fill:var(--fg-dim);font-size:12px}
.cobs .num{fill:var(--accent);font-size:13px;font-family:var(--mono)}
.cobs .ahA{fill:var(--accent)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;cobs-aa&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ahA&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;text class=&quot;lab&quot; x=&quot;44&quot; y=&quot;84&quot; text-anchor=&quot;start&quot;&gt;payload&lt;/text&gt;
&lt;rect class=&quot;box&quot;  x=&quot;250&quot; y=&quot;60&quot; width=&quot;52&quot; height=&quot;44&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;box&quot;  x=&quot;314&quot; y=&quot;60&quot; width=&quot;52&quot; height=&quot;44&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;zero&quot; x=&quot;378&quot; y=&quot;60&quot; width=&quot;52&quot; height=&quot;44&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;box&quot;  x=&quot;442&quot; y=&quot;60&quot; width=&quot;52&quot; height=&quot;44&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;zero&quot; x=&quot;506&quot; y=&quot;60&quot; width=&quot;52&quot; height=&quot;44&quot; rx=&quot;5&quot;/&gt;
&lt;text class=&quot;val&quot;  x=&quot;276&quot; y=&quot;88&quot; text-anchor=&quot;middle&quot;&gt;11&lt;/text&gt;
&lt;text class=&quot;val&quot;  x=&quot;340&quot; y=&quot;88&quot; text-anchor=&quot;middle&quot;&gt;22&lt;/text&gt;
&lt;text class=&quot;valz&quot; x=&quot;404&quot; y=&quot;88&quot; text-anchor=&quot;middle&quot;&gt;00&lt;/text&gt;
&lt;text class=&quot;val&quot;  x=&quot;468&quot; y=&quot;88&quot; text-anchor=&quot;middle&quot;&gt;33&lt;/text&gt;
&lt;text class=&quot;valz&quot; x=&quot;532&quot; y=&quot;88&quot; text-anchor=&quot;middle&quot;&gt;00&lt;/text&gt;
&lt;path class=&quot;arc&quot; d=&quot;M196,152 C196,120 388,120 388,152&quot; marker-end=&quot;url(#cobs-aa)&quot;/&gt;
&lt;path class=&quot;arc&quot; d=&quot;M388,152 C388,124 516,124 516,152&quot; marker-end=&quot;url(#cobs-aa)&quot;/&gt;
&lt;path class=&quot;arc&quot; d=&quot;M516,152 C516,128 580,128 580,152&quot; marker-end=&quot;url(#cobs-aa)&quot;/&gt;
&lt;text class=&quot;num&quot; x=&quot;292&quot; y=&quot;116&quot; text-anchor=&quot;middle&quot;&gt;3&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;452&quot; y=&quot;120&quot; text-anchor=&quot;middle&quot;&gt;2&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;548&quot; y=&quot;126&quot; text-anchor=&quot;middle&quot;&gt;1&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;44&quot; y=&quot;180&quot; text-anchor=&quot;start&quot;&gt;encoded&lt;/text&gt;
&lt;rect class=&quot;code&quot;  x=&quot;170&quot; y=&quot;154&quot; width=&quot;52&quot; height=&quot;44&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;box&quot;   x=&quot;234&quot; y=&quot;154&quot; width=&quot;52&quot; height=&quot;44&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;box&quot;   x=&quot;298&quot; y=&quot;154&quot; width=&quot;52&quot; height=&quot;44&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;code&quot;  x=&quot;362&quot; y=&quot;154&quot; width=&quot;52&quot; height=&quot;44&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;box&quot;   x=&quot;426&quot; y=&quot;154&quot; width=&quot;52&quot; height=&quot;44&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;code&quot;  x=&quot;490&quot; y=&quot;154&quot; width=&quot;52&quot; height=&quot;44&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;delim&quot; x=&quot;554&quot; y=&quot;154&quot; width=&quot;52&quot; height=&quot;44&quot; rx=&quot;5&quot;/&gt;
&lt;text class=&quot;val&quot; x=&quot;196&quot; y=&quot;182&quot; text-anchor=&quot;middle&quot;&gt;03&lt;/text&gt;
&lt;text class=&quot;val&quot; x=&quot;260&quot; y=&quot;182&quot; text-anchor=&quot;middle&quot;&gt;11&lt;/text&gt;
&lt;text class=&quot;val&quot; x=&quot;324&quot; y=&quot;182&quot; text-anchor=&quot;middle&quot;&gt;22&lt;/text&gt;
&lt;text class=&quot;val&quot; x=&quot;388&quot; y=&quot;182&quot; text-anchor=&quot;middle&quot;&gt;02&lt;/text&gt;
&lt;text class=&quot;val&quot; x=&quot;452&quot; y=&quot;182&quot; text-anchor=&quot;middle&quot;&gt;33&lt;/text&gt;
&lt;text class=&quot;val&quot; x=&quot;516&quot; y=&quot;182&quot; text-anchor=&quot;middle&quot;&gt;01&lt;/text&gt;
&lt;text class=&quot;valz&quot; x=&quot;580&quot; y=&quot;182&quot; text-anchor=&quot;middle&quot;&gt;00&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;196&quot; y=&quot;222&quot; text-anchor=&quot;middle&quot;&gt;code = length + 1&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;580&quot; y=&quot;222&quot; text-anchor=&quot;middle&quot;&gt;delimiter&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;COBS on the payload &lt;code&gt;11 22 00 33 00&lt;/code&gt;. It splits at the zeros into groups &lt;code&gt;[11 22]&lt;/code&gt;, &lt;code&gt;[33]&lt;/code&gt;, and a trailing empty group, and prefixes each with a code byte equal to its length plus one: &lt;code&gt;03 11 22&lt;/code&gt;, &lt;code&gt;02 33&lt;/code&gt;, &lt;code&gt;01&lt;/code&gt;. Each code is the distance (accent arcs) to the next code byte — the spot the zero used to occupy — so the decoder can re-insert every zero by following the chain. The encoded stream holds no &lt;code&gt;0x00&lt;/code&gt;, which frees the single trailing &lt;code&gt;0x00&lt;/code&gt; to mean, unambiguously, &lt;em&gt;end of frame&lt;/em&gt;.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Two details in that picture are the entire reason both step functions are more
than ten lines long, so let us name them now and meet them again in the code.&lt;/p&gt;
&lt;p&gt;The first is the &lt;strong&gt;final group&lt;/strong&gt;. Count the groups above: &lt;code&gt;[11 22]&lt;/code&gt;, &lt;code&gt;[33]&lt;/code&gt;, and
then an &lt;em&gt;empty&lt;/em&gt; one, &lt;code&gt;01&lt;/code&gt;. A payload with &lt;em&gt;k&lt;/em&gt; zeros always encodes to &lt;em&gt;k + 1&lt;/em&gt;
groups, because every zero marks a boundary &lt;em&gt;between&lt;/em&gt; two groups — and a payload
that ends in a zero, like this one, leaves an empty run after that last zero,
which becomes the trailing empty group. On decode a zero is re-inserted after
every group &lt;em&gt;but the last&lt;/em&gt;; the final group is closed by the frame delimiter
instead. So the decoder owes exactly &lt;em&gt;k&lt;/em&gt; zeros across &lt;em&gt;k + 1&lt;/em&gt; groups — one after
each group except the final one — and getting that off-by-one asymmetry right is
where its trickiest line lives.&lt;/p&gt;
&lt;p&gt;The second is the &lt;strong&gt;full group&lt;/strong&gt;. A group holds at most 254 bytes because the
code caps at &lt;code&gt;0xFF&lt;/code&gt;. What happens to a run of, say, 300 non-zero bytes with no
zero to break it? COBS emits a full group — code &lt;code&gt;0xFF&lt;/code&gt;, 254 bytes — and then
simply &lt;em&gt;continues&lt;/em&gt; the run in the next group. Crucially, that &lt;code&gt;0xFF&lt;/code&gt; group is a
&lt;strong&gt;continuation&lt;/strong&gt;: unlike every code from &lt;code&gt;0x01&lt;/code&gt; to &lt;code&gt;0xFE&lt;/code&gt;, it does &lt;em&gt;not&lt;/em&gt; stand in
for a zero, because there was no zero — the run was just too long. “Is this code
&lt;code&gt;0xFF&lt;/code&gt;?” is therefore the question that decides whether a zero gets re-inserted,
and it is exactly the &lt;code&gt;dFull&lt;/code&gt; flag in the decoder and the &lt;code&gt;eFill == 254&lt;/code&gt; test in
the encoder.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-consistent-1&quot;&gt;&lt;a href=&quot;#fn-consistent&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;With those two facts in hand the code reads like prose. We take the decoder
first, as promised, because it is the shorter of the two.&lt;/p&gt;
&lt;h2 id=&quot;cobsdecodestep-decoding-a-byte&quot;&gt;&lt;code&gt;cobsDecodeStep&lt;/code&gt;: decoding a byte&lt;/h2&gt;
&lt;p&gt;The decoder’s job is to invert the picture: bytes of a delimiter-stripped COBS
frame go in, the original payload comes out, and any structural nonsense is
flagged. Start, as always, with the type.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;cobsDecodeStep&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; DecSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;DecSt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read against the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt;’s &lt;code&gt;s -&amp;gt; i -&amp;gt; (s, o)&lt;/code&gt;: the state is &lt;code&gt;DecSt&lt;/code&gt;, the
input is &lt;code&gt;(Maybe (BitVector 8), Bool)&lt;/code&gt;, and the output is &lt;code&gt;(Maybe (BitVector 8), Bool, Bool)&lt;/code&gt;. Name the parts and the contract appears. The input is a maybe-byte
— &lt;code&gt;Just b&lt;/code&gt; when the UART handed the loader a byte this cycle, &lt;code&gt;Nothing&lt;/code&gt;
otherwise — paired with a &lt;code&gt;frameEnd&lt;/code&gt; flag the loader pulses the instant it sees
the &lt;code&gt;0x00&lt;/code&gt; delimiter on the wire. The output triple is a maybe-decoded-byte, a
&lt;code&gt;done&lt;/code&gt; pulse, and a &lt;code&gt;malformed&lt;/code&gt; flag. The decoder is a valve: bytes trickle in,
decoded bytes trickle out, and two one-bit signals announce the end of the frame
and whether it held together.&lt;/p&gt;
&lt;p&gt;The body is three guarded cases. Take them in the order the hardware does.&lt;/p&gt;
&lt;h3 id=&quot;frame-end-reset-and-the-malformed-law&quot;&gt;Frame end: reset, and the malformed law&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; frameEnd &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;initDec&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; not &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dGot s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ||&lt;/span&gt;&lt;span&gt; dCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;/=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the loader pulses &lt;code&gt;frameEnd&lt;/code&gt;, the current frame is over regardless of what
the decoder was in the middle of. The next state is &lt;code&gt;initDec&lt;/code&gt; — a hard reset, so
the following frame starts clean — the output byte is &lt;code&gt;Nothing&lt;/code&gt;, and &lt;code&gt;done&lt;/code&gt; is
&lt;code&gt;True&lt;/code&gt;. The interesting part is the last field, the &lt;code&gt;malformed&lt;/code&gt; verdict:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A frame is malformed if no byte ever arrived, or if a group was still owed data
when the delimiter cut it off.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;not (dGot s)&lt;/code&gt; catches the empty frame: two delimiters back to back, nothing
between them, &lt;code&gt;dGot&lt;/code&gt; never set. &lt;code&gt;dCnt s /= 0&lt;/code&gt; catches the truncated group: a code
byte promised &lt;code&gt;n&lt;/code&gt; data bytes, the delimiter arrived before all &lt;code&gt;n&lt;/code&gt; did, and the
counter never wound back to zero. Both are exactly the failures the pure
reference rejects — an empty list, or a code byte “demanding more bytes than
remain” — checked here in a single cheap disjunction at the one moment the whole
frame is known to be complete.&lt;/p&gt;
&lt;h3 id=&quot;no-byte-this-cycle-hold&quot;&gt;No byte this cycle: hold&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;      Nothing&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the loader has no byte for the decoder this cycle — the UART is mid-bit,
say — the decoder does nothing at all: state unchanged, no output, no pulses. A
valve with nothing flowing through it. This is the overwhelmingly common case,
and it is one line.&lt;/p&gt;
&lt;h3 id=&quot;a-byte-arrives-code-byte-or-data-byte&quot;&gt;A byte arrives: code byte, or data byte?&lt;/h3&gt;
&lt;p&gt;Everything real happens when &lt;code&gt;Just b&lt;/code&gt; arrives, and the split is the &lt;code&gt;dCnt s == 0&lt;/code&gt;
guard, which is precisely the question &lt;em&gt;is the next byte a code byte?&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;      Just&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        |&lt;/span&gt;&lt;span&gt; dCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            let&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;out&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; s1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                  if&lt;/span&gt;&lt;span&gt; dPend s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                    then&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Just&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;dPend&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dGot&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                    else&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;dGot&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                s2 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; startGroup s1 b&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;             in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;out&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When &lt;code&gt;dCnt&lt;/code&gt; is zero we are between groups, so &lt;code&gt;b&lt;/code&gt; is a code byte — but before we
interpret it, we settle a debt. If &lt;code&gt;dPend&lt;/code&gt; is set, the &lt;em&gt;previous&lt;/em&gt; group ended
owing an implied zero, and this is the moment to pay it: &lt;code&gt;out = Just 0&lt;/code&gt;. That
timing is the subtle heart of the decoder, so let me state it plainly.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The zero between two groups is emitted lazily — not when the group ends, but
when the &lt;em&gt;next&lt;/em&gt; code byte arrives.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Why lazily? Because the zero only exists if there &lt;em&gt;is&lt;/em&gt; a next group. A group that
turns out to be the frame’s last is followed by the delimiter, not by another
code byte, and its implied zero is not part of the payload — it is the boundary
itself. By deferring the zero until the next code byte, the decoder gets this for
free: if the next thing is a code byte, the zero was real and we emit it; if the
next thing is &lt;code&gt;frameEnd&lt;/code&gt;, we reset and the pending zero evaporates, un-emitted,
exactly as it should. The &lt;code&gt;03 11 22 02 33 01&lt;/code&gt; trace below shows both halves of
that bargain.&lt;/p&gt;
&lt;p&gt;Once the debt is settled, &lt;code&gt;startGroup&lt;/code&gt; interprets the code byte:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  startGroup st c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; full &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 255&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        n &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;unpack c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; c is 1..255 (never 0), so n is 0..254&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; n &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          then&lt;/span&gt;&lt;span&gt; st&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;dCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dFull&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; full&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dPend&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; not full&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          else&lt;/span&gt;&lt;span&gt; st&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;dCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dFull&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; full&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;n = c - 1&lt;/code&gt; is the group’s data-byte count, undoing the encoder’s &lt;code&gt;L + 1&lt;/code&gt;. &lt;code&gt;full = c == 255&lt;/code&gt; records the continuation case. Then a fork on &lt;code&gt;n&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;n == 0&lt;/code&gt; — the code was &lt;code&gt;0x01&lt;/code&gt;, an &lt;strong&gt;empty group&lt;/strong&gt;. There is no data to wait
for, so &lt;code&gt;dCnt&lt;/code&gt; stays &lt;code&gt;0&lt;/code&gt; (the very next byte is again a code byte), and we set
&lt;code&gt;dPend = not full&lt;/code&gt;. For &lt;code&gt;0x01&lt;/code&gt;, &lt;code&gt;full&lt;/code&gt; is &lt;code&gt;False&lt;/code&gt;, so &lt;code&gt;dPend&lt;/code&gt; becomes &lt;code&gt;True&lt;/code&gt;:
an empty group &lt;em&gt;is&lt;/em&gt; a lone zero, and it owes one. (&lt;code&gt;0x01&lt;/code&gt; is the only way &lt;code&gt;n&lt;/code&gt;
is zero; &lt;code&gt;full&lt;/code&gt; is a formality here, always &lt;code&gt;False&lt;/code&gt;, but writing &lt;code&gt;not full&lt;/code&gt;
keeps the rule uniform.)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;n /= 0&lt;/code&gt; — a normal group. Arm the counter, &lt;code&gt;dCnt = n&lt;/code&gt;, and remember whether
it was full. The implied zero, if any, will be decided when the counter hits
bottom, in the other arm.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;inside-a-group-pass-the-byte-count-down&quot;&gt;Inside a group: pass the byte, count down&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            let&lt;/span&gt;&lt;span&gt; cnt&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; dCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                  s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; cnt&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dGot&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dPend&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                        if&lt;/span&gt;&lt;span&gt; cnt&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                          then&lt;/span&gt;&lt;span&gt; not &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dFull s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                          else&lt;/span&gt;&lt;span&gt; dPend s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;             in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Just&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When &lt;code&gt;dCnt&lt;/code&gt; is non-zero, &lt;code&gt;b&lt;/code&gt; is a data byte, and data bytes are transparent:
&lt;code&gt;out = Just b&lt;/code&gt;, passed straight through unchanged (they were never touched by the
encoder — COBS only ever &lt;em&gt;removed&lt;/em&gt; the zeros, never disturbed the rest).
Decrement the counter to &lt;code&gt;cnt&#39;&lt;/code&gt;. And here is the mirror image of &lt;code&gt;startGroup&lt;/code&gt;’s
pending logic: the instant the group empties, &lt;code&gt;cnt&#39; == 0&lt;/code&gt;, we set &lt;code&gt;dPend = not (dFull s)&lt;/code&gt;. A normal group just ended and owes its implied zero; a full (&lt;code&gt;0xFF&lt;/code&gt;)
group ends owing nothing, because it was a continuation. That single &lt;code&gt;not dFull&lt;/code&gt;
is the &lt;code&gt;0xFF&lt;/code&gt;-is-special rule from the concept section, written once, doing all
its work.&lt;/p&gt;
&lt;h3 id=&quot;watching-it-run&quot;&gt;Watching it run&lt;/h3&gt;
&lt;p&gt;Take the frame from the figure, &lt;code&gt;03 11 22 02 33 01&lt;/code&gt;, and feed it in byte by byte,
capped with &lt;code&gt;frameEnd&lt;/code&gt;. The decoder should hand back &lt;code&gt;11 22 00 33 00&lt;/code&gt;.&lt;/p&gt;
&lt;figure class=&quot;cdec-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;cdec&quot; viewBox=&quot;0 0 760 250&quot; role=&quot;img&quot; aria-labelledby=&quot;cdec-t cdec-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;cdec-t&quot;&gt;Decoding 03 11 22 02 33 01 back into 11 22 00 33 00&lt;/title&gt;
&lt;desc id=&quot;cdec-d&quot;&gt;The top row is the COBS input 03 11 22 02 33 01 followed by a dashed 00 delimiter. The bottom row is the decoded output 11 22 00 33 00, column-aligned beneath the input. Data bytes 11, 22 and 33 drop straight down to the output. The code byte 03 arms a counter and produces no output. The code bytes 02 and 01 each manufacture an accent-highlighted zero in the output — the implied zero of the group that just ended — before re-arming. The delimiter produces done and drops the final group&#39;s still-pending zero.&lt;/desc&gt;
&lt;style&gt;
.cdec{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.cdec .box{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.cdec .code{fill:var(--bg-dim);stroke:var(--accent);stroke-width:2.5}
.cdec .delim{fill:none;stroke:var(--accent);stroke-width:2;stroke-dasharray:5 4}
.cdec .zout{fill:var(--accent);opacity:0.16;stroke:var(--accent);stroke-width:2}
.cdec .thru{stroke:var(--fg-main);stroke-width:2;fill:none}
.cdec .mk{stroke:var(--accent);stroke-width:2;fill:none}
.cdec text{font-family:var(--sans)}
.cdec .val{fill:var(--fg-main);font-family:var(--mono);font-size:15px}
.cdec .valz{fill:var(--accent);font-family:var(--mono);font-size:15px}
.cdec .lab{fill:var(--fg-dim);font-size:12px}
.cdec .note{fill:var(--accent);font-size:11.5px}
.cdec .ah{fill:var(--fg-main)}
.cdec .ahA{fill:var(--accent)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;cdec-a&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;cdec-aa&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ahA&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;text class=&quot;lab&quot; x=&quot;34&quot; y=&quot;60&quot; text-anchor=&quot;start&quot;&gt;COBS in&lt;/text&gt;
&lt;rect class=&quot;code&quot;  x=&quot;110&quot; y=&quot;40&quot; width=&quot;60&quot; height=&quot;42&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;box&quot;   x=&quot;192&quot; y=&quot;40&quot; width=&quot;60&quot; height=&quot;42&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;box&quot;   x=&quot;274&quot; y=&quot;40&quot; width=&quot;60&quot; height=&quot;42&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;code&quot;  x=&quot;356&quot; y=&quot;40&quot; width=&quot;60&quot; height=&quot;42&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;box&quot;   x=&quot;438&quot; y=&quot;40&quot; width=&quot;60&quot; height=&quot;42&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;code&quot;  x=&quot;520&quot; y=&quot;40&quot; width=&quot;60&quot; height=&quot;42&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;delim&quot; x=&quot;602&quot; y=&quot;40&quot; width=&quot;60&quot; height=&quot;42&quot; rx=&quot;5&quot;/&gt;
&lt;text class=&quot;val&quot; x=&quot;140&quot; y=&quot;67&quot; text-anchor=&quot;middle&quot;&gt;03&lt;/text&gt;
&lt;text class=&quot;val&quot; x=&quot;222&quot; y=&quot;67&quot; text-anchor=&quot;middle&quot;&gt;11&lt;/text&gt;
&lt;text class=&quot;val&quot; x=&quot;304&quot; y=&quot;67&quot; text-anchor=&quot;middle&quot;&gt;22&lt;/text&gt;
&lt;text class=&quot;val&quot; x=&quot;386&quot; y=&quot;67&quot; text-anchor=&quot;middle&quot;&gt;02&lt;/text&gt;
&lt;text class=&quot;val&quot; x=&quot;468&quot; y=&quot;67&quot; text-anchor=&quot;middle&quot;&gt;33&lt;/text&gt;
&lt;text class=&quot;val&quot; x=&quot;550&quot; y=&quot;67&quot; text-anchor=&quot;middle&quot;&gt;01&lt;/text&gt;
&lt;text class=&quot;valz&quot; x=&quot;632&quot; y=&quot;67&quot; text-anchor=&quot;middle&quot;&gt;00&lt;/text&gt;
&lt;line class=&quot;thru&quot; x1=&quot;222&quot; y1=&quot;82&quot; x2=&quot;222&quot; y2=&quot;168&quot; marker-end=&quot;url(#cdec-a)&quot;/&gt;
&lt;line class=&quot;thru&quot; x1=&quot;304&quot; y1=&quot;82&quot; x2=&quot;304&quot; y2=&quot;168&quot; marker-end=&quot;url(#cdec-a)&quot;/&gt;
&lt;line class=&quot;thru&quot; x1=&quot;468&quot; y1=&quot;82&quot; x2=&quot;468&quot; y2=&quot;168&quot; marker-end=&quot;url(#cdec-a)&quot;/&gt;
&lt;path class=&quot;mk&quot; d=&quot;M386,82 C386,120 386,132 386,168&quot; marker-end=&quot;url(#cdec-aa)&quot;/&gt;
&lt;path class=&quot;mk&quot; d=&quot;M550,82 C550,120 550,132 550,168&quot; marker-end=&quot;url(#cdec-aa)&quot;/&gt;
&lt;line class=&quot;mk&quot; x1=&quot;140&quot; y1=&quot;82&quot; x2=&quot;140&quot; y2=&quot;120&quot;/&gt;
&lt;text class=&quot;note&quot; x=&quot;140&quot; y=&quot;136&quot; text-anchor=&quot;middle&quot;&gt;arm 2 · no out&lt;/text&gt;
&lt;line class=&quot;mk&quot; x1=&quot;632&quot; y1=&quot;82&quot; x2=&quot;632&quot; y2=&quot;120&quot;/&gt;
&lt;text class=&quot;note&quot; x=&quot;632&quot; y=&quot;132&quot; text-anchor=&quot;middle&quot;&gt;done ·&lt;/text&gt;
&lt;text class=&quot;note&quot; x=&quot;632&quot; y=&quot;147&quot; text-anchor=&quot;middle&quot;&gt;drop pending 0&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;34&quot; y=&quot;196&quot; text-anchor=&quot;start&quot;&gt;bytes out&lt;/text&gt;
&lt;rect class=&quot;box&quot;  x=&quot;192&quot; y=&quot;172&quot; width=&quot;60&quot; height=&quot;42&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;box&quot;  x=&quot;274&quot; y=&quot;172&quot; width=&quot;60&quot; height=&quot;42&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;zout&quot; x=&quot;356&quot; y=&quot;172&quot; width=&quot;60&quot; height=&quot;42&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;box&quot;  x=&quot;438&quot; y=&quot;172&quot; width=&quot;60&quot; height=&quot;42&quot; rx=&quot;5&quot;/&gt;
&lt;rect class=&quot;zout&quot; x=&quot;520&quot; y=&quot;172&quot; width=&quot;60&quot; height=&quot;42&quot; rx=&quot;5&quot;/&gt;
&lt;text class=&quot;val&quot;  x=&quot;222&quot; y=&quot;199&quot; text-anchor=&quot;middle&quot;&gt;11&lt;/text&gt;
&lt;text class=&quot;val&quot;  x=&quot;304&quot; y=&quot;199&quot; text-anchor=&quot;middle&quot;&gt;22&lt;/text&gt;
&lt;text class=&quot;valz&quot; x=&quot;386&quot; y=&quot;199&quot; text-anchor=&quot;middle&quot;&gt;00&lt;/text&gt;
&lt;text class=&quot;val&quot;  x=&quot;468&quot; y=&quot;199&quot; text-anchor=&quot;middle&quot;&gt;33&lt;/text&gt;
&lt;text class=&quot;valz&quot; x=&quot;550&quot; y=&quot;199&quot; text-anchor=&quot;middle&quot;&gt;00&lt;/text&gt;
&lt;text class=&quot;note&quot; x=&quot;468&quot; y=&quot;238&quot; text-anchor=&quot;middle&quot;&gt;accent zeros — manufactured when the next code byte arrives&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;One decode, column-aligned. Data bytes (&lt;code&gt;11&lt;/code&gt;, &lt;code&gt;22&lt;/code&gt;, &lt;code&gt;33&lt;/code&gt;) fall straight through. The first code byte &lt;code&gt;03&lt;/code&gt; only arms the counter — no output. Each later code byte (&lt;code&gt;02&lt;/code&gt;, &lt;code&gt;01&lt;/code&gt;) first pays the previous group&#39;s debt, manufacturing the accent &lt;code&gt;00&lt;/code&gt;, then re-arms: the implied zero is born &lt;em&gt;at the next code byte&lt;/em&gt;, not when its group ended. The delimiter raises &lt;code&gt;done&lt;/code&gt; and drops the final group&#39;s still-pending zero — which is why &lt;code&gt;01&lt;/code&gt;&#39;s zero never appears, and the output ends at &lt;code&gt;33 00&lt;/code&gt;, the original payload exactly.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Trace it against the state:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;03&lt;/code&gt;&lt;/strong&gt; — &lt;code&gt;dCnt&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;dPend&lt;/code&gt; is &lt;code&gt;False&lt;/code&gt;, so no output; &lt;code&gt;startGroup&lt;/code&gt;
reads &lt;code&gt;n = 2&lt;/code&gt;, sets &lt;code&gt;dCnt = 2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;11&lt;/code&gt;&lt;/strong&gt; — inside a group; emit &lt;code&gt;11&lt;/code&gt;, &lt;code&gt;dCnt&lt;/code&gt; falls to &lt;code&gt;1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;22&lt;/code&gt;&lt;/strong&gt; — emit &lt;code&gt;22&lt;/code&gt;, &lt;code&gt;dCnt&lt;/code&gt; falls to &lt;code&gt;0&lt;/code&gt;; the group is done, so &lt;code&gt;dPend = not dFull = True&lt;/code&gt;. &lt;em&gt;Debt incurred.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;02&lt;/code&gt;&lt;/strong&gt; — &lt;code&gt;dCnt&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;dPend&lt;/code&gt; is set, so emit the owed &lt;code&gt;0&lt;/code&gt;; then
&lt;code&gt;startGroup&lt;/code&gt; reads &lt;code&gt;n = 1&lt;/code&gt;, &lt;code&gt;dCnt = 1&lt;/code&gt;. Output so far: &lt;code&gt;11 22 00&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;33&lt;/code&gt;&lt;/strong&gt; — emit &lt;code&gt;33&lt;/code&gt;, &lt;code&gt;dCnt&lt;/code&gt; falls to &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;dPend = True&lt;/code&gt; again.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;01&lt;/code&gt;&lt;/strong&gt; — &lt;code&gt;dPend&lt;/code&gt; set, emit the owed &lt;code&gt;0&lt;/code&gt;; &lt;code&gt;startGroup&lt;/code&gt; reads &lt;code&gt;n = 0&lt;/code&gt;, an
empty group, &lt;code&gt;dCnt = 0&lt;/code&gt;, &lt;code&gt;dPend = True&lt;/code&gt;. Output: &lt;code&gt;11 22 00 33 00&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;frameEnd&lt;/code&gt;&lt;/strong&gt; — reset. &lt;code&gt;dGot&lt;/code&gt; is &lt;code&gt;True&lt;/code&gt; and &lt;code&gt;dCnt&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt;, so &lt;code&gt;malformed&lt;/code&gt;
is &lt;code&gt;False&lt;/code&gt;. The &lt;code&gt;dPend&lt;/code&gt; left set by step 6 is simply discarded.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The output is &lt;code&gt;11 22 00 33 00&lt;/code&gt; — the payload we started from. Notice the whole
drama is in that last &lt;code&gt;dPend&lt;/code&gt;: step 6 dutifully set it, and step 7 threw it away,
which is exactly right, because the final group’s zero was the frame boundary,
not a byte. Emit it and we would have handed back &lt;code&gt;11 22 00 33 00 00&lt;/code&gt;, one zero
too many. The lazy timing is what makes the difference free.&lt;/p&gt;
&lt;p&gt;And the malformed cases fall straight out of the same law. Feed &lt;code&gt;05 11&lt;/code&gt; then
&lt;code&gt;frameEnd&lt;/code&gt;: the code &lt;code&gt;0x05&lt;/code&gt; arms &lt;code&gt;dCnt = 4&lt;/code&gt;, one data byte arrives, and the
delimiter finds &lt;code&gt;dCnt = 3 /= 0&lt;/code&gt; — truncated, malformed. Feed nothing between two
delimiters: &lt;code&gt;dGot&lt;/code&gt; is &lt;code&gt;False&lt;/code&gt; — empty, malformed. Feed a lone &lt;code&gt;03&lt;/code&gt;: &lt;code&gt;dGot&lt;/code&gt; is
set but &lt;code&gt;dCnt = 2&lt;/code&gt; still owes two bytes — malformed. The decoder never has to
reach for a special error path; the verdict is a one-line consequence of the
state it was already keeping.&lt;/p&gt;
&lt;h2 id=&quot;cobsencodestep-encoding-a-byte&quot;&gt;&lt;code&gt;cobsEncodeStep&lt;/code&gt;: encoding a byte&lt;/h2&gt;
&lt;p&gt;Encoding is the harder direction, and the reason is timing. The decoder could act
on each byte the instant it arrived, because a code byte tells it everything up
front. The encoder cannot: to emit a group’s code byte it must first know the
group’s &lt;em&gt;length&lt;/em&gt;, and it cannot know the length until it has seen the zero (or
the 254th byte, or the end of input) that closes the group. So the encoder is a
two-beat machine — &lt;strong&gt;fill&lt;/strong&gt; a buffer, then &lt;strong&gt;emit&lt;/strong&gt; what it holds — and the
whole function is a &lt;code&gt;case&lt;/code&gt; on which beat we are in.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;cobsEncodeStep&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; EncSt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;EncSt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Bool&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The input is a maybe-&lt;code&gt;(byte, is-last)&lt;/code&gt; paired with &lt;code&gt;dsReady&lt;/code&gt;, the downstream
ready line. The &lt;code&gt;is-last&lt;/code&gt; flag rides along with each byte because, again, the
encoder needs to know when the stream ends to close the final group. &lt;code&gt;dsReady&lt;/code&gt; is
back-pressure from whatever consumes the COBS bytes (the transmitter, via the
loader): the encoder may only push a byte on a cycle the consumer can take one.
The output is &lt;code&gt;(readyIn, mOut, done)&lt;/code&gt; — a ready line &lt;em&gt;back&lt;/em&gt; toward the source,
the maybe-COBS-byte, and a &lt;code&gt;done&lt;/code&gt; pulse when the frame is fully emitted. Two
ready lines, pointing opposite ways, because the encoder sits in the middle of a
pipeline and must talk to both ends.&lt;/p&gt;
&lt;h3 id=&quot;filling-buffer-a-group-and-the-load-bearing-order&quot;&gt;Filling: buffer a group, and the load-bearing order&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  EFilling&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; mIn &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    Nothing&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      |&lt;/span&gt;&lt;span&gt; eFill s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 254&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eMode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; EEmitting&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ePend&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      |&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eMode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; EEmitting&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eFinal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eLast&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; lst&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      |&lt;/span&gt;&lt;span&gt; lst &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          (&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;store s b&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eMode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; EEmitting&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eLast&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eFinal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          (&lt;/span&gt;&lt;span&gt;store s b&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Throughout &lt;code&gt;EFilling&lt;/code&gt;, &lt;code&gt;readyIn&lt;/code&gt; is &lt;code&gt;True&lt;/code&gt;: this is the one mode that consumes
input, so it is the one mode that says “keep them coming.” With no byte offered
(&lt;code&gt;Nothing&lt;/code&gt;) it idles, ready. With a byte &lt;code&gt;(b, lst)&lt;/code&gt; in hand, four guards decide
its fate, and &lt;em&gt;their order is load-bearing&lt;/em&gt; — this is the single most important
thing in the encoder, so we take the guards in order and dwell on the first.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;eFill == 254&lt;/code&gt; comes first, before even the zero check.&lt;/strong&gt; If the buffer already
holds 254 bytes, the group is full and must flush as a &lt;code&gt;0xFF&lt;/code&gt; continuation —
&lt;em&gt;whatever &lt;code&gt;b&lt;/code&gt; is&lt;/em&gt;. We switch to &lt;code&gt;EEmitting&lt;/code&gt;, reset the emit cursor, and stash &lt;code&gt;b&lt;/code&gt;
(with its &lt;code&gt;lst&lt;/code&gt;) in &lt;code&gt;ePend&lt;/code&gt; to be reprocessed into a fresh group after the flush.
Why must this outrank &lt;code&gt;b == 0&lt;/code&gt;? Because a &lt;code&gt;0xFF&lt;/code&gt; group carries no implied zero, so
if we let a zero landing on a full buffer fold &lt;em&gt;into&lt;/em&gt; the full group, that zero
would simply vanish on decode. The comment in the source is blunt about it, and
it deserves quoting in full:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A 254-byte group flushes as a &lt;code&gt;0xFF&lt;/code&gt; continuation (no implied zero), then &lt;code&gt;b&lt;/code&gt; is
reprocessed via &lt;code&gt;ePend&lt;/code&gt;. A zero landing here must terminate a &lt;em&gt;fresh&lt;/em&gt; empty
group, not fold into the full group.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So a zero arriving on a full buffer produces &lt;code&gt;…FF,&amp;lt;254&amp;gt;&lt;/code&gt; and &lt;em&gt;then&lt;/em&gt; &lt;code&gt;01&lt;/code&gt; — the
full group, then a fresh empty group for the zero — never a single &lt;code&gt;0xFF&lt;/code&gt; that
quietly eats it. Get the guard order wrong and the encoder loses data on exactly
the inputs a naive test suite is least likely to try. (We will see the regression
test that pins this down.)&lt;/p&gt;
&lt;p&gt;The remaining three guards are the ordinary closes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;b == 0&lt;/code&gt;&lt;/strong&gt; — the zero terminates the current group. Switch to &lt;code&gt;EEmitting&lt;/code&gt; to
flush what we have; the zero itself contributes no byte to the buffer (it is
implied). Record &lt;code&gt;eFinal = lst&lt;/code&gt; and &lt;code&gt;eLast = lst&lt;/code&gt;: if this terminating zero was
the stream’s last byte, we will owe a final empty group after the flush, and
the stream has ended.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;lst&lt;/code&gt;&lt;/strong&gt; — a non-zero last byte. &lt;code&gt;store&lt;/code&gt; it, then switch to emit. &lt;code&gt;eLast = True&lt;/code&gt; (the stream is over) but &lt;code&gt;eFinal = False&lt;/code&gt; (a non-zero last byte owes no
trailing group; the final group is the one this very byte is in).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;otherwise&lt;/strong&gt; — an ordinary non-zero, non-last byte. &lt;code&gt;store&lt;/code&gt; it and stay in
&lt;code&gt;EFilling&lt;/code&gt;, ready for more. &lt;code&gt;store&lt;/code&gt; writes &lt;code&gt;b&lt;/code&gt; at index &lt;code&gt;eFill&lt;/code&gt; and bumps the
count: &lt;code&gt;st{eBuf = replace (eFill st) b (eBuf st), eFill = eFill st + 1}&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;emitting-drive-code-then-data-then-whatever-is-owed&quot;&gt;Emitting: drive code, then data, then whatever is owed&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  EEmitting&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; not dsReady &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; eIx s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; eFill s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        let&lt;/span&gt;&lt;span&gt; out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; eIx s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; then&lt;/span&gt;&lt;span&gt; fromIntegral &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;eFill s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; +&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; else&lt;/span&gt;&lt;span&gt; eBuf s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;!!&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;eIx s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;         in&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; eIx s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; out&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In &lt;code&gt;EEmitting&lt;/code&gt;, &lt;code&gt;readyIn&lt;/code&gt; is &lt;code&gt;False&lt;/code&gt; throughout — the encoder is draining its
buffer, not accepting input. The first guard is the back-pressure gate: if
&lt;code&gt;dsReady&lt;/code&gt; is low the consumer cannot take a byte, so we stall, holding all state,
emitting nothing. Nothing moves until downstream is ready.&lt;/p&gt;
&lt;p&gt;When it is ready, the &lt;code&gt;eIx &amp;lt;= eFill&lt;/code&gt; guard walks the group out. &lt;code&gt;eIx == 0&lt;/code&gt; emits
the &lt;strong&gt;code byte&lt;/strong&gt;, computed right here as &lt;code&gt;eFill + 1&lt;/code&gt; — the group’s length plus
one, the encoder’s half of the &lt;code&gt;L + 1&lt;/code&gt; the decoder undoes. Every later index emits
a data byte, &lt;code&gt;eBuf !! (eIx - 1)&lt;/code&gt;, and the cursor advances. So a group of &lt;code&gt;eFill&lt;/code&gt;
bytes takes &lt;code&gt;eFill + 1&lt;/code&gt; emit cycles: one code, then the data. Straightforward
tape-out of &lt;code&gt;code ++ group&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The interesting question is what happens &lt;em&gt;after&lt;/em&gt; the cursor runs past the data,
and the answer is a little cascade of owed work, tried in a deliberate order.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; eFinal s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eFill&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eFinal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; ePend s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;        Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;pb&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; pl&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          |&lt;/span&gt;&lt;span&gt; pb &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eMode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; EEmitting&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eFill&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ePend&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eFinal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; pl&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eLast&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; pl&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          |&lt;/span&gt;&lt;span&gt; pl &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              (&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;store s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eFill&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; pb&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eMode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; EEmitting&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eIx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ePend&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eLast&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              (&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;store s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eFill&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; pb&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eMode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; EFilling&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; ePend&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;        Nothing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          |&lt;/span&gt;&lt;span&gt; eLast s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;initEnc&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;eMode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; EFilling&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; eFill&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;False&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;eFinal&lt;/code&gt; first.&lt;/strong&gt; If a final empty group is owed — because the last input byte
was a zero — clear &lt;code&gt;eFill&lt;/code&gt; to &lt;code&gt;0&lt;/code&gt; and loop back through &lt;code&gt;EEmitting&lt;/code&gt;, which will
emit the &lt;code&gt;0x01&lt;/code&gt; of an empty group (&lt;code&gt;eFill + 1 = 1&lt;/code&gt;) and then fall through here
again with &lt;code&gt;eFinal&lt;/code&gt; now &lt;code&gt;False&lt;/code&gt;. This is the trailing-empty-group from the
concept section, made concrete: a payload ending in a zero needs one last &lt;code&gt;01&lt;/code&gt;,
and this is where it comes from.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Then &lt;code&gt;ePend&lt;/code&gt;.&lt;/strong&gt; A stashed byte means we just flushed a full 254-group and must
fold that byte into a fresh group. Three sub-cases, mirroring the fill guards:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;pb == 0&lt;/code&gt;&lt;/strong&gt; — the stashed byte was a zero. The full group already flushed as
&lt;code&gt;0xFF&lt;/code&gt; (no implied zero); now the zero gets its &lt;em&gt;own&lt;/em&gt; fresh empty group. Reset
&lt;code&gt;eFill&lt;/code&gt;, stay emitting, and set &lt;code&gt;eFinal&lt;/code&gt;/&lt;code&gt;eLast&lt;/code&gt; from &lt;code&gt;pl&lt;/code&gt; — if that zero was
the last input byte, we still owe the final empty group after it. This is the
&lt;code&gt;…FF,&amp;lt;254&amp;gt;,01,01&lt;/code&gt; path in the flesh, and the reason the guard order upstream
had to put &lt;code&gt;eFill == 254&lt;/code&gt; first.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;pl&lt;/code&gt;&lt;/strong&gt; — the stashed byte is non-zero and last. &lt;code&gt;store&lt;/code&gt; it in a fresh buffer
and emit that one-byte group, marking &lt;code&gt;eLast&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;otherwise&lt;/strong&gt; — a non-zero, non-last stashed byte. &lt;code&gt;store&lt;/code&gt; it into a fresh group
and return to &lt;code&gt;EFilling&lt;/code&gt; to keep accumulating from there.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Finally &lt;code&gt;Nothing&lt;/code&gt;.&lt;/strong&gt; No stash, so the group we just emitted was clean. If
&lt;code&gt;eLast&lt;/code&gt; is set the whole frame is done: reset to &lt;code&gt;initEnc&lt;/code&gt; and pulse &lt;code&gt;done&lt;/code&gt;.
Otherwise there is more payload coming, so drop back to &lt;code&gt;EFilling&lt;/code&gt; with an empty
buffer.&lt;/p&gt;
&lt;h3 id=&quot;watching-it-run-1&quot;&gt;Watching it run&lt;/h3&gt;
&lt;p&gt;Run the payload &lt;code&gt;11 22 00 33 00&lt;/code&gt; through, &lt;code&gt;is-last&lt;/code&gt; on the final zero, &lt;code&gt;dsReady&lt;/code&gt;
always high. It should produce &lt;code&gt;03 11 22 02 33 01&lt;/code&gt; — the figure’s encoding, now
built rather than read.&lt;/p&gt;
&lt;figure class=&quot;cenc-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;cenc&quot; viewBox=&quot;0 0 760 288&quot; role=&quot;img&quot; aria-labelledby=&quot;cenc-t cenc-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;cenc-t&quot;&gt;The encoder&#39;s two modes and the work owed between them&lt;/title&gt;
&lt;desc id=&quot;cenc-d&quot;&gt;Two large state boxes: EFilling on the left, buffer a group, and EEmitting on the right, drive code then data. EFilling has a self-loop labelled store non-zero byte. An accent arrow from EFilling to EEmitting is labelled with three triggers: b equals zero, last byte, or group full at 254; a note says the full case stashes the byte in ePend. EEmitting has a self-loop labelled emit code then data, gated on downstream ready. A lower arrow returns from EEmitting to EFilling labelled more input, group drained. A short accent arrow from EEmitting reaches a done terminal on the right labelled input ended, nothing owed. Below EEmitting a bracket lists the work owed before returning: eFinal for a final empty group, and ePend to reprocess a stashed byte.&lt;/desc&gt;
&lt;style&gt;
.cenc{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.cenc .st{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.cenc .term{fill:var(--bg-main);stroke:var(--fg-main);stroke-width:2}
.cenc .wire{stroke:var(--fg-main);stroke-width:2;fill:none}
.cenc .hot{stroke:var(--accent);stroke-width:2.5;fill:none}
.cenc .loop{stroke:var(--fg-main);stroke-width:2;fill:none}
.cenc .bracket{stroke:var(--fg-dim);stroke-width:1.5;fill:none}
.cenc text{font-family:var(--sans)}
.cenc .name{fill:var(--fg-main);font-family:var(--mono);font-size:15px}
.cenc .sub{fill:var(--fg-dim);font-size:12px}
.cenc .lab{fill:var(--fg-main);font-size:12px}
.cenc .labA{fill:var(--accent);font-size:12px}
.cenc .dim{fill:var(--fg-dim);font-size:11.5px}
.cenc .ah{fill:var(--fg-main)}
.cenc .ahA{fill:var(--accent)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;cenc-a&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;cenc-aa&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ahA&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;rect class=&quot;st&quot; x=&quot;70&quot; y=&quot;118&quot; width=&quot;176&quot; height=&quot;64&quot; rx=&quot;8&quot;/&gt;
&lt;rect class=&quot;st&quot; x=&quot;430&quot; y=&quot;118&quot; width=&quot;196&quot; height=&quot;64&quot; rx=&quot;8&quot;/&gt;
&lt;circle class=&quot;term&quot; cx=&quot;700&quot; cy=&quot;150&quot; r=&quot;20&quot;/&gt;
&lt;circle class=&quot;term&quot; cx=&quot;700&quot; cy=&quot;150&quot; r=&quot;14&quot;/&gt;
&lt;path class=&quot;loop&quot; d=&quot;M120,118 C114,80 202,80 196,118&quot; marker-end=&quot;url(#cenc-a)&quot;/&gt;
&lt;path class=&quot;hot&quot; d=&quot;M246,138 C330,120 350,120 428,138&quot; marker-end=&quot;url(#cenc-aa)&quot;/&gt;
&lt;path class=&quot;loop&quot; d=&quot;M470,118 C464,80 592,80 586,118&quot; marker-end=&quot;url(#cenc-a)&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M428,168 C350,196 330,196 248,172&quot; marker-end=&quot;url(#cenc-a)&quot;/&gt;
&lt;line class=&quot;hot&quot; x1=&quot;626&quot; y1=&quot;150&quot; x2=&quot;678&quot; y2=&quot;150&quot; marker-end=&quot;url(#cenc-aa)&quot;/&gt;
&lt;text class=&quot;name&quot; x=&quot;158&quot; y=&quot;150&quot; text-anchor=&quot;middle&quot;&gt;EFilling&lt;/text&gt;
&lt;text class=&quot;sub&quot;  x=&quot;158&quot; y=&quot;169&quot; text-anchor=&quot;middle&quot;&gt;buffer a group&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;528&quot; y=&quot;150&quot; text-anchor=&quot;middle&quot;&gt;EEmitting&lt;/text&gt;
&lt;text class=&quot;sub&quot;  x=&quot;528&quot; y=&quot;169&quot; text-anchor=&quot;middle&quot;&gt;drive code + data&lt;/text&gt;
&lt;text class=&quot;dim&quot;  x=&quot;158&quot; y=&quot;72&quot; text-anchor=&quot;middle&quot;&gt;store non-zero byte&lt;/text&gt;
&lt;text class=&quot;labA&quot; x=&quot;337&quot; y=&quot;103&quot; text-anchor=&quot;middle&quot;&gt;b = 0 · last · full(254)&lt;/text&gt;
&lt;text class=&quot;dim&quot;  x=&quot;337&quot; y=&quot;212&quot; text-anchor=&quot;middle&quot;&gt;more input, group drained&lt;/text&gt;
&lt;text class=&quot;dim&quot;  x=&quot;528&quot; y=&quot;72&quot; text-anchor=&quot;middle&quot;&gt;emit code, then data (if dsReady)&lt;/text&gt;
&lt;text class=&quot;dim&quot;  x=&quot;700&quot; y=&quot;196&quot; text-anchor=&quot;middle&quot;&gt;input ended,&lt;/text&gt;
&lt;text class=&quot;dim&quot;  x=&quot;700&quot; y=&quot;210&quot; text-anchor=&quot;middle&quot;&gt;nothing owed&lt;/text&gt;
&lt;path class=&quot;bracket&quot; d=&quot;M436,196 V204 H620 V196&quot;/&gt;
&lt;text class=&quot;dim&quot;  x=&quot;528&quot; y=&quot;224&quot; text-anchor=&quot;middle&quot;&gt;owed before returning:&lt;/text&gt;
&lt;text class=&quot;labA&quot; x=&quot;528&quot; y=&quot;240&quot; text-anchor=&quot;middle&quot;&gt;eFinal (final empty group) · ePend (reprocess stashed byte)&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The encoder as a two-beat machine. &lt;code&gt;EFilling&lt;/code&gt; buffers non-zero bytes (self-loop) until a byte closes the group (accent): a zero, the last byte, or the 254th byte — the last of which stashes the offending byte in &lt;code&gt;ePend&lt;/code&gt;. &lt;code&gt;EEmitting&lt;/code&gt; drives &lt;code&gt;code ++ group&lt;/code&gt; out one byte per ready cycle, then settles any owed work before it leaves: &lt;code&gt;eFinal&lt;/code&gt; emits the trailing empty group a payload-ending zero requires, and &lt;code&gt;ePend&lt;/code&gt; folds a stashed byte into a fresh group. Only with nothing owed and the input ended does it reset and pulse &lt;code&gt;done&lt;/code&gt;.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Every emitted byte gets its own cycle, so the trace is longer than the decode,
but it breaks into three clean groups:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;11&lt;/code&gt;, &lt;code&gt;22&lt;/code&gt;&lt;/strong&gt; (fill) — non-zero, not last: &lt;code&gt;store&lt;/code&gt; each; &lt;code&gt;eFill&lt;/code&gt; climbs to
&lt;code&gt;2&lt;/code&gt;, and we stay in &lt;code&gt;EFilling&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;00&lt;/code&gt;&lt;/strong&gt; (fill) — the zero closes the group &lt;code&gt;[11 22]&lt;/code&gt;; switch to &lt;code&gt;EEmitting&lt;/code&gt;.
&lt;code&gt;eFinal&lt;/code&gt; and &lt;code&gt;eLast&lt;/code&gt; stay &lt;code&gt;False&lt;/code&gt;: this zero is not the last byte.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;emit&lt;/strong&gt; — &lt;code&gt;eIx 0&lt;/code&gt;: code &lt;code&gt;= eFill + 1 = 03&lt;/code&gt;; then data &lt;code&gt;11&lt;/code&gt;, then &lt;code&gt;22&lt;/code&gt;. The
cursor runs past the data with nothing owed (&lt;code&gt;eFinal&lt;/code&gt; false, &lt;code&gt;ePend&lt;/code&gt; none,
&lt;code&gt;eLast&lt;/code&gt; false), so back to &lt;code&gt;EFilling&lt;/code&gt;. Out: &lt;code&gt;03 11 22&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;33&lt;/code&gt;&lt;/strong&gt; (fill) — &lt;code&gt;store&lt;/code&gt;, &lt;code&gt;eFill = 1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;00&lt;/code&gt;&lt;/strong&gt; (fill, last) — the zero closes the group &lt;code&gt;[33]&lt;/code&gt;, and it is the last
byte, so set &lt;code&gt;eFinal = eLast = True&lt;/code&gt;. Switch to emit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;emit&lt;/strong&gt; — &lt;code&gt;eIx 0&lt;/code&gt;: code &lt;code&gt;= eFill + 1 = 02&lt;/code&gt;; then data &lt;code&gt;33&lt;/code&gt;. Out:
&lt;code&gt;03 11 22 02 33&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;emit&lt;/strong&gt; — cursor past the data; now &lt;code&gt;eFinal&lt;/code&gt; fires, clearing &lt;code&gt;eFill&lt;/code&gt; for one
&lt;em&gt;more&lt;/em&gt; empty group and looping.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;emit&lt;/strong&gt; — code &lt;code&gt;= 0 + 1 = 01&lt;/code&gt; for that final empty group. Then &lt;code&gt;ePend&lt;/code&gt; is
&lt;code&gt;Nothing&lt;/code&gt; and &lt;code&gt;eLast&lt;/code&gt; is &lt;code&gt;True&lt;/code&gt;, so reset to &lt;code&gt;initEnc&lt;/code&gt; and pulse &lt;code&gt;done&lt;/code&gt;. Out:
&lt;code&gt;03 11 22 02 33 01&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Five bytes of payload became six on the wire, and steps 7–8 are where the extra
one is minted: &lt;code&gt;eFinal&lt;/code&gt; adds the trailing &lt;code&gt;01&lt;/code&gt; that a payload ending in a zero
always owes. That single byte &lt;em&gt;is&lt;/em&gt; the “consistent overhead” — the final group
has no zero in the payload to pay for its code byte, so it costs one, flat, and
COBS never charges more than one such byte per 254.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-overhead-1&quot;&gt;&lt;a href=&quot;#fn-overhead&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-254-boundary-where-the-encoder-earns-its-length&quot;&gt;The 254 boundary, where the encoder earns its length&lt;/h3&gt;
&lt;p&gt;Everything genuinely hard about the encoder lives at the full-group boundary, and
three vectors from the test suite map it exactly. All three feed non-zero runs
around the 254-byte cap; watch how the guard order pays off.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;254 non-zero bytes, &lt;code&gt;[1..254]&lt;/code&gt;&lt;/strong&gt;, the last flagged. The 254th byte arrives
while &lt;code&gt;eFill&lt;/code&gt; is still &lt;code&gt;253&lt;/code&gt;, so &lt;code&gt;eFill == 254&lt;/code&gt; does &lt;em&gt;not&lt;/em&gt; fire — the &lt;code&gt;lst&lt;/code&gt;
guard does. &lt;code&gt;store&lt;/code&gt; fills the buffer to exactly 254 and emits one full group,
code &lt;code&gt;0xFF&lt;/code&gt; then the 254 bytes: &lt;code&gt;FF,&amp;lt;254&amp;gt;&lt;/code&gt;. No trailing anything, because a
non-zero last byte owes no final group. One full group, and done.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;255 non-zero bytes, &lt;code&gt;[1..255]&lt;/code&gt;&lt;/strong&gt;. Now the 255th byte (&lt;code&gt;lst&lt;/code&gt;) arrives with
&lt;code&gt;eFill&lt;/code&gt; already at &lt;code&gt;254&lt;/code&gt;, so &lt;code&gt;eFill == 254&lt;/code&gt; fires &lt;em&gt;first&lt;/em&gt;: flush &lt;code&gt;FF,&amp;lt;254&amp;gt;&lt;/code&gt; and
stash byte 255 in &lt;code&gt;ePend&lt;/code&gt;. After the flush, &lt;code&gt;ePend&lt;/code&gt;’s &lt;code&gt;pl&lt;/code&gt; case stores it into a
fresh group and emits &lt;code&gt;02,255&lt;/code&gt;. Result: &lt;code&gt;FF,&amp;lt;254&amp;gt;&lt;/code&gt; then &lt;code&gt;02,255&lt;/code&gt;. The run split
across the cap, exactly as a continuation should.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;254 non-zero bytes then a &lt;code&gt;0x00&lt;/code&gt;&lt;/strong&gt;, the zero flagged last. The 254 fill the
buffer; the zero arrives with &lt;code&gt;eFill == 254&lt;/code&gt;, so — and this is the whole point
of the guard order — &lt;code&gt;eFill == 254&lt;/code&gt; fires &lt;em&gt;before&lt;/em&gt; &lt;code&gt;b == 0&lt;/code&gt;: flush &lt;code&gt;FF,&amp;lt;254&amp;gt;&lt;/code&gt;
and stash the zero in &lt;code&gt;ePend&lt;/code&gt;. After the flush, &lt;code&gt;ePend&lt;/code&gt;’s &lt;code&gt;pb == 0&lt;/code&gt; case emits a
fresh empty group &lt;code&gt;01&lt;/code&gt; for the zero and, because it was last, sets &lt;code&gt;eFinal&lt;/code&gt;,
which adds the final &lt;code&gt;01&lt;/code&gt;. Result: &lt;code&gt;FF,&amp;lt;254&amp;gt;,01,01&lt;/code&gt;. The zero survived as its
own group instead of being swallowed by the &lt;code&gt;0xFF&lt;/code&gt; — the bug the guard order
exists to prevent.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Three vectors, three faces of one boundary, and the encoder handles them with a
stash register and a carefully ranked &lt;code&gt;case&lt;/code&gt;. That is what those extra fields and
that extra mode buy: an encoder that never drops a byte at the seam.&lt;/p&gt;
&lt;h2 id=&quot;the-tests&quot;&gt;The tests&lt;/h2&gt;
&lt;p&gt;The module is under a hundred lines of logic, and its tests pin it down the same
way the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt;’s did — from more than one direction at once — but with a
tool the CRC only hinted at: a &lt;strong&gt;pure reference model&lt;/strong&gt; standing in as an oracle.&lt;/p&gt;
&lt;p&gt;There are, in fact, &lt;em&gt;two&lt;/em&gt; COBS implementations in Tamal. The one we just read,
&lt;code&gt;Tamal.Loader.Cobs&lt;/code&gt;, is the streaming, one-byte-at-a-time form the hardware runs
(the spec calls it §9). Beside it lives &lt;code&gt;Tamal.Wire.Cobs&lt;/code&gt; (spec §5), a pure
list-to-list reference:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;cobsEncode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;cobsDecode&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;cobsEncode&lt;/code&gt; is COBS written the easy way — fold over a list, accumulate a group,
&lt;code&gt;emit&lt;/code&gt; it at each zero — with no state machine, no back-pressure, no &lt;code&gt;is-last&lt;/code&gt;
flag, nothing but the algorithm. It is short enough to read and trust at a glance,
and &lt;em&gt;that&lt;/em&gt; is its job: it is the specification made executable. The streaming
steps are the optimised, clocked implementation of the very same function, and
the tests weld the two together:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;streaming decode of cobsEncode x reconstructs x&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  x &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Gen.&lt;/span&gt;&lt;span&gt;list &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Range.&lt;/span&gt;&lt;span&gt;linear &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 300&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; genByteZeros&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;dec&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; bad&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; decDrive &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cobsEncode x&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  dec &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;===&lt;/span&gt;&lt;span&gt; x&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  bad &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;===&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;streaming encode equals cobsEncode (non-empty)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  x &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Gen.&lt;/span&gt;&lt;span&gt;list &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Range.&lt;/span&gt;&lt;span&gt;linear &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 300&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; genByteZeros&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  encDrive x &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;===&lt;/span&gt;&lt;span&gt; cobsEncode x&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first drives the streaming decoder over &lt;code&gt;cobsEncode x&lt;/code&gt; — the oracle’s output
— and demands the original &lt;code&gt;x&lt;/code&gt; back, un-mangled and not malformed. The second is
the sharper claim: the streaming encoder, byte for byte, produces &lt;em&gt;exactly&lt;/em&gt; what
the pure &lt;code&gt;cobsEncode&lt;/code&gt; produces, for hundreds of random payloads. &lt;a rel=&quot;external&quot; href=&quot;https://hedgehog.qa&quot;&gt;Hedgehog&lt;/a&gt;
manufactures the payloads with &lt;code&gt;genByteZeros&lt;/code&gt;, a generator deliberately about
one-quarter zeros so that group boundaries — the whole point of COBS — come up
constantly rather than by luck.&lt;/p&gt;
&lt;p&gt;But &lt;code&gt;genByteZeros&lt;/code&gt; has a blind spot, and the suite knows it: a random
quarter-zero list almost never contains a 254-byte run of non-zeros, so it never
reaches the full-group machinery we spent so long on. So there is a second
generator, &lt;code&gt;genRuns&lt;/code&gt;, that builds concatenated runs of up to 260 non-zero bytes
each — straddling the cap on purpose — and the same equivalence is asserted over
&lt;em&gt;it&lt;/em&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;streaming encode equals cobsEncode (boundary runs)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  x &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Gen.&lt;/span&gt;&lt;span&gt;filter &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;not &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; genRuns&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  encDrive x &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;===&lt;/span&gt;&lt;span&gt; cobsEncode x&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the round-trips, both directions streaming, over both generators —
&lt;code&gt;decDrive (encDrive x) === (x, False)&lt;/code&gt; — and, nailing the corner the guard order
turns on, the explicit regression:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testCase &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;encode 254 non-zero then 0x00 == FF,&amp;lt;254&amp;gt;,01,01&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  $&lt;/span&gt;&lt;span&gt; encDrive &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;L.&lt;/span&gt;&lt;span&gt;map fromIntegral &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ..&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 254&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Int&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0x00&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  @?=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0xFF&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; :&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;map fromIntegral &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ..&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 254&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Int&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0x01&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x01&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That last one is the guardrail bolted directly over the trap: if some future edit
ever lets a zero fold into a full group, this line goes red before the property
tests even get their turn. It is the same move the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC post&lt;/a&gt; admired — a
hand-derived witness bolted to the workbench beside the sweeping property — here
guarding the one input a random generator is least likely to stumble onto.&lt;/p&gt;
&lt;p&gt;The shape of the whole suite is worth stepping back for. The pure model is the
&lt;em&gt;meaning&lt;/em&gt; of COBS; the streaming steps are its &lt;em&gt;hardware&lt;/em&gt;; and the property tests
are the proof that the hardware means what the model says, checked against a fresh
shower of payloads every run, with the awkward boundary explicitly seeded so it is
never left to chance. You get to write the algorithm twice — once for clarity,
once for the clock — and let the machine hold the two versions to each
other.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-purestream-1&quot;&gt;&lt;a href=&quot;#fn-purestream&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-we-read&quot;&gt;What we read&lt;/h2&gt;
&lt;p&gt;Two pure step functions, no clock between them, and underneath them a whole
framing layer: a decoder that walks a COBS stream a group at a time and
manufactures each implied zero &lt;em&gt;lazily&lt;/em&gt; — at the next code byte, so the last
group’s phantom zero falls away at the delimiter for free — and an encoder that
buffers a group, flushes &lt;code&gt;code ++ group&lt;/code&gt;, and guards the 254-byte seam so
carefully that a zero landing on a full group becomes its own honest little group
instead of vanishing. Beside them a pure reference model that is the same
algorithm written for people, and a property suite that welds the fast version to
the readable one.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt; gave us a byte pipe; the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-top/&quot;&gt;top&lt;/a&gt;
sealed it; and today the pipe became a &lt;strong&gt;protocol&lt;/strong&gt; — a stream with a reserved
delimiter and a codec that honours it in both directions. What is still missing is
the little machine that &lt;em&gt;drives&lt;/em&gt; these steps: the loader proper, the &lt;code&gt;mealy&lt;/code&gt; that
catches &lt;code&gt;rxByte&lt;/code&gt; strobes and clocks &lt;code&gt;cobsDecodeStep&lt;/code&gt;, writes decoded words into
instruction memory, and runs &lt;code&gt;cobsEncodeStep&lt;/code&gt; in reverse to drain the engine’s
results back out while &lt;code&gt;txReady&lt;/code&gt; is high. It wears the silhouette we have now read
five times — a sum for its phase, a record for the rest, a step function beside a
&lt;code&gt;mealy&lt;/code&gt; — and it is where these two codecs finally get their clock. That is the
next thing we read. And past it, at last, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;Engine&lt;/a&gt; the whole project
has been circling.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-postcard&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/jamesmunns/postcard-rpc&quot;&gt;postcard-rpc&lt;/a&gt; sits on top of &lt;code&gt;postcard&lt;/code&gt;, a &lt;code&gt;serde&lt;/code&gt;
serializer for the embedded world that encodes Rust types into a compact,
&lt;code&gt;no_std&lt;/code&gt;-friendly wire format. COBS is the framing underneath it — the layer that
turns &lt;code&gt;postcard&lt;/code&gt;’s byte blobs into delimited messages a receiver can find the
edges of. Meeting COBS there, in anger, doing a real job on a real link, is a
better teacher than any diagram: you internalise very quickly that a byte stream
is not a message stream until something draws the borders. &lt;a href=&quot;#fr-postcard-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-consistent&quot;&gt;
&lt;p&gt;The “consistent” in Consistent Overhead Byte Stuffing is a promise
about the &lt;em&gt;worst case&lt;/em&gt;. Older byte-stuffing schemes — the escape-character trick
SLIP uses, say — have overhead that depends on the data: a payload full of the
reserved byte can &lt;em&gt;double&lt;/em&gt; in size, because every occurrence becomes two bytes.
COBS refuses that variance. Its overhead is at most one byte per 254 bytes of
payload, roughly 0.4%, no matter what the data is — and, just as importantly, it
is &lt;em&gt;bounded and predictable&lt;/em&gt;, which is what lets a fixed-size buffer on the far
end be sized with confidence. You pay a small, flat tax rather than an occasional
ruinous one. That flatness is the property the name is bragging about. &lt;a href=&quot;#fr-consistent-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-overhead&quot;&gt;
&lt;p&gt;Where does the “+1” go in the happy case? A payload with &lt;em&gt;no&lt;/em&gt; zeros
and under 255 bytes encodes to a single group: one code byte, then the payload
verbatim. That is one byte of overhead for the whole frame — the code byte the
delimiter cannot be. Every additional group (born at each zero, or at each 254-byte
cap) costs one more code byte, but &lt;em&gt;buys back&lt;/em&gt; the zero it replaces, so a
zero-dense payload is close to break-even and a zero-free one pays the flat
one-per-254. The delimiter itself is the only byte that is pure tax, and it buys
the thing the whole scheme exists for. &lt;a href=&quot;#fr-overhead-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-purestream&quot;&gt;
&lt;p&gt;The two-implementations pattern — a pure list model beside a
streaming, clocked one, proven equal — is not unique to COBS in Tamal; it is
becoming the house style. The pure model is where you &lt;em&gt;think&lt;/em&gt;, unencumbered by
back-pressure and cycle timing; the streaming model is where you &lt;em&gt;ship&lt;/em&gt;, and it
has to survive a consumer that stalls and a producer that dribbles. Keeping both
and testing one against the other means the gnarly version is never the only place
the algorithm is written down — there is always a clean copy to check it against,
and to read when the clocked one stops making sense. &lt;a href=&quot;#fr-purestream-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Tamal: Wiring Uart Through Composition</title>
    <published>2026-07-24T09:00:00+00:00</published>
    <updated>2026-07-24T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-uart-top/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-uart-top/</id>
    <summary type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt; we read yesterday closed the keystone. It wired the
transmitter’s line into its own, shared one tick between them, fed a
random byte in one end and got the same byte, bit-for-bit, out the other
— first with an always-true tick, then with the real fractional-3.125
heartbeat the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator&lt;/a&gt; fought to keep honest. By the
last paragraph the UART &lt;em&gt;worked&lt;/em&gt;, end to end, and I told you it was
behind us.&lt;/p&gt;
&lt;p&gt;It was — as behaviour. But there is a small dishonesty in having
watched the whole thing run without ever reading the file that makes it
a &lt;em&gt;single thing&lt;/em&gt;. Those loopback tests called &lt;code&gt;uartTx&lt;/code&gt; and &lt;code&gt;uartRx&lt;/code&gt; and
shared a &lt;code&gt;tick&lt;/code&gt; by hand, or they called &lt;code&gt;uart&lt;/code&gt; — and that &lt;code&gt;uart&lt;/code&gt;, the
five-line module this post is about, we leaned on and never once read.
This is the shortest post in the series, because its subject is the
shortest module in the UART: the top, the umbrella, the seam. And it
earns its own post for exactly one reason — it is the first Tamal
module with &lt;strong&gt;no behaviour of its own.&lt;/strong&gt; The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; computed a
residue; the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator&lt;/a&gt; counted a phase; the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; drove a wire; the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt; recovered one.
&lt;code&gt;Tamal.Uart&lt;/code&gt; does none of these. It &lt;em&gt;connects&lt;/em&gt; the ones that do. Its
whole content is composition, and composition — how three blocks that
each own one idea become one block that owns none — is the subject.&lt;/p&gt;
&lt;p&gt;Every post so far ended by pointing &lt;em&gt;forward&lt;/em&gt;, at the next block. This
one points &lt;em&gt;inward&lt;/em&gt;, at the wiring.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt; we read yesterday closed the keystone. It wired the
transmitter’s line into its own, shared one tick between them, fed a
random byte in one end and got the same byte, bit-for-bit, out the other
— first with an always-true tick, then with the real fractional-3.125
heartbeat the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator&lt;/a&gt; fought to keep honest. By the
last paragraph the UART &lt;em&gt;worked&lt;/em&gt;, end to end, and I told you it was
behind us.&lt;/p&gt;
&lt;p&gt;It was — as behaviour. But there is a small dishonesty in having
watched the whole thing run without ever reading the file that makes it
a &lt;em&gt;single thing&lt;/em&gt;. Those loopback tests called &lt;code&gt;uartTx&lt;/code&gt; and &lt;code&gt;uartRx&lt;/code&gt; and
shared a &lt;code&gt;tick&lt;/code&gt; by hand, or they called &lt;code&gt;uart&lt;/code&gt; — and that &lt;code&gt;uart&lt;/code&gt;, the
five-line module this post is about, we leaned on and never once read.
This is the shortest post in the series, because its subject is the
shortest module in the UART: the top, the umbrella, the seam. And it
earns its own post for exactly one reason — it is the first Tamal
module with &lt;strong&gt;no behaviour of its own.&lt;/strong&gt; The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; computed a
residue; the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator&lt;/a&gt; counted a phase; the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; drove a wire; the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt; recovered one.
&lt;code&gt;Tamal.Uart&lt;/code&gt; does none of these. It &lt;em&gt;connects&lt;/em&gt; the ones that do. Its
whole content is composition, and composition — how three blocks that
each own one idea become one block that owns none — is the subject.&lt;/p&gt;
&lt;p&gt;Every post so far ended by pointing &lt;em&gt;forward&lt;/em&gt;, at the next block. This
one points &lt;em&gt;inward&lt;/em&gt;, at the wiring.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;the-entire-source&quot;&gt;The entire source&lt;/h2&gt;
&lt;p&gt;Minus the license header and the doc-comment — and there is nothing
else to minus, no helper to hide — here is &lt;code&gt;src/Tamal/Uart.hs&lt;/code&gt; in full:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Uart&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; uart&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Clash.Prelude&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Uart.BaudGen&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;oversampleTick&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Uart.Rx&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;uartRx&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Uart.Tx&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;uartTx&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;uart&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  forall&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; baud&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;HiddenClockResetEnable&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; KnownDomain&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; KnownNat&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; baud&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  SNat&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; baud&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;uart baud rxLine txByte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;rxByte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; rxErr&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; txLine&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; txReady&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tick &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; oversampleTick baud&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;rxByte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; rxErr&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; uartRx tick rxLine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;txLine&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; txReady&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; uartTx tick txByte&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is the whole module. A header with something new in it, a type
signature longer than the code it types, and a body of four lines —
one to name the tick, two to place the machines, and a return that
bundles their outputs. No &lt;code&gt;data&lt;/code&gt; declarations, because the top remembers
nothing. No &lt;code&gt;mealy&lt;/code&gt;, no &lt;code&gt;register&lt;/code&gt;, because it clocks nothing. No
&lt;code&gt;where&lt;/code&gt;-hidden &lt;code&gt;step&lt;/code&gt;, because it decides nothing. For the first time in
the series the entire module fits in a paragraph, and every line is
either a type or a wire.&lt;/p&gt;
&lt;h2 id=&quot;three-imports-and-one-door&quot;&gt;Three imports and one door&lt;/h2&gt;
&lt;p&gt;The opening beat is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; module’s, played a fifth time, and I
will be as quick as the fifth playing deserves:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Uart&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; uart&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Clash.Prelude&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;module Tamal.Uart&lt;/code&gt; names the module after its path — &lt;code&gt;src/Tamal/Uart.hs&lt;/code&gt;,
the one &lt;em&gt;without&lt;/em&gt; a third dot, the parent directory of
&lt;code&gt;Tamal.Uart.BaudGen&lt;/code&gt;, &lt;code&gt;Tamal.Uart.Rx&lt;/code&gt;, and &lt;code&gt;Tamal.Uart.Tx&lt;/code&gt;. Only &lt;code&gt;uart&lt;/code&gt;
leaves through the one door in the wall, and this time there is genuinely
nothing else behind it: no private &lt;code&gt;step&lt;/code&gt;, no helper, no state type. The
export list has never been so nearly the whole file. &lt;code&gt;import Clash.Prelude&lt;/code&gt; is the same prelude swap every earlier post dwelt on, the
line that trades ordinary Haskell’s furniture for &lt;code&gt;Signal&lt;/code&gt;, &lt;code&gt;Bit&lt;/code&gt;, and
the rest of the vocabulary that lowers to gates; I will not re-derive it
a fifth time either.&lt;/p&gt;
&lt;p&gt;What &lt;em&gt;is&lt;/em&gt; new sits just underneath it — three imports, the first the
series has had cause to read:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Uart.BaudGen&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;oversampleTick&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Uart.Rx&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;uartRx&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Uart.Tx&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;uartTx&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each pulls exactly one name across from a child module — the one name
that module’s own export list let out. &lt;code&gt;oversampleTick&lt;/code&gt; from the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud
generator&lt;/a&gt;, &lt;code&gt;uartRx&lt;/code&gt; from the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt;, &lt;code&gt;uartTx&lt;/code&gt; from the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt;. These three imports are the top’s &lt;em&gt;material&lt;/em&gt;: it has
no primitives of its own to work with, only the three finished blocks the
last three posts built, each sealed to a single public name and each
imported by that name. And the parentheses on each import are the mirror
of the parentheses on each &lt;code&gt;module&lt;/code&gt; line we read one at a time. The
export list said “only this leaves”; the import says “only this enters.”
Set the four modules side by side and the doors line up exactly:
&lt;code&gt;BaudGen&lt;/code&gt; lets out &lt;code&gt;oversampleTick&lt;/code&gt; and the top lets it in; &lt;code&gt;Rx&lt;/code&gt; lets out
&lt;code&gt;uartRx&lt;/code&gt; and the top lets it in; &lt;code&gt;Tx&lt;/code&gt; lets out &lt;code&gt;uartTx&lt;/code&gt; and the top lets
it in. The top is the room those three doors open into.&lt;/p&gt;
&lt;h2 id=&quot;the-type-the-numbers-come-home&quot;&gt;The type: the numbers come home&lt;/h2&gt;
&lt;p&gt;The signature is longer than the body, and every part of it is a
reunion:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;uart&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  forall&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; baud&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;HiddenClockResetEnable&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; KnownDomain&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; KnownNat&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; baud&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  SNat&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; baud&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Under the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt;’s reading a signature is half the
documentation&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-repetitive-1&quot;&gt;&lt;a href=&quot;#fn-repetitive&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;, and this one is visibly &lt;em&gt;assembled&lt;/em&gt; out of
the three signatures we already know. Begin with what the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt; made a point of &lt;em&gt;not&lt;/em&gt;
having. Both of them, you will remember, shed every number from their
types: one lone &lt;code&gt;HiddenClockResetEnable dom&lt;/code&gt; constraint, no &lt;code&gt;forall&lt;/code&gt;,
no &lt;code&gt;SNat&lt;/code&gt;, no &lt;code&gt;KnownNat&lt;/code&gt;, because neither block names a baud rate —
a bit is sixteen ticks and the ticks arrive from elsewhere. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud
generator&lt;/a&gt; was the opposite: it carried the whole numeric
burden — &lt;code&gt;forall baud dom&lt;/code&gt;, the &lt;code&gt;KnownDomain&lt;/code&gt;/&lt;code&gt;KnownNat&lt;/code&gt; pile, and
the &lt;code&gt;SNat baud&lt;/code&gt; argument — because it was the one block that turns a
baud rate into a tick.&lt;/p&gt;
&lt;p&gt;Now look at the top’s constraints and first argument: &lt;code&gt;forall baud dom&lt;/code&gt;,
&lt;code&gt;(HiddenClockResetEnable dom, KnownDomain dom, KnownNat baud)&lt;/code&gt;, &lt;code&gt;SNat baud -&amp;gt;&lt;/code&gt;. They are the baud generator’s, character for character. &lt;strong&gt;The
numbers come home.&lt;/strong&gt; The top wears the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator’s&lt;/a&gt; exact
type obligations because it &lt;em&gt;owns&lt;/em&gt; the baud generator: it is the block
that will call &lt;code&gt;oversampleTick baud&lt;/code&gt;, so it must be handed the &lt;code&gt;SNat baud&lt;/code&gt; to pass along, and must carry the &lt;code&gt;KnownNat baud&lt;/code&gt; and &lt;code&gt;KnownDomain dom&lt;/code&gt; that &lt;code&gt;oversampleTick&lt;/code&gt; needs to read the clock frequency out of the
domain. The transmitter and receiver got to be numberless precisely
because the top volunteered to hold the number for all three. Factoring
the timing into one small block did not delete the &lt;code&gt;SNat&lt;/code&gt;; it
&lt;em&gt;concentrated&lt;/em&gt; it — out of TX and RX, into BaudGen, and back out to
whoever owns BaudGen, which is here.&lt;/p&gt;
&lt;p&gt;Then the ports, and they are just as plainly a concatenation. Two
arguments go in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Signal dom Bit&lt;/code&gt; — the RX line, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver’s&lt;/a&gt; one input, the
asynchronous wire from a pin.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Signal dom (Maybe (BitVector 8))&lt;/code&gt; — the TX byte, the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter’s&lt;/a&gt; one input, the &lt;code&gt;Just b&lt;/code&gt; send request.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And the four-tuple comes out as &lt;code&gt;(rxByte, rxErr, txLine, txReady)&lt;/code&gt; —
the receiver’s two outputs followed by the transmitter’s two:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Signal dom (Maybe (BitVector 8))&lt;/code&gt; and &lt;code&gt;Signal dom Bool&lt;/code&gt; — &lt;code&gt;uartRx&lt;/code&gt;’s
byte strobe and framing-error strobe, unchanged.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Signal dom Bit&lt;/code&gt; and &lt;code&gt;Signal dom Bool&lt;/code&gt; — &lt;code&gt;uartTx&lt;/code&gt;’s line and its
&lt;code&gt;ready&lt;/code&gt; flag, unchanged.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the top’s interface is exactly &lt;code&gt;uartRx&lt;/code&gt;’s external ports set beside
&lt;code&gt;uartTx&lt;/code&gt;’s external ports, with the baud generator’s type obligations
wrapped around the pair. And notice the one thing that is &lt;em&gt;absent&lt;/em&gt;. The
tick — &lt;code&gt;Signal dom Bool&lt;/code&gt;, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator’s&lt;/a&gt; output and
the first argument of &lt;em&gt;both&lt;/em&gt; &lt;code&gt;uartRx&lt;/code&gt; and &lt;code&gt;uartTx&lt;/code&gt; — appears nowhere in
&lt;code&gt;uart&lt;/code&gt;’s type. It was an input to each machine when we read them alone;
here it has gone &lt;strong&gt;internal.&lt;/strong&gt; That is the signature telling you, before
you read a line of the body, what composition did: it took the wire that
ran &lt;em&gt;between&lt;/em&gt; the blocks and tucked it inside, leaving only the wires
that still face the world. The heartbeat is now a private matter.&lt;/p&gt;
&lt;h2 id=&quot;four-lines-no-state&quot;&gt;Four lines, no state&lt;/h2&gt;
&lt;p&gt;Here is the body, the only part of the file that is neither ceremony nor
type:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;uart baud rxLine txByte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;rxByte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; rxErr&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; txLine&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; txReady&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tick &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; oversampleTick baud&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;rxByte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; rxErr&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; uartRx tick rxLine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;txLine&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; txReady&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; uartTx tick txByte&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read it as what it is: a wiring diagram written as equations. The head
binds the three inputs — &lt;code&gt;baud&lt;/code&gt;, &lt;code&gt;rxLine&lt;/code&gt;, &lt;code&gt;txByte&lt;/code&gt; — and returns the
four-tuple. The &lt;code&gt;where&lt;/code&gt; block defines the internal names the tuple is
built from. And every one of those definitions is an &lt;em&gt;instantiation&lt;/em&gt;, not
a computation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tick = oversampleTick baud&lt;/code&gt; — place one baud generator, hand it the
baud, call its output &lt;code&gt;tick&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(rxByte, rxErr) = uartRx tick rxLine&lt;/code&gt; — place one receiver, feed it
the tick and the line, name its two outputs.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(txLine, txReady) = uartTx tick txByte&lt;/code&gt; — place one transmitter,
feed it the tick and the byte, name its two outputs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is no arithmetic, no &lt;code&gt;case&lt;/code&gt;, no state threaded from cycle to cycle.
Every earlier module’s &lt;code&gt;where&lt;/code&gt; block hid &lt;em&gt;work&lt;/em&gt; — the baud generator’s
phase accumulator feeding back through a &lt;code&gt;register&lt;/code&gt;, the transmitter’s
&lt;code&gt;initTx&lt;/code&gt; and the &lt;code&gt;mealy&lt;/code&gt; lift over &lt;code&gt;txStep&lt;/code&gt;, the receiver’s synchronizer
and its &lt;code&gt;mealy&lt;/code&gt; over &lt;code&gt;rxStep&lt;/code&gt;. This &lt;code&gt;where&lt;/code&gt; block hides nothing but
&lt;em&gt;names for wires&lt;/em&gt;. &lt;code&gt;tick&lt;/code&gt; is not a value that gets computed and returned;
it is a net, and &lt;code&gt;oversampleTick baud&lt;/code&gt; is the sub-circuit driving it.
&lt;code&gt;rxByte&lt;/code&gt; is not a byte; it is the wire on which the receiver will present
its strobes.&lt;/p&gt;
&lt;p&gt;The tell is the ordering — or the absence of it. In an imperative
reading you would object that &lt;code&gt;tick&lt;/code&gt; is &lt;em&gt;used&lt;/em&gt; on the second and third
lines but &lt;em&gt;defined&lt;/em&gt; on the first, so the order is load-bearing: define
before use. In Haskell’s &lt;code&gt;where&lt;/code&gt;, and in the hardware it denotes, the
order does not matter at all. I could write the three bindings in any
sequence and the module would be identical, because they are not steps
executed in time but nets that all exist at once. &lt;code&gt;tick&lt;/code&gt; drives &lt;code&gt;uartRx&lt;/code&gt;
and &lt;code&gt;uartTx&lt;/code&gt; the way a wire drives the pins soldered to it —
simultaneously, continuously, with no notion of “first.” The &lt;code&gt;where&lt;/code&gt;
block is a net-list, and a net-list is a set, not a sequence.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-netlist-1&quot;&gt;&lt;a href=&quot;#fn-netlist&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;This is what it looks like when a module’s job is purely structural. It
adds no gate that computes anything and no flip-flop that remembers
anything; it adds only &lt;em&gt;connections&lt;/em&gt;. Put the four modules on a bench and
this file is the wiring loom between them — and, like a loom, it is
invisible in the behaviour and total in the structure. Nothing in the
UART works without it, and it does nothing but let the rest work.&lt;/p&gt;
&lt;h2 id=&quot;one-heartbeat-two-machines&quot;&gt;One heartbeat, two machines&lt;/h2&gt;
&lt;p&gt;Four lines, and only one of them carries a design decision. The other
three are forced: a receiver needs the line, a transmitter needs the
byte, the outputs are what they are. But the first line, and the way the
name it binds gets used &lt;em&gt;twice&lt;/em&gt;, is a genuine choice:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tick &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; oversampleTick baud&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;rxByte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; rxErr&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; uartRx tick rxLine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;txLine&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; txReady&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; uartTx tick txByte&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;tick&lt;/code&gt; is generated &lt;strong&gt;once&lt;/strong&gt; and fanned into &lt;strong&gt;both&lt;/strong&gt; machines. This is
decision 2 of the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;UART design&lt;/a&gt; — build RX and TX together,
sharing one oversample tick — and it is the whole reason the two halves
live in one module instead of two.&lt;/p&gt;
&lt;p&gt;Nothing in the types forced it. Each machine takes its own &lt;code&gt;Signal dom Bool&lt;/code&gt; tick, so I could have called &lt;code&gt;oversampleTick baud&lt;/code&gt; twice, once
for the receiver and once for the transmitter, and every signature
would still typecheck. It would even &lt;em&gt;work&lt;/em&gt; — two identical NCOs in
the same domain, same increment, same modulus, are deterministic
functions of the one clock and would count in perfect lock-step, bit
for bit. But &lt;em&gt;identical&lt;/em&gt; is the tell. You would be spending a second
phase accumulator, adder, and comparator to build an exact copy of a
signal you already have, and then leaning on the two staying exact —
a property you now maintain in &lt;em&gt;two&lt;/em&gt; places, where changing one
generator’s width or modulus and forgetting the other silently splits
the one clock into two that disagree&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-splitbaudgen-1&quot;&gt;&lt;a href=&quot;#fn-splitbaudgen&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;. And even kept in
step, the duplication states a falsehood about the design: that the
transmit clock and the receive clock are two things that happen to
agree, when the whole point is that they are &lt;strong&gt;one thing.&lt;/strong&gt; Sharing
&lt;code&gt;tick&lt;/code&gt; says the true thing in a single wire.&lt;/p&gt;
&lt;p&gt;And because they &lt;em&gt;are&lt;/em&gt; one thing, the loopback the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt; closed
rests on something firmer than luck. In a real UART the two ends of the
link are different chips with genuinely independent clocks, and the
receiver’s whole apparatus — oversample sixteen times, sample at the
center, majority-vote — exists precisely to &lt;em&gt;tolerate&lt;/em&gt; the drift
between them. Inside Tamal there is no drift to tolerate, because there
are not two clocks: the transmitter holds each bit for sixteen ticks and
the receiver centers its 7/8/9 window in the &lt;em&gt;same&lt;/em&gt; sixteen ticks, off
the &lt;em&gt;same&lt;/em&gt; accumulator. The center-sampling margin the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt;
spent a whole post earning is, in loopback, slack it never has to spend
— the sample lands dead-center by construction, because one counter is
timing both ends. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator&lt;/a&gt; promised it was handing
“the rest of the UART” an enable rather than a clock; this line is where
“the rest of the UART” turns out to be precisely two consumers, wired in
parallel across the one enable.&lt;/p&gt;
&lt;figure class=&quot;uarttop-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;uarttop&quot; viewBox=&quot;0 0 760 344&quot; role=&quot;img&quot; aria-labelledby=&quot;uarttop-t uarttop-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;uarttop-t&quot;&gt;The uart top module: one shared tick into a receiver and a transmitter&lt;/title&gt;
&lt;desc id=&quot;uarttop-d&quot;&gt;A dashed box labelled uart is the module boundary. Inside it sit three solid boxes: oversampleTick, the baud generator, at left centre; uartRx, the receiver, at upper right; and uartTx, the transmitter, at lower right. A compile-time baud parameter enters oversampleTick from the left as a dashed arrow. oversampleTick drives a single accent wire, the tick, which branches into both uartRx and uartTx — the shared heartbeat. Two runtime inputs cross the boundary from the left: rxLine into uartRx and txByte into uartTx. Four outputs cross the boundary to the right: rxByte and rxErr from uartRx, txLine and txReady from uartTx. The tick wire never crosses the boundary; it is internal to uart.&lt;/desc&gt;
&lt;style&gt;
.uarttop{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.uarttop .box{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.uarttop .mbox{fill:none;stroke:var(--fg-dim);stroke-width:1.5;stroke-dasharray:6 5}
.uarttop .wire{stroke:var(--fg-main);stroke-width:2;fill:none}
.uarttop .tickw{stroke:var(--accent);stroke-width:2.5;fill:none}
.uarttop .parm{stroke:var(--fg-dim);stroke-width:1.5;fill:none;stroke-dasharray:5 4}
.uarttop .node{fill:var(--accent)}
.uarttop text{font-family:var(--sans)}
.uarttop .name{fill:var(--fg-main);font-family:var(--mono);font-size:14px}
.uarttop .dim{fill:var(--fg-dim);font-size:11.5px}
.uarttop .sig{fill:var(--fg-dim);font-family:var(--mono);font-size:12px}
.uarttop .sigA{fill:var(--accent);font-family:var(--mono);font-size:12.5px}
.uarttop .mlab{fill:var(--fg-dim);font-family:var(--mono);font-size:12.5px}
.uarttop .ah{fill:var(--fg-main)}
.uarttop .aha{fill:var(--accent)}
.uarttop .ahd{fill:var(--fg-dim)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;ut-a&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;ut-aa&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;aha&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;ut-ad&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ahd&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;rect class=&quot;mbox&quot; x=&quot;150&quot; y=&quot;44&quot; width=&quot;470&quot; height=&quot;256&quot; rx=&quot;10&quot;/&gt;
&lt;text class=&quot;mlab&quot; x=&quot;160&quot; y=&quot;62&quot; text-anchor=&quot;start&quot;&gt;uart&lt;/text&gt;
&lt;rect class=&quot;box&quot; x=&quot;170&quot; y=&quot;150&quot; width=&quot;150&quot; height=&quot;52&quot; rx=&quot;6&quot;/&gt;
&lt;text class=&quot;name&quot; x=&quot;245&quot; y=&quot;173&quot; text-anchor=&quot;middle&quot;&gt;oversampleTick&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;245&quot; y=&quot;190&quot; text-anchor=&quot;middle&quot;&gt;baud generator&lt;/text&gt;
&lt;rect class=&quot;box&quot; x=&quot;400&quot; y=&quot;70&quot; width=&quot;140&quot; height=&quot;56&quot; rx=&quot;6&quot;/&gt;
&lt;text class=&quot;name&quot; x=&quot;470&quot; y=&quot;96&quot; text-anchor=&quot;middle&quot;&gt;uartRx&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;470&quot; y=&quot;113&quot; text-anchor=&quot;middle&quot;&gt;receiver&lt;/text&gt;
&lt;rect class=&quot;box&quot; x=&quot;400&quot; y=&quot;226&quot; width=&quot;140&quot; height=&quot;56&quot; rx=&quot;6&quot;/&gt;
&lt;text class=&quot;name&quot; x=&quot;470&quot; y=&quot;252&quot; text-anchor=&quot;middle&quot;&gt;uartTx&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;470&quot; y=&quot;269&quot; text-anchor=&quot;middle&quot;&gt;transmitter&lt;/text&gt;
&lt;line class=&quot;parm&quot; x1=&quot;78&quot; y1=&quot;176&quot; x2=&quot;168&quot; y2=&quot;176&quot; marker-end=&quot;url(#ut-ad)&quot;/&gt;
&lt;text class=&quot;sig&quot; x=&quot;40&quot; y=&quot;172&quot; text-anchor=&quot;start&quot;&gt;baud&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;40&quot; y=&quot;188&quot; text-anchor=&quot;start&quot;&gt;(SNat)&lt;/text&gt;
&lt;line class=&quot;wire&quot; x1=&quot;100&quot; y1=&quot;98&quot; x2=&quot;398&quot; y2=&quot;98&quot; marker-end=&quot;url(#ut-a)&quot;/&gt;
&lt;text class=&quot;sig&quot; x=&quot;40&quot; y=&quot;95&quot; text-anchor=&quot;start&quot;&gt;rxLine&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;40&quot; y=&quot;110&quot; text-anchor=&quot;start&quot;&gt;Bit&lt;/text&gt;
&lt;line class=&quot;wire&quot; x1=&quot;100&quot; y1=&quot;254&quot; x2=&quot;398&quot; y2=&quot;254&quot; marker-end=&quot;url(#ut-a)&quot;/&gt;
&lt;text class=&quot;sig&quot; x=&quot;40&quot; y=&quot;251&quot; text-anchor=&quot;start&quot;&gt;txByte&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;40&quot; y=&quot;267&quot; text-anchor=&quot;start&quot;&gt;Maybe byte&lt;/text&gt;
&lt;line class=&quot;tickw&quot; x1=&quot;320&quot; y1=&quot;176&quot; x2=&quot;376&quot; y2=&quot;176&quot;/&gt;
&lt;line class=&quot;tickw&quot; x1=&quot;376&quot; y1=&quot;112&quot; x2=&quot;376&quot; y2=&quot;268&quot;/&gt;
&lt;line class=&quot;tickw&quot; x1=&quot;376&quot; y1=&quot;112&quot; x2=&quot;398&quot; y2=&quot;112&quot; marker-end=&quot;url(#ut-aa)&quot;/&gt;
&lt;line class=&quot;tickw&quot; x1=&quot;376&quot; y1=&quot;268&quot; x2=&quot;398&quot; y2=&quot;268&quot; marker-end=&quot;url(#ut-aa)&quot;/&gt;
&lt;circle class=&quot;node&quot; cx=&quot;376&quot; cy=&quot;176&quot; r=&quot;3.5&quot;/&gt;
&lt;text class=&quot;sigA&quot; x=&quot;348&quot; y=&quot;168&quot; text-anchor=&quot;middle&quot;&gt;tick&lt;/text&gt;
&lt;line class=&quot;wire&quot; x1=&quot;540&quot; y1=&quot;86&quot; x2=&quot;688&quot; y2=&quot;86&quot; marker-end=&quot;url(#ut-a)&quot;/&gt;
&lt;text class=&quot;sig&quot; x=&quot;694&quot; y=&quot;90&quot; text-anchor=&quot;start&quot;&gt;rxByte&lt;/text&gt;
&lt;line class=&quot;wire&quot; x1=&quot;540&quot; y1=&quot;110&quot; x2=&quot;688&quot; y2=&quot;110&quot; marker-end=&quot;url(#ut-a)&quot;/&gt;
&lt;text class=&quot;sig&quot; x=&quot;694&quot; y=&quot;114&quot; text-anchor=&quot;start&quot;&gt;rxErr&lt;/text&gt;
&lt;line class=&quot;wire&quot; x1=&quot;540&quot; y1=&quot;242&quot; x2=&quot;688&quot; y2=&quot;242&quot; marker-end=&quot;url(#ut-a)&quot;/&gt;
&lt;text class=&quot;sig&quot; x=&quot;694&quot; y=&quot;246&quot; text-anchor=&quot;start&quot;&gt;txLine&lt;/text&gt;
&lt;line class=&quot;wire&quot; x1=&quot;540&quot; y1=&quot;266&quot; x2=&quot;688&quot; y2=&quot;266&quot; marker-end=&quot;url(#ut-a)&quot;/&gt;
&lt;text class=&quot;sig&quot; x=&quot;694&quot; y=&quot;270&quot; text-anchor=&quot;start&quot;&gt;txReady&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The whole of &lt;code&gt;Tamal.Uart&lt;/code&gt; as one picture. The dashed box is the module boundary; inside it a single &lt;code&gt;oversampleTick&lt;/code&gt; — the &lt;a href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator&lt;/a&gt; — drives one accent wire, the &lt;code&gt;tick&lt;/code&gt;, that fans into both &lt;code&gt;uartRx&lt;/code&gt; — the &lt;a href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt; — and &lt;code&gt;uartTx&lt;/code&gt; — the &lt;a href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt;. The compile-time &lt;code&gt;baud&lt;/code&gt; enters the generator as a dashed parameter, not a runtime wire. Six signals cross the boundary: &lt;code&gt;rxLine&lt;/code&gt; and &lt;code&gt;txByte&lt;/code&gt; in, and &lt;code&gt;rxByte&lt;/code&gt;, &lt;code&gt;rxErr&lt;/code&gt;, &lt;code&gt;txLine&lt;/code&gt;, &lt;code&gt;txReady&lt;/code&gt; out — the receiver&#39;s two ports beside the transmitter&#39;s two, which is precisely what &lt;code&gt;uart&lt;/code&gt;&#39;s type says. The one wire that never crosses the boundary is the &lt;code&gt;tick&lt;/code&gt;: generated once, shared by both machines, internal to the module. Composition in a single figure — three blocks that each own an idea, joined by a seam that owns none.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;where-the-four-wires-go&quot;&gt;Where the four wires go&lt;/h2&gt;
&lt;p&gt;Inside &lt;code&gt;uart&lt;/code&gt; the story is over and self-contained: the loopback proved
the four wires carry what they claim. The interesting question is where
that four-tuple &lt;em&gt;goes&lt;/em&gt; when the UART stops being read as a closed unit
and starts being used. That happens one level up, in the board shell’s
&lt;code&gt;system&lt;/code&gt;, and the seam is worth seeing even though the shell is a later
post’s subject:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rxByte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; _rxErr&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; txLine&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; txReady&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; uart &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;SNat&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;2_000_000&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; rxLine txByteL&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three things to read here. First, the baud finally gets a &lt;em&gt;number&lt;/em&gt;: &lt;code&gt;SNat @2_000_000&lt;/code&gt;, two megabaud, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator’s&lt;/a&gt; &lt;code&gt;2_000_000&lt;/code&gt;
chosen at the one call site that has to choose it. &lt;code&gt;uart&lt;/code&gt; itself stayed
baud-generic to the end — the &lt;code&gt;SNat baud&lt;/code&gt; in its type — exactly as
decision 3 of the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;design&lt;/a&gt; intended; the shell is where the knob
is finally turned. Everything below &lt;code&gt;uart&lt;/code&gt; inherited its freedom from a
number by never naming one, and the shell names it once, here, and the
whole tree specialises.&lt;/p&gt;
&lt;p&gt;Second, follow the wires into the rig. &lt;code&gt;rxByte&lt;/code&gt; feeds a loader FSM that
fills the instruction BRAM byte by byte — the RX-to-load path.
&lt;code&gt;txReady&lt;/code&gt;, paired with the loader’s own &lt;code&gt;txByteL&lt;/code&gt;, drains trace words
back out — the TX side of the same FSM. &lt;code&gt;txLine&lt;/code&gt; goes to the USB-UART
pin. These are the four terminals the boring diagram in the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;introduction&lt;/a&gt; drew as a single &lt;code&gt;UART&lt;/code&gt; box between the host and
the loader; this is that box’s actual boundary, four wires wide.&lt;/p&gt;
&lt;p&gt;Third, and most honestly: &lt;code&gt;_rxErr&lt;/code&gt;. The framing-error strobe — the
output the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt; worked hardest for, the one with no transmitter
analogue, the flag it raises when a stop bit comes back low — is bound
to &lt;code&gt;_rxErr&lt;/code&gt; and &lt;strong&gt;dropped.&lt;/strong&gt; Today’s shell does not consume it. Section 9
of the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;design&lt;/a&gt; reserved it for “abort or flag a corrupt load,”
and that consumer is not built yet, so the wire is generated, typed,
tested, and — for now — left hanging under the underscore that tells
GHC we meant to ignore it. It is a small, true window into how a design
grows: the receiver produces the honest signal before the shell is ready
to act on it, and the top faithfully carries it to a boundary where it
currently goes nowhere. The wire is ready for its consumer the way the
whole interface is ready for the loader that plugs in next — built to
the right shape, waiting.&lt;/p&gt;
&lt;h2 id=&quot;what-we-read&quot;&gt;What we read&lt;/h2&gt;
&lt;p&gt;The shortest module in the UART, read at last. Five lines — one
exported name, four wires — and no state at all. The type was longer
than the code and told most of the story before the code did: the baud
generator’s numeric obligations &lt;strong&gt;come home&lt;/strong&gt; to the top, because owning
the generator means owning its &lt;code&gt;SNat baud&lt;/code&gt; and its &lt;code&gt;KnownNat&lt;/code&gt;, and the
transmitter’s and receiver’s numberless freedom was bought by
concentrating every number here. The interface is the receiver’s ports
and the transmitter’s ports set side by side, with the one wire that ran
&lt;em&gt;between&lt;/em&gt; them — the tick — gone internal, so the type itself shows
what composition tucks away and what it leaves facing the world.&lt;/p&gt;
&lt;p&gt;The body was a net-list wearing the syntax of equations: not computations
run in order but wires existing at once, three sub-blocks &lt;em&gt;placed&lt;/em&gt; and
connected, a &lt;code&gt;where&lt;/code&gt; that hides names rather than work. Its one decision
was to generate the heartbeat &lt;strong&gt;once&lt;/strong&gt; and fan it into both machines —
decision 2 — which is what makes the loopback a structural certainty
rather than a coincidence of two clocks, one time base counting the one
“sixteen” that is both the bit the transmitter holds and the center the
receiver samples. And its four output wires are the seam: one level up,
&lt;code&gt;rxByte&lt;/code&gt; and &lt;code&gt;txReady&lt;/code&gt; and &lt;code&gt;txLine&lt;/code&gt; join a loader FSM and a pin, while
&lt;code&gt;rxErr&lt;/code&gt; waits, dropped for now, for a consumer the design has promised
but not yet built.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt; told you the UART was behind us, and it was right
about the behaviour — we had watched the byte survive the round trip.
Now it is behind us as &lt;em&gt;text&lt;/em&gt;, too: every file of the transport read,
down to the five-line seam that lets us say “the UART” as a single word.
And those four wires reach, for now, exactly one block — the one
pressed flush against the UART. Not the Engine but the &lt;strong&gt;loader&lt;/strong&gt;: the
FSM that catches &lt;code&gt;rxByte&lt;/code&gt; strobes and writes them into instruction
memory, and feeds bytes back out while &lt;code&gt;txReady&lt;/code&gt; is high, the very four
terminals we traced into &lt;code&gt;system&lt;/code&gt; a moment ago. Most of it we will read
quickly, because it wears the silhouette we have now read four times —
&lt;code&gt;mealy&lt;/code&gt; over a pure &lt;code&gt;loaderStep&lt;/code&gt;, a sum for the phase (&lt;code&gt;RxControl | Run | Drain&lt;/code&gt;, with a finer one for draining) and a record for the rest. What is
&lt;em&gt;new&lt;/em&gt; is the wire it speaks. A UART hands you a bare stream of bytes, and
a bare stream is not yet a &lt;em&gt;message&lt;/em&gt;: where does one frame end and the
next begin? The loader’s answer is &lt;strong&gt;COBS&lt;/strong&gt; — Consistent Overhead Byte
Stuffing — which stuffs every payload so the byte &lt;code&gt;0x00&lt;/code&gt; never appears
inside it, freeing that one value to mean &lt;em&gt;frame boundary&lt;/em&gt;; the loader
decodes the stream on the way in and encodes the drain on the way out. It
is where the byte &lt;em&gt;pipe&lt;/em&gt; we finished today becomes a byte &lt;em&gt;protocol&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Engine&lt;/strong&gt; the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;introduction&lt;/a&gt; promised — the stateful
difficulty spike the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;design&lt;/a&gt; kept deferring — still waits its
turn beyond the loader. But it waits. The heartbeat is generated, the
machines are wired, the bytes cross whole. Next we read the little
machine that frames them: the loader, and its COBS.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-repetitive&quot;&gt;
&lt;p&gt;Yes, the refrain is deliberate. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt; made the
point and every block since has leaned on it, because a claim repeated
across a series is a claim that sticks: in Haskell, and doubly in Clash,
the type signature &lt;strong&gt;is&lt;/strong&gt; half the documentation. Read the type before
the body and the body holds few surprises. I will keep saying it until
it needs no saying. &lt;a href=&quot;#fr-repetitive-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-netlist&quot;&gt;
&lt;p&gt;This is the difference between &lt;em&gt;composition&lt;/em&gt; in software and
in hardware, and &lt;a rel=&quot;external&quot; href=&quot;https://clash-lang.org&quot;&gt;Clash&lt;/a&gt; sits exactly on the seam. In an ordinary Haskell
program, &lt;code&gt;uart baud rxLine txByte&lt;/code&gt; would &lt;em&gt;call&lt;/em&gt; &lt;code&gt;oversampleTick&lt;/code&gt;,
&lt;code&gt;uartRx&lt;/code&gt;, and &lt;code&gt;uartTx&lt;/code&gt; — run them, wait for their results, thread values
through. Clash does not run them; it &lt;strong&gt;elaborates&lt;/strong&gt; them. Each call site
becomes an &lt;em&gt;instance&lt;/em&gt; of that sub-circuit stamped into the net-list, and
each &lt;code&gt;where&lt;/code&gt;-bound name becomes a &lt;em&gt;net&lt;/em&gt; joining the instances — &lt;code&gt;tick&lt;/code&gt; a
single wire with a fan-out of two, driving the enable input of the
receiver instance and the transmitter instance at once. That is why
naming &lt;code&gt;tick&lt;/code&gt; once and using it twice costs one oscillator and not two:
it is one net, not two evaluations. It is also why the binding order is
immaterial — a net-list is a graph, and a graph has no first line. The
top adds no register and no gate that computes; it is a pure structural
node, transparent to behaviour and total to structure, which is the
precise hardware meaning of “this module is only wiring.” &lt;a href=&quot;#fr-netlist-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-splitbaudgen&quot;&gt;
&lt;p&gt;In fairness, there is a version of two generators that
is not mere duplication. Run one at wire speed and one at 16× wire speed
— the slow one for the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt;, the fast one for the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-rx/&quot;&gt;receiver&lt;/a&gt; — and the transmitter genuinely simplifies: handed a
tick already at one-per-bit, it sheds its &lt;code&gt;Index 16&lt;/code&gt; and advances a bit
per tick, leaving oversampling to the receiver, the only half that needs
it. But weigh it. The split spends a second oscillator — another
accumulator, adder, and comparator — to save a four-bit counter: more
logic, not less, though at this scale the difference vanishes into the
noise. So the choice was never cost; it is which story is cleaner, and
&lt;em&gt;one heartbeat, divided where it is needed&lt;/em&gt; reads better than &lt;em&gt;two
heartbeats that must be kept in tune&lt;/em&gt;. Here, reading better and
maintaining better are the same thing. &lt;a href=&quot;#fr-splitbaudgen-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Tamal: The receiver</title>
    <published>2026-07-23T09:00:00+00:00</published>
    <updated>2026-07-23T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-uart-rx/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-uart-rx/</id>
    <summary type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; we read yesterday &lt;em&gt;drove&lt;/em&gt; a wire. It owned the
line: it held each level for sixteen ticks, and it made every output a
pure function of its own state, so the serial output sat rock-steady
between bit boundaries and never once twitched. That was a luxury, and
we spent most of the post admiring it. The receiver is the mirror of
that module — and a mirror, it turns out, is not a reflection so much
as the harder half. &lt;strong&gt;The transmitter drove a wire; the receiver must
read one it does not control.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That single inversion — &lt;em&gt;drive&lt;/em&gt; becomes &lt;em&gt;read&lt;/em&gt; — is where every new
idea in this post comes from, and it is worth saying up front which
parts of the receiver are genuinely new and which are just the
transmitter run backwards. The skeleton is the same: the same
four-constructor &lt;code&gt;RxState&lt;/code&gt;, the same record-of-fields carried tick to
tick, the same &lt;code&gt;Index 16&lt;/code&gt; counting sixteen oversample ticks to a bit,
the same &lt;code&gt;mealy&lt;/code&gt; at the top of the module, the same freeze-off-tick
cadence, the same LSB-first convention. If that were all, this post
would be short and I would send you back to re-read the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt;
with the names changed. It is not all. The interesting parts of the
receiver are precisely the parts that are &lt;em&gt;not&lt;/em&gt; “TX in reverse,” and
there are five of them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The line is an &lt;strong&gt;input&lt;/strong&gt; now, arriving asynchronously from a pin, so
the very first thing the module does is register it twice — a
&lt;strong&gt;2-flop synchronizer&lt;/strong&gt; against metastability, a thing the transmitter
never needed because it drove the wire itself.&lt;/li&gt;
&lt;li&gt;The start bit is not commanded but &lt;strong&gt;hunted for&lt;/strong&gt;, and hunted for by
its &lt;em&gt;falling edge&lt;/em&gt; rather than its mere low level — an edge, so a
line simply held low can never fake a frame.&lt;/li&gt;
&lt;li&gt;The bit is not held but &lt;strong&gt;sampled&lt;/strong&gt;, and sampled carefully: at the
&lt;em&gt;center&lt;/em&gt; of the bit-time, three times, and resolved by a &lt;strong&gt;majority
vote&lt;/strong&gt;. This is the whole point of oversampling sixteen times, and it
is the richest section of the post.&lt;/li&gt;
&lt;li&gt;A stop bit that comes back low is a &lt;strong&gt;framing error&lt;/strong&gt; — the byte is
dropped and a flag is raised. The transmitter had no notion of a frame
going wrong; the receiver must.&lt;/li&gt;
&lt;li&gt;The outputs are &lt;strong&gt;genuinely Mealy&lt;/strong&gt; — one-cycle strobes that read the
&lt;code&gt;tick&lt;/code&gt; input — where the transmitter’s were Moore levels. This is the
sharpest contrast between the two halves, and the one the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; post set us up to notice.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Like the transmitter, the whole receiver fits in a screenful and change.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; we read yesterday &lt;em&gt;drove&lt;/em&gt; a wire. It owned the
line: it held each level for sixteen ticks, and it made every output a
pure function of its own state, so the serial output sat rock-steady
between bit boundaries and never once twitched. That was a luxury, and
we spent most of the post admiring it. The receiver is the mirror of
that module — and a mirror, it turns out, is not a reflection so much
as the harder half. &lt;strong&gt;The transmitter drove a wire; the receiver must
read one it does not control.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That single inversion — &lt;em&gt;drive&lt;/em&gt; becomes &lt;em&gt;read&lt;/em&gt; — is where every new
idea in this post comes from, and it is worth saying up front which
parts of the receiver are genuinely new and which are just the
transmitter run backwards. The skeleton is the same: the same
four-constructor &lt;code&gt;RxState&lt;/code&gt;, the same record-of-fields carried tick to
tick, the same &lt;code&gt;Index 16&lt;/code&gt; counting sixteen oversample ticks to a bit,
the same &lt;code&gt;mealy&lt;/code&gt; at the top of the module, the same freeze-off-tick
cadence, the same LSB-first convention. If that were all, this post
would be short and I would send you back to re-read the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt;
with the names changed. It is not all. The interesting parts of the
receiver are precisely the parts that are &lt;em&gt;not&lt;/em&gt; “TX in reverse,” and
there are five of them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The line is an &lt;strong&gt;input&lt;/strong&gt; now, arriving asynchronously from a pin, so
the very first thing the module does is register it twice — a
&lt;strong&gt;2-flop synchronizer&lt;/strong&gt; against metastability, a thing the transmitter
never needed because it drove the wire itself.&lt;/li&gt;
&lt;li&gt;The start bit is not commanded but &lt;strong&gt;hunted for&lt;/strong&gt;, and hunted for by
its &lt;em&gt;falling edge&lt;/em&gt; rather than its mere low level — an edge, so a
line simply held low can never fake a frame.&lt;/li&gt;
&lt;li&gt;The bit is not held but &lt;strong&gt;sampled&lt;/strong&gt;, and sampled carefully: at the
&lt;em&gt;center&lt;/em&gt; of the bit-time, three times, and resolved by a &lt;strong&gt;majority
vote&lt;/strong&gt;. This is the whole point of oversampling sixteen times, and it
is the richest section of the post.&lt;/li&gt;
&lt;li&gt;A stop bit that comes back low is a &lt;strong&gt;framing error&lt;/strong&gt; — the byte is
dropped and a flag is raised. The transmitter had no notion of a frame
going wrong; the receiver must.&lt;/li&gt;
&lt;li&gt;The outputs are &lt;strong&gt;genuinely Mealy&lt;/strong&gt; — one-cycle strobes that read the
&lt;code&gt;tick&lt;/code&gt; input — where the transmitter’s were Moore levels. This is the
sharpest contrast between the two halves, and the one the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; post set us up to notice.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Like the transmitter, the whole receiver fits in a screenful and change.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;the-entire-source&quot;&gt;The entire source&lt;/h2&gt;
&lt;p&gt;Minus the license header and the doc-comments — but keeping the short
inline comments, because on this module they carry real reasoning —
here is &lt;code&gt;src/Tamal/Uart/Rx.hs&lt;/code&gt; in full:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Uart.Rx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; uartRx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Clash.Prelude&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxIdle&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxStart&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxData&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxStop&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RxS&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RxState&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 16&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxShift&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxS7&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxS8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxS9&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxPrev&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;uartRx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;HiddenClockResetEnable&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;uartRx tick rxLine &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; unbundle &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mealy rxStep initRx &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;bundle &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tick&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; synced&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; 2-flop synchronizer, clocked every cycle (not tick-gated); idle line is high.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  sync1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; register high rxLine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  synced &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; register high sync1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  initRx &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxS&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxIdle&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt; high&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;maj&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;maj a b c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;a &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&amp;amp;.&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; .|.&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;a &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&amp;amp;.&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; .|.&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&amp;amp;.&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;captureSample&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RxS&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RxS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;captureSample s bit&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; rxCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  7&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxS7&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; bit&amp;#39;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxS8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; bit&amp;#39;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  9&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxS9&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; bit&amp;#39;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; s &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; actually unreachable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;decideBit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RxS&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;RxS&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;decideBit s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; rxState s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  RxStart&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; bit&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; low &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxData&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxIdle&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  RxData&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; sh &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          if&lt;/span&gt;&lt;span&gt; bit&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; high&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            then&lt;/span&gt;&lt;span&gt; setBit &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rxShift s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;fromEnum i&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            else&lt;/span&gt;&lt;span&gt; clearBit &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rxShift s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;fromEnum i&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxShift&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; sh&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; maxBound&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          then&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxStop&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          else&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxData&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  RxStop&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; bit&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; high &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxIdle&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;rxShift s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxIdle&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  RxIdle&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; actually unreachable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  bit&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; maj &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rxS7 s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;rxS8 s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;rxS9 s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxStep&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RxS&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Bool&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;RxS&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;rxStep s &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tick&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; line&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; not tick &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; only move on oversample ticks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      let&lt;/span&gt;&lt;span&gt; s0 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxPrev&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; line&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; remember this tick&amp;#39;s level for next tick&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;       in&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; rxState s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;            RxIdle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;              |&lt;/span&gt;&lt;span&gt; rxPrev s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; high &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; line &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; low &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s0&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxStart&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;              |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;            RxStop&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;              --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; Resolve the stop bit at its center sample (count 9) rather than&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;              --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; waiting out the whole window: returning to idle ~6 ticks early&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;              --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; gives the falling-edge detector idle-high ticks to arm rxPrev,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;              --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; so a back-to-back next frame (stop immediately followed by start)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;              --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; still resyncs.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;              let&lt;/span&gt;&lt;span&gt; s1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; captureSample s0 line&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;               in&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; rxCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 9&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                    then&lt;/span&gt;&lt;span&gt; decideBit s1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                    else&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s1&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; rxCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;              --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; RxStart / RxData&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;              let&lt;/span&gt;&lt;span&gt; s1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; captureSample s0 line&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;               in&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; rxCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; maxBound&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                    then&lt;/span&gt;&lt;span&gt; decideBit s1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                    else&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s1&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; rxCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A dozen more lines than the transmitter, and every one of the extra
lines is one of the five new ideas. Top to bottom: the two &lt;code&gt;data&lt;/code&gt;
declarations that are the machine’s memory — the transmitter’s two
types, mirrored, with four new fields bolted on; the six-line &lt;code&gt;uartRx&lt;/code&gt;,
which is the transmitter’s &lt;code&gt;mealy&lt;/code&gt; lift with a synchronizer soldered in
front of it; a tiny &lt;code&gt;maj&lt;/code&gt; that is the majority vote; a &lt;code&gt;captureSample&lt;/code&gt;
that stashes the three center samples; a &lt;code&gt;decideBit&lt;/code&gt; that turns those
samples into a byte or an error; and &lt;code&gt;rxStep&lt;/code&gt;, the transition, which is
where the falling-edge hunt and the early stop resolution live. We will
read them in roughly that order, spending almost all of our words on the
five things the transmitter never had to do.&lt;/p&gt;
&lt;p&gt;The module header we can wave past. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud
generator&lt;/a&gt;, and &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; posts each dwelt on the same
opening beat — the single-name export list sealing everything private
behind it, and the &lt;code&gt;import Clash.Prelude&lt;/code&gt; swap that trades ordinary
Haskell’s furniture for &lt;code&gt;Signal&lt;/code&gt;, &lt;code&gt;Bit&lt;/code&gt;, &lt;code&gt;register&lt;/code&gt;, and &lt;code&gt;mealy&lt;/code&gt; — and a
fourth reading would teach nothing. The one detail worth a glance is an
&lt;em&gt;absence&lt;/em&gt;: there is no &lt;code&gt;{-# LANGUAGE NumericUnderscores #-}&lt;/code&gt;, because the
receiver names no clock frequency and converts no baud rate the way the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator&lt;/a&gt; did. Its idea is not &lt;em&gt;timing&lt;/em&gt; but &lt;em&gt;recovery&lt;/em&gt; —
given a wire it did not schedule, find the bits.&lt;/p&gt;
&lt;h2 id=&quot;the-type-a-line-in-a-byte-out&quot;&gt;The type: a line in, a byte out&lt;/h2&gt;
&lt;p&gt;The signature is the transmitter’s, read in a mirror:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;uartRx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;HiddenClockResetEnable&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Set it beside &lt;code&gt;uartTx&lt;/code&gt; and the symmetry is almost too neat. The
transmitter took a tick and a &lt;code&gt;Maybe (BitVector 8)&lt;/code&gt; and returned a &lt;code&gt;Bit&lt;/code&gt;
and a &lt;code&gt;Bool&lt;/code&gt;; the receiver takes a tick and a &lt;code&gt;Bit&lt;/code&gt; and returns a &lt;code&gt;Maybe (BitVector 8)&lt;/code&gt; and a &lt;code&gt;Bool&lt;/code&gt;. The byte and the wire have swapped ends —
same lone &lt;code&gt;HiddenClockResetEnable&lt;/code&gt; constraint, same numberless type. The
&lt;code&gt;Signal dom Bool&lt;/code&gt; tick is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator’s&lt;/a&gt; enable plugged
in exactly as it was for the transmitter, the receiver being the &lt;em&gt;other&lt;/em&gt;
block that enable was built to gate. The rest of the signature is where
the mirror starts to bend.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Signal dom Bit&lt;/code&gt; is the RX line — and this is the first genuinely new
thing in the type. For the transmitter, &lt;code&gt;Bit&lt;/code&gt; was an &lt;em&gt;output&lt;/em&gt;, a wire it
drove. Here &lt;code&gt;Bit&lt;/code&gt; is an &lt;em&gt;input&lt;/em&gt;, a wire it reads, and the comment on the
matching argument in the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;design doc&lt;/a&gt; is the whole story in
four words: &lt;em&gt;asynchronous; synchronized internally&lt;/em&gt;. This wire arrives
from a physical pin, from a host whose clock has no relationship to
Tamal’s. Its edges fall wherever they fall. The receiver cannot assume
it is stable at the clock edge, cannot assume it will not change while a
flip-flop is trying to latch it, and so the very first thing &lt;code&gt;uartRx&lt;/code&gt;
does — before the state machine ever sees the line — is register it
twice. We will get to that in a moment; the type is already warning us.&lt;/p&gt;
&lt;p&gt;The result, &lt;code&gt;(Signal dom (Maybe (BitVector 8)), Signal dom Bool)&lt;/code&gt;, is a
pair of output wires that are the mirror of the transmitter’s:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Signal dom (Maybe (BitVector 8))&lt;/code&gt; is the received byte, and the
&lt;code&gt;Maybe&lt;/code&gt; is doing the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt;’s job again. &lt;code&gt;Just b&lt;/code&gt; on a cycle means
“a whole frame just landed and here is its byte”; &lt;code&gt;Nothing&lt;/code&gt;, which is
the value on the overwhelming majority of cycles, means “nothing
completed this cycle.” It is a one-cycle &lt;strong&gt;strobe&lt;/strong&gt;, an event, not a
held register — and that word, &lt;em&gt;strobe&lt;/em&gt;, is going to matter enormously
when we ask whether these outputs are Moore or Mealy.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Signal dom Bool&lt;/code&gt; is the framing-error flag, likewise a one-cycle
strobe, raised on the single cycle a frame ends without a valid stop
bit. Where the transmitter’s second output was &lt;code&gt;ready&lt;/code&gt; — a &lt;em&gt;level&lt;/em&gt;,
high for as long as the machine idled — the receiver’s is an
&lt;em&gt;error&lt;/em&gt; — a pulse, high for exactly one cycle when a frame goes bad.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the interface reads: &lt;em&gt;here is a heartbeat and a wire I do not
control; take the bytes I recover from it and a flag for the frames that
came apart.&lt;/em&gt; Two strobes out, no back-pressure, no &lt;code&gt;ready&lt;/code&gt; — because a
receiver cannot ask the host to slow down anyway. The bytes arrive when
they arrive; the strobe simply announces them.&lt;/p&gt;
&lt;h2 id=&quot;two-types-mirrored-and-four-new-fields&quot;&gt;Two types, mirrored — and four new fields&lt;/h2&gt;
&lt;p&gt;Everything the receiver remembers lives in two &lt;code&gt;data&lt;/code&gt; declarations, and
the first of them is the transmitter’s phase type with the tags renamed:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxIdle&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxStart&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxData&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxStop&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read against &lt;code&gt;TxState&lt;/code&gt; it is identical in shape — the same four
constructors, the same eleven inhabitants once &lt;code&gt;RxData (Index 8)&lt;/code&gt; fans
into eight. What differs is only what each phase &lt;em&gt;does&lt;/em&gt;: where &lt;code&gt;TxStart&lt;/code&gt;
drove the line low, &lt;code&gt;RxStart&lt;/code&gt; &lt;em&gt;confirms&lt;/em&gt; a start bit it thinks it saw;
where &lt;code&gt;TxData i&lt;/code&gt; drove bit &lt;code&gt;i&lt;/code&gt; out, &lt;code&gt;RxData i&lt;/code&gt; shifts bit &lt;code&gt;i&lt;/code&gt; in; where
&lt;code&gt;TxStop&lt;/code&gt; drove a high stop bit, &lt;code&gt;RxStop&lt;/code&gt; &lt;em&gt;checks&lt;/em&gt; whether the stop bit
really came back high. Same skeleton, opposite verbs — drive versus
read, the inversion the whole post turns on. The &lt;code&gt;deriving&lt;/code&gt; block is the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt;’s incantation unchanged, read in full in the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt;
post.&lt;/p&gt;
&lt;p&gt;The second type is where the receiver stops being a mirror. The
transmitter’s record had three fields; the receiver’s has seven:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RxS&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RxState&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 16&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxShift&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxS7&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxS8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxS9&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxPrev&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first three are &lt;code&gt;TxS&lt;/code&gt; again, one of them reordered. &lt;code&gt;rxState&lt;/code&gt; is the
phase we just met. &lt;code&gt;rxCnt :: Index 16&lt;/code&gt; is the position within the current
bit, counting the sixteen oversample ticks — &lt;code&gt;0..15&lt;/code&gt;, never 16, the
width load-bearing exactly as it was for &lt;code&gt;txCnt&lt;/code&gt;. &lt;code&gt;rxShift :: BitVector 8&lt;/code&gt; is the byte, though here it is &lt;em&gt;assembled&lt;/em&gt; rather than &lt;em&gt;emitted&lt;/em&gt; —
bits are written into it as they arrive, not shifted out of it. Three
fields, and if the receiver were merely “TX in reverse” that would be the
end of the record.&lt;/p&gt;
&lt;p&gt;It is not the end. Four more fields carry the ideas the transmitter
never needed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;rxS7&lt;/code&gt;, &lt;code&gt;rxS8&lt;/code&gt;, &lt;code&gt;rxS9&lt;/code&gt;&lt;/strong&gt;, three lone &lt;code&gt;Bit&lt;/code&gt;s, are the three samples of
the line taken at oversample counts 7, 8, and 9 — the three readings
around the center of the bit that the majority vote will resolve into a
decision. The transmitter &lt;em&gt;drove&lt;/em&gt; a level and knew what it was; the
receiver must &lt;em&gt;read&lt;/em&gt; a level it does not know, and reads it three times
to be sure. These three flip-flops are the physical memory of “I looked
at the wire near the middle of this bit and here is what I saw, three
times.” Nothing in &lt;code&gt;TxS&lt;/code&gt; corresponds to them, because nothing in the
transmitter ever had to look.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;rxPrev&lt;/code&gt;&lt;/strong&gt;, one more &lt;code&gt;Bit&lt;/code&gt;, is the synchronized line as it stood at
the &lt;em&gt;previous&lt;/em&gt; oversample tick. It exists for one purpose: so that the
idle state can trigger on a high-to-low &lt;em&gt;edge&lt;/em&gt; rather than a low
&lt;em&gt;level&lt;/em&gt;. To notice an edge you must remember where you were, and
&lt;code&gt;rxPrev&lt;/code&gt; is that memory — last tick’s line, held so this tick’s line
can be compared against it. Again, the transmitter had no analogue: it
chose when the start bit began, so it never had to detect the moment
one arrived.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add it up and the receiver’s memory is a phase, a four-bit counter, a
byte, three sampled bits, and one remembered bit — some twenty-odd bits
of flip-flop against the transmitter’s fifteen. Every extra bit buys one
of the jobs that reading-a-wire-you-do-not-own demands. The initial
value wires them all to their resting state:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;initRx &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxS&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxIdle&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt; high&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read positionally against the record: &lt;code&gt;rxState = RxIdle&lt;/code&gt;, &lt;code&gt;rxCnt = 0&lt;/code&gt;,
&lt;code&gt;rxShift = 0&lt;/code&gt;, &lt;code&gt;rxS7 = rxS8 = rxS9 = 0&lt;/code&gt;, and — the one non-zero —
&lt;code&gt;rxPrev = high&lt;/code&gt;. That last is not an accident. The idle line sits high,
so at power-up the receiver must &lt;em&gt;believe&lt;/em&gt; the line was high a tick ago;
otherwise its very first observation of a genuine high line would look
like no change, or worse, a spurious edge. Priming &lt;code&gt;rxPrev&lt;/code&gt; to &lt;code&gt;high&lt;/code&gt; is
the software equivalent of assuming the wire was idle before we started
watching — which, on a UART, it was.&lt;/p&gt;
&lt;figure class=&quot;rxfsm-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;rxfsm&quot; viewBox=&quot;0 0 760 288&quot; role=&quot;img&quot; aria-labelledby=&quot;rxfsm-t rxfsm-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;rxfsm-t&quot;&gt;The receiver&#39;s RxState finite-state machine&lt;/title&gt;
&lt;desc id=&quot;rxfsm-d&quot;&gt;Four state nodes in a row: RxIdle (await edge), RxStart (confirm start), RxData i (assemble bit i), and RxStop (test stop). RxIdle has a dashed self-loop labelled no edge, meaning it stays idle while the line does not fall. An accented arrow leaves RxIdle for RxStart labelled high-to-low edge, the falling-edge trigger. A back arrow from RxStart to RxIdle is labelled vote high implies false start, the glitch rejection. RxStart advances to RxData after count 15 when the vote is low; RxData has a self-loop that votes and writes bit i with setBit while i is less than seven; after bit seven it advances to RxStop; RxStop returns to RxIdle early at count 9, and the received-byte and framing-error strobes fire on that stop resolution.&lt;/desc&gt;
&lt;style&gt;
.rxfsm{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.rxfsm .st{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.rxfsm .wire{stroke:var(--fg-main);stroke-width:2;fill:none}
.rxfsm .hs{stroke:var(--accent);stroke-width:2.5;fill:none}
.rxfsm .loop{stroke:var(--fg-main);stroke-width:2;fill:none}
.rxfsm .idleloop{stroke:var(--fg-dim);stroke-width:2;fill:none;stroke-dasharray:4 4}
.rxfsm text{font-family:var(--sans)}
.rxfsm .name{fill:var(--fg-main);font-family:var(--mono);font-size:15px}
.rxfsm .lab{fill:var(--fg-main);font-size:12px}
.rxfsm .labA{fill:var(--accent);font-size:12px}
.rxfsm .dim{fill:var(--fg-dim);font-size:11.5px}
.rxfsm .ah{fill:var(--fg-main)}
.rxfsm .aha{fill:var(--accent)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;rxfsm-a&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;rxfsm-aa&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;aha&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;rect class=&quot;st&quot; x=&quot;40&quot; y=&quot;118&quot; width=&quot;96&quot; height=&quot;54&quot; rx=&quot;8&quot;/&gt;
&lt;rect class=&quot;st&quot; x=&quot;236&quot; y=&quot;118&quot; width=&quot;96&quot; height=&quot;54&quot; rx=&quot;8&quot;/&gt;
&lt;rect class=&quot;st&quot; x=&quot;432&quot; y=&quot;118&quot; width=&quot;96&quot; height=&quot;54&quot; rx=&quot;8&quot;/&gt;
&lt;rect class=&quot;st&quot; x=&quot;628&quot; y=&quot;118&quot; width=&quot;96&quot; height=&quot;54&quot; rx=&quot;8&quot;/&gt;
&lt;path class=&quot;idleloop&quot; d=&quot;M72,118 C69,92 103,92 100,118&quot; marker-end=&quot;url(#rxfsm-a)&quot;/&gt;
&lt;path class=&quot;loop&quot; d=&quot;M464,118 C460,90 496,90 492,118&quot; marker-end=&quot;url(#rxfsm-a)&quot;/&gt;
&lt;line class=&quot;hs&quot; x1=&quot;136&quot; y1=&quot;145&quot; x2=&quot;234&quot; y2=&quot;145&quot; marker-end=&quot;url(#rxfsm-aa)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;332&quot; y1=&quot;145&quot; x2=&quot;430&quot; y2=&quot;145&quot; marker-end=&quot;url(#rxfsm-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;528&quot; y1=&quot;145&quot; x2=&quot;626&quot; y2=&quot;145&quot; marker-end=&quot;url(#rxfsm-a)&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M252,118 L252,76 L120,76 L120,116&quot; marker-end=&quot;url(#rxfsm-a)&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M676,172 L676,232 L88,232 L88,174&quot; marker-end=&quot;url(#rxfsm-a)&quot;/&gt;
&lt;text class=&quot;name&quot; x=&quot;88&quot; y=&quot;141&quot; text-anchor=&quot;middle&quot;&gt;RxIdle&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;284&quot; y=&quot;141&quot; text-anchor=&quot;middle&quot;&gt;RxStart&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;480&quot; y=&quot;141&quot; text-anchor=&quot;middle&quot;&gt;RxData i&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;676&quot; y=&quot;141&quot; text-anchor=&quot;middle&quot;&gt;RxStop&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;88&quot; y=&quot;160&quot; text-anchor=&quot;middle&quot;&gt;await edge&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;284&quot; y=&quot;160&quot; text-anchor=&quot;middle&quot;&gt;confirm start&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;480&quot; y=&quot;160&quot; text-anchor=&quot;middle&quot;&gt;assemble bit i&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;676&quot; y=&quot;160&quot; text-anchor=&quot;middle&quot;&gt;test stop&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;86&quot; y=&quot;86&quot; text-anchor=&quot;middle&quot;&gt;no edge&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;186&quot; y=&quot;70&quot; text-anchor=&quot;middle&quot;&gt;vote high ⇒ false start&lt;/text&gt;
&lt;text class=&quot;labA&quot; x=&quot;185&quot; y=&quot;138&quot; text-anchor=&quot;middle&quot;&gt;high→low edge&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;185&quot; y=&quot;163&quot; text-anchor=&quot;middle&quot;&gt;not a level&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;480&quot; y=&quot;82&quot; text-anchor=&quot;middle&quot;&gt;vote → setBit i, i&amp;lt;7&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;380&quot; y=&quot;138&quot; text-anchor=&quot;middle&quot;&gt;count 15, vote low&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;577&quot; y=&quot;138&quot; text-anchor=&quot;middle&quot;&gt;bit 7 done&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;382&quot; y=&quot;250&quot; text-anchor=&quot;middle&quot;&gt;count 9 — resolve early, back to idle&lt;/text&gt;
&lt;text class=&quot;labA&quot; x=&quot;382&quot; y=&quot;268&quot; text-anchor=&quot;middle&quot;&gt;byte / framing-error strobe fires here&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The receiver&#39;s eleven states as an FSM — the &lt;a href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&#39;s&lt;/a&gt; diagram read in a mirror, with two genuinely new edges. The accented transition out of &lt;code&gt;RxIdle&lt;/code&gt; fires on a high→low &lt;em&gt;edge&lt;/em&gt; (tracked through &lt;code&gt;rxPrev&lt;/code&gt;), not a low level, so a line simply held low cannot fake a frame. The back-edge &lt;code&gt;RxStart → RxIdle&lt;/code&gt; is glitch rejection: if the confirming vote comes back high, the &quot;start&quot; was noise and we abandon it. &lt;code&gt;RxData i&lt;/code&gt;&#39;s self-loop votes the three center samples and writes bit &lt;code&gt;i&lt;/code&gt; with &lt;code&gt;setBit&lt;/code&gt;/&lt;code&gt;clearBit&lt;/code&gt;. All transitions are tick-gated (they advance only on an oversample tick); &lt;code&gt;RxStart&lt;/code&gt; and &lt;code&gt;RxData&lt;/code&gt; resolve at the end of the 16-tick window (count 15), but &lt;code&gt;RxStop&lt;/code&gt; resolves &lt;em&gt;early&lt;/em&gt;, at its center sample (count 9), and it is there that the byte and framing-error strobes fire.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;the-same-silhouette&quot;&gt;The same silhouette&lt;/h2&gt;
&lt;p&gt;Here is the line that lifts the whole thing into hardware, and it is the
transmitter’s line wearing a hat:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;uartRx tick rxLine &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; unbundle &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mealy rxStep initRx &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;bundle &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tick&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; synced&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  sync1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; register high rxLine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  synced &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; register high sync1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  initRx &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxS&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxIdle&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt; high&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Strip the &lt;code&gt;where&lt;/code&gt; for a second and the outer shape is &lt;em&gt;exactly&lt;/em&gt;
&lt;code&gt;uartTx&lt;/code&gt;’s: &lt;code&gt;unbundle (mealy rxStep initRx (bundle (tick, ...)))&lt;/code&gt;, the
register-plus-pure-function machine the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; post read at
length — &lt;code&gt;mealy&lt;/code&gt; clocking a pure step, &lt;code&gt;bundle&lt;/code&gt;/&lt;code&gt;unbundle&lt;/code&gt; zipping the
several signals into the one the combinator wants and back. The drill is
unchanged, so I will not run it again: find the &lt;code&gt;mealy&lt;/code&gt;, find the step
function beside it, and &lt;code&gt;rxStep&lt;/code&gt; is the brain.&lt;/p&gt;
&lt;p&gt;What &lt;em&gt;is&lt;/em&gt; new is one substitution, and it is the whole reason this
section exists. Look at what gets bundled:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mealy rxStep initRx &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;bundle &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tick&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; synced&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not &lt;code&gt;bundle (tick, rxLine)&lt;/code&gt; — &lt;code&gt;bundle (tick, synced)&lt;/code&gt;. The state
machine is not fed the raw line at all. It is fed &lt;code&gt;synced&lt;/code&gt;, a signal that
does not appear in the argument list, defined in the &lt;code&gt;where&lt;/code&gt; block by two
&lt;code&gt;register&lt;/code&gt;s standing between the pin and the logic. That little
substitution — &lt;code&gt;rxLine&lt;/code&gt; in the type, &lt;code&gt;synced&lt;/code&gt; into the machine — is
the 2-flop synchronizer, and it is the first genuinely new idea in the
receiver. It deserves its own section.&lt;/p&gt;
&lt;h2 id=&quot;the-line-is-now-an-input-the-2-flop-synchronizer&quot;&gt;The line is now an input: the 2-flop synchronizer&lt;/h2&gt;
&lt;p&gt;Two lines of the &lt;code&gt;where&lt;/code&gt; block are the entire mechanism:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  sync1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; register high rxLine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  synced &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; register high sync1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;rxLine&lt;/code&gt; — the asynchronous wire from the pin — goes into a &lt;code&gt;register&lt;/code&gt;,
whose output &lt;code&gt;sync1&lt;/code&gt; goes into a second &lt;code&gt;register&lt;/code&gt;, whose output &lt;code&gt;synced&lt;/code&gt;
is what the state machine reads. Two flip-flops in series, both
initialised &lt;code&gt;high&lt;/code&gt;, and the line is delayed by two clock cycles before
any logic touches it. That is the whole thing. It is also one of the most
important two lines in the codebase, because it is the guard against
&lt;strong&gt;metastability&lt;/strong&gt;, and metastability is the failure mode that a
transmitter, driving its own wire, is simply never exposed to.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-metastability-1&quot;&gt;&lt;a href=&quot;#fn-metastability&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Here is the problem it solves. A flip-flop is a promise: sample the input
at the clock edge, hold it steady until the next edge. But the promise
has fine print — the input must itself be steady for a small window
around the clock edge, a &lt;em&gt;setup&lt;/em&gt; time before and a &lt;em&gt;hold&lt;/em&gt; time after.
Meet that window and the flip-flop resolves cleanly to &lt;code&gt;0&lt;/code&gt; or &lt;code&gt;1&lt;/code&gt;. Miss
it — change the input &lt;em&gt;while&lt;/em&gt; the edge is happening — and the flip-flop
can enter a &lt;strong&gt;metastable&lt;/strong&gt; state: its output hovers at neither level, a
voltage stuck halfway, for an unbounded and unpredictable time before it
eventually, randomly, falls to &lt;code&gt;0&lt;/code&gt; or &lt;code&gt;1&lt;/code&gt;. A transmitter never risks this
because it changes its line in step with its own clock; the setup and
hold windows are honoured by construction. A receiver has no such luck.
The host’s line changes when the &lt;em&gt;host’s&lt;/em&gt; clock says so, which bears no
relationship to Tamal’s clock, so sooner or later a bit edge will land
exactly on a Tamal clock edge and the first flip-flop that samples it
will go metastable. There is no avoiding that first strike. The line is
asynchronous; asynchronous means “will, eventually, violate setup/hold”;
and no amount of cleverness changes it.&lt;/p&gt;
&lt;p&gt;What you &lt;em&gt;can&lt;/em&gt; do is contain the damage, and that is what the second
flip-flop is for. When &lt;code&gt;sync1&lt;/code&gt; goes metastable, you give it one whole
clock cycle — the time between this edge and the next — to settle,
untouched by any logic, before &lt;code&gt;synced&lt;/code&gt; samples it. A clock period is an
eternity next to the time a flip-flop stays balanced on the knife-edge;
the probability that &lt;code&gt;sync1&lt;/code&gt; is &lt;em&gt;still&lt;/em&gt; undecided a full cycle later is
vanishingly small, and &lt;code&gt;synced&lt;/code&gt; almost certainly captures a clean &lt;code&gt;0&lt;/code&gt; or
&lt;code&gt;1&lt;/code&gt;. “Almost certainly” is the honest phrase — two flops do not make
metastability impossible, they push its mean-time-between-failures out to
centuries, which for a hobby FPGA link is the same as impossible. The
cost is two flip-flops and two cycles of latency, and the receiver
happily pays it: two cycles is nothing against a bit that is fifty clocks
wide.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-metastability-2&quot;&gt;&lt;a href=&quot;#fn-metastability&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Two details reward a second look. The first is that these two &lt;code&gt;register&lt;/code&gt;s
are clocked on &lt;strong&gt;every single cycle&lt;/strong&gt; — they are &lt;em&gt;not&lt;/em&gt; tick-gated. This
is the sharpest possible contrast with everything downstream. The state
machine, &lt;code&gt;rxStep&lt;/code&gt;, freezes between oversample ticks; it is enabled by the
tick and does nothing on the roughly two cycles in three when the tick is
low. The synchronizer must do the opposite. A metastable event can happen
on &lt;em&gt;any&lt;/em&gt; clock edge, tick or no tick, because the host’s line does not
know or care where Tamal’s oversample cadence sits. So the synchronizer
runs free, catching and settling the line continuously, and hands the
already-cleaned &lt;code&gt;synced&lt;/code&gt; to the tick-gated machine. Free-running guard,
gated brain: the two live at different rhythms on purpose.&lt;/p&gt;
&lt;p&gt;The second is the initial value, &lt;code&gt;register high&lt;/code&gt; and not &lt;code&gt;register 0&lt;/code&gt;.
The idle UART line sits high, so the synchronizer powers up &lt;em&gt;believing
the line is idle&lt;/em&gt;. Were it to power up low, the machine’s freshly-primed
&lt;code&gt;rxPrev = high&lt;/code&gt; would meet a &lt;code&gt;synced = low&lt;/code&gt; on the very first tick and
read a phantom falling edge — a start bit that never happened, out of a
line that was never driven. Seeding both the synchronizer and &lt;code&gt;rxPrev&lt;/code&gt; to
&lt;code&gt;high&lt;/code&gt; makes the receiver’s first belief about the world match the
world’s actual resting state: quiet, high, waiting.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-metastability-3&quot;&gt;&lt;a href=&quot;#fn-metastability&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;This synchronizer is decision 6 in the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;UART design&lt;/a&gt;, and its
rationale is placement: the line is asynchronous because it comes from a
pin, so the synchronizer belongs &lt;em&gt;with the receiver&lt;/em&gt;, inside &lt;code&gt;uartRx&lt;/code&gt;,
rather than bolted on by whatever shell wires the pin. The receiver is
self-contained and metastability-safe on its own terms; a caller need
only hand it the raw pad. It is the one piece of the receiver with no
transmitter analogue whatsoever — the transmitter drove the wire, and
you do not synchronize a wire you are driving.&lt;/p&gt;
&lt;figure class=&quot;rxsy-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;rxsy&quot; viewBox=&quot;0 0 760 300&quot; role=&quot;img&quot; aria-labelledby=&quot;rxsy-t rxsy-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;rxsy-t&quot;&gt;The 2-flop synchronizer feeding the tick-gated FSM&lt;/title&gt;
&lt;desc id=&quot;rxsy-d&quot;&gt;The asynchronous rxLine crosses a dashed boundary into the Dom100 clock domain and enters the first synchronizer flip-flop sync1, whose output feeds the second flip-flop synced. Both flip-flops are clocked every cycle with no enable, marked by clock notches; a brace beneath them reads two-flop synchronizer, clocked every cycle. The synced line then enters a dashed mealy rxStep box, the state machine, which additionally takes the tick input from below as an enable that gates it. The machine emits two outputs on the right, a byte strobe and an error strobe. The accent highlights the async crossing and the tick enable, and a note contrasts the always-clocked flops with the tick-gated FSM.&lt;/desc&gt;
&lt;style&gt;
.rxsy{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.rxsy .box{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.rxsy .mbox{fill:none;stroke:var(--fg-dim);stroke-width:1.5;stroke-dasharray:6 5}
.rxsy .wire{stroke:var(--fg-main);stroke-width:2;fill:none}
.rxsy .in{stroke:var(--accent);stroke-width:2.5;fill:none}
.rxsy .cross{stroke:var(--accent);stroke-width:1.5;fill:none;stroke-dasharray:5 4}
.rxsy .brace{stroke:var(--fg-dim);stroke-width:1.5;fill:none}
.rxsy .clk{fill:none;stroke:var(--fg-main);stroke-width:1.5}
.rxsy text{font-family:var(--sans)}
.rxsy .name{fill:var(--fg-main);font-family:var(--mono);font-size:13px}
.rxsy .lab{fill:var(--fg-main);font-size:12px}
.rxsy .dim{fill:var(--fg-dim);font-size:11.5px}
.rxsy .sig{fill:var(--fg-dim);font-family:var(--mono);font-size:12px}
.rxsy .sigA{fill:var(--accent);font-family:var(--mono);font-size:12px}
.rxsy .note{fill:var(--accent);font-size:11.5px}
.rxsy .ah{fill:var(--fg-main)}
.rxsy .aha{fill:var(--accent)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;rxsy-a&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;rxsy-aa&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;aha&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;line class=&quot;cross&quot; x1=&quot;98&quot; y1=&quot;70&quot; x2=&quot;98&quot; y2=&quot;250&quot;/&gt;
&lt;text class=&quot;note&quot; x=&quot;98&quot; y=&quot;62&quot; text-anchor=&quot;middle&quot;&gt;async → Dom100&lt;/text&gt;
&lt;text class=&quot;sigA&quot; x=&quot;30&quot; y=&quot;151&quot; text-anchor=&quot;start&quot;&gt;rxLine&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;30&quot; y=&quot;168&quot; text-anchor=&quot;start&quot;&gt;async · pin&lt;/text&gt;
&lt;line class=&quot;in&quot; x1=&quot;72&quot; y1=&quot;155&quot; x2=&quot;126&quot; y2=&quot;155&quot; marker-end=&quot;url(#rxsy-aa)&quot;/&gt;
&lt;rect class=&quot;box&quot; x=&quot;128&quot; y=&quot;131&quot; width=&quot;84&quot; height=&quot;48&quot; rx=&quot;6&quot;/&gt;
&lt;path class=&quot;clk&quot; d=&quot;M128,166 L138,172 L128,178&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;212&quot; y1=&quot;155&quot; x2=&quot;246&quot; y2=&quot;155&quot; marker-end=&quot;url(#rxsy-a)&quot;/&gt;
&lt;rect class=&quot;box&quot; x=&quot;248&quot; y=&quot;131&quot; width=&quot;84&quot; height=&quot;48&quot; rx=&quot;6&quot;/&gt;
&lt;path class=&quot;clk&quot; d=&quot;M248,166 L258,172 L248,178&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;332&quot; y1=&quot;155&quot; x2=&quot;430&quot; y2=&quot;155&quot; marker-end=&quot;url(#rxsy-a)&quot;/&gt;
&lt;path class=&quot;brace&quot; d=&quot;M128,190 V198 H332 V190&quot;/&gt;
&lt;text class=&quot;lab&quot; x=&quot;230&quot; y=&quot;214&quot; text-anchor=&quot;middle&quot;&gt;2-flop synchronizer&lt;/text&gt;
&lt;text class=&quot;note&quot; x=&quot;230&quot; y=&quot;230&quot; text-anchor=&quot;middle&quot;&gt;clocked EVERY cycle — no enable&lt;/text&gt;
&lt;rect class=&quot;mbox&quot; x=&quot;432&quot; y=&quot;108&quot; width=&quot;286&quot; height=&quot;96&quot; rx=&quot;10&quot;/&gt;
&lt;text class=&quot;sig&quot; x=&quot;575&quot; y=&quot;100&quot; text-anchor=&quot;middle&quot;&gt;mealy rxStep initRx&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;575&quot; y=&quot;140&quot; text-anchor=&quot;middle&quot;&gt;register RxS + rxStep&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;575&quot; y=&quot;159&quot; text-anchor=&quot;middle&quot;&gt;the TX silhouette,&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;575&quot; y=&quot;174&quot; text-anchor=&quot;middle&quot;&gt;but freezes when ¬tick&lt;/text&gt;
&lt;line class=&quot;in&quot; x1=&quot;120&quot; y1=&quot;272&quot; x2=&quot;575&quot; y2=&quot;272&quot; marker-end=&quot;url(#rxsy-aa)&quot;/&gt;
&lt;path class=&quot;in&quot; d=&quot;M575,272 V206&quot; marker-end=&quot;url(#rxsy-aa)&quot;/&gt;
&lt;text class=&quot;sigA&quot; x=&quot;30&quot; y=&quot;269&quot; text-anchor=&quot;start&quot;&gt;tick&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;30&quot; y=&quot;285&quot; text-anchor=&quot;start&quot;&gt;enable&lt;/text&gt;
&lt;text class=&quot;note&quot; x=&quot;360&quot; y=&quot;266&quot; text-anchor=&quot;middle&quot;&gt;gates the FSM (never the sync flops)&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;380&quot; y=&quot;148&quot; text-anchor=&quot;middle&quot;&gt;synced&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;180&quot; y=&quot;152&quot; text-anchor=&quot;middle&quot;&gt;sync1&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;290&quot; y=&quot;152&quot; text-anchor=&quot;middle&quot;&gt;synced&lt;/text&gt;
&lt;line class=&quot;wire&quot; x1=&quot;718&quot; y1=&quot;140&quot; x2=&quot;748&quot; y2=&quot;140&quot; marker-end=&quot;url(#rxsy-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;718&quot; y1=&quot;172&quot; x2=&quot;748&quot; y2=&quot;172&quot; marker-end=&quot;url(#rxsy-a)&quot;/&gt;
&lt;text class=&quot;sig&quot; x=&quot;752&quot; y=&quot;144&quot; text-anchor=&quot;start&quot;&gt;byte&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;752&quot; y=&quot;176&quot; text-anchor=&quot;start&quot;&gt;error&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;Where the receiver differs from the transmitter before a single bit is decoded. The asynchronous &lt;code&gt;rxLine&lt;/code&gt; crosses out of no clock domain and into &lt;code&gt;Dom100&lt;/code&gt; (accent), and is caught by two back-to-back flip-flops — &lt;code&gt;sync1&lt;/code&gt; then &lt;code&gt;synced&lt;/code&gt; — that are clocked on &lt;em&gt;every&lt;/em&gt; cycle, with no enable, so they can settle a metastable strike whenever it lands. Only the already-cleaned &lt;code&gt;synced&lt;/code&gt; reaches the &lt;code&gt;mealy rxStep&lt;/code&gt; machine, which wears the &lt;a href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&#39;s&lt;/a&gt; silhouette but is &lt;em&gt;tick-gated&lt;/em&gt;: the &lt;code&gt;tick&lt;/code&gt; enable (accent) freezes it between oversample ticks. Free-running guard, gated brain — the two run at different rhythms on purpose.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;finding-the-start-bit-an-edge-not-a-level&quot;&gt;Finding the start bit: an edge, not a level&lt;/h2&gt;
&lt;p&gt;Now we can read &lt;code&gt;rxStep&lt;/code&gt;, and the natural place to start is where the
frame starts — the idle arm, where the receiver is watching a quiet
line and waiting for a byte to begin. Here is that arm, lifted out of the
&lt;code&gt;case&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;            RxIdle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;              |&lt;/span&gt;&lt;span&gt; rxPrev s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; high &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; line &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; low &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s0&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxStart&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;              |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The transmitter’s idle arm looked at &lt;code&gt;mbyte&lt;/code&gt; and, on a &lt;code&gt;Just b&lt;/code&gt;, latched
the byte and left for &lt;code&gt;TxStart&lt;/code&gt;. It &lt;em&gt;decided&lt;/em&gt; when the frame began,
because it was the one sending it. The receiver cannot decide; it can
only &lt;em&gt;notice&lt;/em&gt;. And what it notices is not that the line is low but that
the line has just &lt;em&gt;gone&lt;/em&gt; low — the guard is &lt;code&gt;rxPrev s == high &amp;amp;&amp;amp; line == low&lt;/code&gt;, which is true on exactly the tick where the previous sample was
high and this one is low. A high-to-low transition. The &lt;strong&gt;falling edge&lt;/strong&gt;
that opens every UART frame, because the line idles high and the start
bit is the first thing to pull it down.&lt;/p&gt;
&lt;p&gt;Why insist on the edge? Why not the far simpler &lt;code&gt;line == low&lt;/code&gt; — if the
line is low, a start bit must be here, so go? Because “the line is low”
is true for reasons that are not the start of a frame, and a receiver
that triggered on the level would start decoding garbage out of all of
them. Consider a &lt;strong&gt;break condition&lt;/strong&gt;: a host holding the line low for a
long stretch, deliberately, as an out-of-band signal. A level-triggered
receiver would see &lt;code&gt;low&lt;/code&gt;, leap into &lt;code&gt;RxStart&lt;/code&gt;, decode a frame of zeros,
return to idle, see &lt;code&gt;low&lt;/code&gt; &lt;em&gt;again&lt;/em&gt; — still the same break — and leap
right back in, manufacturing frame after frame out of one long silence.
Or consider the ordinary end of a normal frame that happens to carry a
low most-significant bit: the line is low right up until the stop bit
lifts it. A level trigger has no way to tell “the line is low because a
new frame is starting” from “the line is low because it never went back
up.” An edge trigger does, and trivially: a new frame requires a &lt;em&gt;fresh&lt;/em&gt;
high-to-low transition, and a line already held low offers none. There is
no new edge, so there is no new frame. Break conditions, stuck lines, and
lingering low bits all sail past.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-edge-1&quot;&gt;&lt;a href=&quot;#fn-edge&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;This is exactly what &lt;code&gt;rxPrev&lt;/code&gt; was carried for. To notice a transition you
must remember the previous value, and &lt;code&gt;rxPrev&lt;/code&gt; is that memory — the
synchronized line as it stood at the &lt;em&gt;previous tick&lt;/em&gt;, not the previous
cycle. (It updates only on ticks, because off a tick the whole &lt;code&gt;rxStep&lt;/code&gt;
returns &lt;code&gt;s&lt;/code&gt; untouched, &lt;code&gt;rxPrev&lt;/code&gt; included.) On every idle tick, &lt;code&gt;s0 = s{rxPrev = line}&lt;/code&gt; refreshes it, so the comparison always pits last tick’s
level against this tick’s. When they differ in the falling direction, the
frame is on, and the machine moves to &lt;code&gt;RxStart&lt;/code&gt; with &lt;code&gt;rxCnt&lt;/code&gt; reset to
zero to begin timing the start bit. On the non-edge case it returns
&lt;code&gt;s0&lt;/code&gt; — staying idle, but with &lt;code&gt;rxPrev&lt;/code&gt; freshly updated, always primed to
catch the next fall.&lt;/p&gt;
&lt;p&gt;There is one honest imprecision to name, because the design names it too.
Start detection is checked &lt;em&gt;once per tick&lt;/em&gt;, not continuously. The true
falling edge on the wire can occur anywhere within a tick interval, so
the tick that first observes &lt;code&gt;line == low&lt;/code&gt; can lag the real edge by up to
one full tick — up to one sixteenth of a bit. The receiver’s whole sense
of “where am I in this bit” is therefore up to a sixteenth of a bit late
from the very start. This sounds alarming and is completely harmless, and
the reason it is harmless is the subject of the next section: the
receiver does not sample at the &lt;em&gt;edge&lt;/em&gt; of a bit, where a sixteenth’s slip
would matter, but at its &lt;em&gt;center&lt;/em&gt;, where there is a full eight ticks of
margin on either side to absorb the slip and then some. The lag is real;
the center swallows it.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-edge-2&quot;&gt;&lt;a href=&quot;#fn-edge&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;figure class=&quot;rxedge-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;rxedge&quot; viewBox=&quot;0 0 760 240&quot; role=&quot;img&quot; aria-labelledby=&quot;rxedge-t rxedge-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;rxedge-t&quot;&gt;Falling-edge start detection versus a held-low line&lt;/title&gt;
&lt;desc id=&quot;rxedge-d&quot;&gt;A tick-by-tick strip of the synchronized line. It is high for ticks 0 to 3, falls low at tick 4, stays low through tick 10, and rises again at tick 11. The falling transition at tick 4 is highlighted in accent and labelled START, because there rxPrev was high and line is low. Ticks 5 through 10, where the line is held low, are bracketed and labelled as carrying no new high-to-low edge, so no phantom re-trigger occurs. The point is that the receiver fires on the edge, not the level.&lt;/desc&gt;
&lt;style&gt;
.rxedge{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.rxedge .line{stroke:var(--fg-main);stroke-width:2.5;fill:none}
.rxedge .guide{stroke:var(--fg-dim);stroke-width:1;fill:none;stroke-dasharray:3 4}
.rxedge .fire{stroke:var(--accent);stroke-width:2.5;fill:none;stroke-dasharray:5 4}
.rxedge .brace{stroke:var(--fg-dim);stroke-width:1.5;fill:none}
.rxedge .dot{fill:var(--fg-main)}
.rxedge .dotA{fill:var(--accent)}
.rxedge text{font-family:var(--sans)}
.rxedge .lab{fill:var(--fg-main);font-size:12px}
.rxedge .dim{fill:var(--fg-dim);font-size:11px}
.rxedge .acc{fill:var(--accent);font-size:12px}
.rxedge .sig{fill:var(--fg-dim);font-family:var(--mono);font-size:11px}
&lt;/style&gt;
&lt;line class=&quot;guide&quot; x1=&quot;78&quot; y1=&quot;78&quot; x2=&quot;690&quot; y2=&quot;78&quot;/&gt;
&lt;line class=&quot;guide&quot; x1=&quot;78&quot; y1=&quot;128&quot; x2=&quot;690&quot; y2=&quot;128&quot;/&gt;
&lt;text class=&quot;sig&quot; x=&quot;70&quot; y=&quot;82&quot; text-anchor=&quot;end&quot;&gt;high&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;70&quot; y=&quot;132&quot; text-anchor=&quot;end&quot;&gt;low&lt;/text&gt;
&lt;line class=&quot;fire&quot; x1=&quot;282&quot; y1=&quot;58&quot; x2=&quot;282&quot; y2=&quot;142&quot;/&gt;
&lt;text class=&quot;acc&quot; x=&quot;282&quot; y=&quot;48&quot; text-anchor=&quot;middle&quot;&gt;high→low edge → START&lt;/text&gt;
&lt;polyline class=&quot;line&quot; points=&quot;78,78 258,78 258,128 594,128 594,78 690,78&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;90&quot; cy=&quot;78&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;138&quot; cy=&quot;78&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;186&quot; cy=&quot;78&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;234&quot; cy=&quot;78&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dotA&quot; cx=&quot;282&quot; cy=&quot;128&quot; r=&quot;4.5&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;330&quot; cy=&quot;128&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;378&quot; cy=&quot;128&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;426&quot; cy=&quot;128&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;474&quot; cy=&quot;128&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;522&quot; cy=&quot;128&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;570&quot; cy=&quot;128&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;618&quot; cy=&quot;78&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;666&quot; cy=&quot;78&quot; r=&quot;3&quot;/&gt;
&lt;text class=&quot;sig&quot; x=&quot;90&quot; y=&quot;158&quot; text-anchor=&quot;middle&quot;&gt;0&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;138&quot; y=&quot;158&quot; text-anchor=&quot;middle&quot;&gt;1&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;186&quot; y=&quot;158&quot; text-anchor=&quot;middle&quot;&gt;2&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;234&quot; y=&quot;158&quot; text-anchor=&quot;middle&quot;&gt;3&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;282&quot; y=&quot;158&quot; text-anchor=&quot;middle&quot;&gt;4&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;330&quot; y=&quot;158&quot; text-anchor=&quot;middle&quot;&gt;5&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;378&quot; y=&quot;158&quot; text-anchor=&quot;middle&quot;&gt;6&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;426&quot; y=&quot;158&quot; text-anchor=&quot;middle&quot;&gt;7&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;474&quot; y=&quot;158&quot; text-anchor=&quot;middle&quot;&gt;8&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;522&quot; y=&quot;158&quot; text-anchor=&quot;middle&quot;&gt;9&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;570&quot; y=&quot;158&quot; text-anchor=&quot;middle&quot;&gt;10&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;618&quot; y=&quot;158&quot; text-anchor=&quot;middle&quot;&gt;11&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;666&quot; y=&quot;158&quot; text-anchor=&quot;middle&quot;&gt;12&lt;/text&gt;
&lt;path class=&quot;brace&quot; d=&quot;M330,176 V184 H570 V176&quot;/&gt;
&lt;text class=&quot;lab&quot; x=&quot;450&quot; y=&quot;202&quot; text-anchor=&quot;middle&quot;&gt;line held low — no new high→low edge&lt;/text&gt;
&lt;text class=&quot;acc&quot; x=&quot;450&quot; y=&quot;220&quot; text-anchor=&quot;middle&quot;&gt;so no phantom re-trigger (a break, or a low bit, is ignored)&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;Why the trigger is an &lt;em&gt;edge&lt;/em&gt; and not a &lt;em&gt;level&lt;/em&gt;. The receiver fires &lt;code&gt;RxStart&lt;/code&gt; only on the tick where &lt;code&gt;rxPrev&lt;/code&gt; was high and the synchronized &lt;code&gt;line&lt;/code&gt; is low — the single accented falling transition at tick 4. Through ticks 5–10 the line stays low, but there is no &lt;em&gt;new&lt;/em&gt; high→low edge, so nothing re-triggers: a break condition, or a frame ending on a low bit, cannot manufacture a phantom start the way a bare &lt;code&gt;line == low&lt;/code&gt; test would. Detection is checked once per tick, so it can lag the true edge by under one tick — harmless, because the receiver samples each bit at its center, not here at its edge.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;zoom-into-one-bit-the-center-and-the-vote&quot;&gt;Zoom into one bit: the center and the vote&lt;/h2&gt;
&lt;p&gt;This is the heart of the receiver, the section the whole post has been
walking toward, and it answers a question the transmitter never had to
ask: &lt;em&gt;given a bit that is sixteen oversample ticks wide, where in those
sixteen ticks do you actually look, and how many times?&lt;/em&gt; The
transmitter’s answer was trivial — it held the level for all sixteen and
never looked at all. The receiver’s answer is the one interesting design
decision in the module, and it is worth deriving slowly.&lt;/p&gt;
&lt;p&gt;Start with the window. Within any bit the receiver counts &lt;code&gt;rxCnt :: Index 16&lt;/code&gt;, running &lt;code&gt;0..15&lt;/code&gt;, one step per oversample tick — sixteen
positions tiling one bit-time. The transmitter used the same counter to
&lt;em&gt;hold&lt;/em&gt; a level for sixteen ticks; the receiver uses it to &lt;em&gt;locate&lt;/em&gt; itself
within a bit it is reading. And of those sixteen positions, the receiver
reads the line at exactly three of them — counts 7, 8, and 9 — stashing
each reading into its own field:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;captureSample&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RxS&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RxS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;captureSample s bit&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; rxCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  7&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxS7&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; bit&amp;#39;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxS8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; bit&amp;#39;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  9&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxS9&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; bit&amp;#39;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; s &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; actually unreachable&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At count 7 the current line goes into &lt;code&gt;rxS7&lt;/code&gt;, at 8 into &lt;code&gt;rxS8&lt;/code&gt;, at 9 into
&lt;code&gt;rxS9&lt;/code&gt;, and at every other count — the twelve counts we are not sampling
— the function returns &lt;code&gt;s&lt;/code&gt; unchanged, a deliberate no-op. Three
flip-flops filled once each per bit, at three adjacent positions near the
middle of the window. Everything interesting about the receiver’s
robustness is in the choice of &lt;em&gt;those three positions&lt;/em&gt;, so let us take
the choice apart in three questions.&lt;/p&gt;
&lt;h3 id=&quot;why-the-center-at-all&quot;&gt;Why the center at all?&lt;/h3&gt;
&lt;p&gt;Because the center of the bit is the one place on the waveform that is
furthest from trouble, and the trouble is at the edges. A bit is bounded
by two &lt;em&gt;transitions&lt;/em&gt; — the moment the line moves from the previous bit’s
level to this one’s, and the moment it moves on to the next. Right at
those transitions the line is in flight: slewing between levels, ringing,
not yet settled, and — because of everything in the last two sections —
possibly a fraction of a tick misaligned from where the receiver thinks
the bit boundary is. Sample there and you are reading a value that is
changing. Sample at the center, count 8 of 16, and you are as far from
both transitions as it is possible to be — eight ticks of clear air on
each side — where the line has long since settled to its true level and
will sit there untouched until the next boundary.&lt;/p&gt;
&lt;p&gt;And here the two halves of the UART clasp hands. Recall the single most
important property of the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt;: its &lt;code&gt;line&lt;/code&gt; was a &lt;strong&gt;Moore&lt;/strong&gt;
output, a pure function of state, &lt;em&gt;dead-steady for the entire sixteen-tick
bit&lt;/em&gt; because nothing but a state change could move it and the state
changed only at boundaries. The transmitter went to the trouble of making
its wire rock-solid across the whole cell precisely so that a receiver
sampling the middle would land on solid ground. The receiver is the other
end of that bargain: it samples the center &lt;em&gt;because&lt;/em&gt; the transmitter
guaranteed the center is flat. Center sampling and Moore outputs are one
design decision made twice, once at each end. The center is also where
the receiver’s own small errors go to be forgiven: the sub-tick
start-detection lag from the last section, and any slow baud-rate
mismatch that accumulates across a frame, both push the sample point
away from the true center — but they have to push it a full eight ticks
before it reaches an edge, and neither is remotely that large over one
frame. The center is a generous target.&lt;/p&gt;
&lt;h3 id=&quot;why-three-samples-and-not-one&quot;&gt;Why three samples and not one?&lt;/h3&gt;
&lt;p&gt;A single center sample is the classic minimal UART, and it works right up
until it doesn’t. One sample means zero noise immunity: a single glitch
on the line at the one instant you look — a coupled spike, a runt from
imperfect signal integrity, one unlucky sample of a marginal level — and
the bit is simply wrong, with nothing to catch it. You looked once, you
saw the wrong thing, you believed it.&lt;/p&gt;
&lt;p&gt;Three samples, resolved by a majority vote, tolerate exactly one such
corruption. If two of the three readings agree — and in a clean signal
all three agree, since they sit within two ticks of each other on a flat
level — then a single glitched sample is outvoted two-to-one and the bit
comes out right anyway. This is, in the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;design doc’s&lt;/a&gt; blunt
phrase, &lt;em&gt;the actual point of 16× oversampling&lt;/em&gt;. You do not oversample
sixteen times in order to sample sixteen times; you oversample so that you
can afford to take several readings clustered at the center and let them
check each other. Majority-of-three is the smallest arrangement that buys
any noise immunity at all, and it turns “one bad sample ruins the byte”
into “one bad sample is shrugged off.” The &lt;a rel=&quot;external&quot; href=&quot;https://hedgehog.qa&quot;&gt;tests&lt;/a&gt; prove exactly
this, as we will see: they flip a single center sample and demand the byte
still decode.&lt;/p&gt;
&lt;h3 id=&quot;why-7-8-and-9-why-not-others-why-not-more&quot;&gt;Why 7, 8, and 9 — why not others, why not more?&lt;/h3&gt;
&lt;p&gt;This is the precise question, and it has a precise answer with four parts.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Odd, so the vote cannot tie.&lt;/em&gt; A majority vote needs an odd number of
voters or it can deadlock — two against two decides nothing. Three is
odd; the vote &lt;code&gt;maj&lt;/code&gt; always has a strict winner. Four samples would
reintroduce the possibility of a tie and force some tie-break rule, which
is complexity bought for nothing.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Three, because it is the smallest odd number greater than one.&lt;/em&gt; One
sample gives no immunity, as we just saw. Three is the next odd count up,
and it already tolerates a single fault — the common case, a lone
glitch. Five would tolerate two simultaneous faults, but two independent
glitches landing inside the same three-tick-wide center window of the
same bit is a scenario a 2 Mbaud hobby link never sees, and you would pay
for the guard against it in every bit forever. Three is the sweet spot:
the cheapest count that does anything.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-vote-1&quot;&gt;&lt;a href=&quot;#fn-vote&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Tight around the center, so drift can never push a sample onto an edge.&lt;/em&gt;
The three samples sit at 7, 8, 9 — adjacent, packed into the middle three
ticks of the sixteen. This matters. Suppose instead you spread the three
readings out — say counts 4, 8, 12 — reasoning that a wider spread
somehow samples “more” of the bit. It does the opposite of what you want:
counts 4 and 12 are only four ticks from their respective transitions, so
the moment the receiver’s alignment drifts by even a few ticks — start
lag plus a little baud mismatch — one of those outer samples wanders onto
a bit edge and reads a value in flight, corrupting the very vote that was
supposed to protect you. Keeping all three within two ticks of the center
keeps all three deep in the flat region under every realistic drift. The
spread is not a feature; it is a liability. Tight is correct.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Cheap, because it is three flip-flops and a three-term boolean.&lt;/em&gt; The
whole apparatus is &lt;code&gt;rxS7&lt;/code&gt;, &lt;code&gt;rxS8&lt;/code&gt;, &lt;code&gt;rxS9&lt;/code&gt; — three single-bit registers —
and the &lt;code&gt;maj&lt;/code&gt; function, which is two ANDs-of-two ORed… three ANDs and
an OR, a handful of gates. More samples would mean more capture registers
and a wider voting function for a robustness gain the link cannot use.
The design spends exactly what buys the one property that matters and not
a gate more.&lt;/p&gt;
&lt;p&gt;One honest note on symmetry, because it is easy to overclaim. In a
&lt;code&gt;0..15&lt;/code&gt; window the exact geometric center is 7.5 — there is no integer
count &lt;em&gt;at&lt;/em&gt; the center, because sixteen is even. The design nominates count
&lt;strong&gt;8&lt;/strong&gt; as the working “center” and brackets it with its two neighbours, 7
and 9. So the three samples are symmetric about count 8, but sit very
slightly forward of the true midpoint 7.5: count 7 is half a tick before
it, count 8 half a tick after, count 9 a tick and a half after. Half a
tick of asymmetry in a window with eight ticks of margin to either edge is
nothing — but it is there, and it is more accurate to say “count 8 and
its neighbours” than to pretend the three straddle 7.5 evenly. They do
not; they cluster just past it.&lt;/p&gt;
&lt;p&gt;That leaves the &lt;code&gt;_ -&amp;gt; s&lt;/code&gt; arm of &lt;code&gt;captureSample&lt;/code&gt; — the no-op at all twelve
non-sampled counts — and its comment, &lt;code&gt;-- actually unreachable&lt;/code&gt;. It is
reached constantly in the ordinary sense: on every tick that is not a 7,
8, or 9, &lt;code&gt;captureSample&lt;/code&gt; runs and does nothing. The “unreachable” is
subtler and is the same totality point the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; post made
about &lt;code&gt;TxIdle -&amp;gt; s&lt;/code&gt;: the &lt;code&gt;case&lt;/code&gt; must cover every &lt;code&gt;Index 16&lt;/code&gt; value or GHC’s
exhaustiveness warning fires, so the catch-all is there to satisfy the
compiler that the function is total. Whether any &lt;em&gt;particular&lt;/em&gt; count ever
flows through it is a runtime matter the type cannot see; the wildcard
makes the function honest for all sixteen regardless.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-unreachable-1&quot;&gt;&lt;a href=&quot;#fn-unreachable&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;figure class=&quot;rxbit-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;rxbit&quot; viewBox=&quot;0 0 760 344&quot; role=&quot;img&quot; aria-labelledby=&quot;rxbit-t rxbit-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;rxbit-t&quot;&gt;Zoom into one bit: the sixteen oversample ticks and the three center samples&lt;/title&gt;
&lt;desc id=&quot;rxbit-d&quot;&gt;A single bit spans sixteen oversample ticks, counts 0 through 15, marked as dots along a settled flat level between two edge transitions at the far left and far right. The exact geometric center is 7.5; the design nominates count 8 as the center. An accent band highlights counts 7, 8 and 9, whose dots are enlarged and labelled s7, s8 and s9 — the three samples captured near the center. Arrows show that these three sit about eight ticks from each edge, the maximum margin, where the Moore-driven line is flat and settled. A small accent arrow at the left edge notes that the sub-tick start-detection lag is absorbed here. Below, the three samples s7, s8, s9 feed a majority box that outputs the decided bit.&lt;/desc&gt;
&lt;style&gt;
.rxbit{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.rxbit .cell{fill:var(--fg-main);stroke:none}
.rxbit .band{fill:var(--accent);opacity:0.13}
.rxbit .edge{stroke:var(--fg-dim);stroke-width:1.5;fill:none;stroke-dasharray:4 4}
.rxbit .ctr{stroke:var(--fg-dim);stroke-width:1.5;fill:none;stroke-dasharray:2 3}
.rxbit .line{stroke:var(--fg-main);stroke-width:2.5;fill:none}
.rxbit .marg{stroke:var(--fg-dim);stroke-width:1.5;fill:none}
.rxbit .lag{stroke:var(--accent);stroke-width:2;fill:none}
.rxbit .box{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.rxbit .fan{stroke:var(--accent);stroke-width:2;fill:none}
.rxbit .out{stroke:var(--fg-main);stroke-width:2;fill:none}
.rxbit .dot{fill:var(--fg-dim)}
.rxbit .dotA{fill:var(--accent)}
.rxbit text{font-family:var(--sans)}
.rxbit .num{fill:var(--fg-dim);font-family:var(--mono);font-size:10.5px}
.rxbit .samp{fill:var(--accent);font-family:var(--mono);font-size:12px}
.rxbit .lab{fill:var(--fg-main);font-size:12px}
.rxbit .dim{fill:var(--fg-dim);font-size:11px}
.rxbit .acc{fill:var(--accent);font-size:11.5px}
.rxbit .ah{fill:var(--fg-dim)}
.rxbit .aha{fill:var(--accent)}
.rxbit .af{fill:var(--fg-main)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;rxbit-a&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;rxbit-aa&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;aha&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;rxbit-af&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;af&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;rect class=&quot;band&quot; x=&quot;345&quot; y=&quot;70&quot; width=&quot;105&quot; height=&quot;74&quot;/&gt;
&lt;line class=&quot;edge&quot; x1=&quot;100&quot; y1=&quot;70&quot; x2=&quot;100&quot; y2=&quot;150&quot;/&gt;
&lt;line class=&quot;edge&quot; x1=&quot;660&quot; y1=&quot;70&quot; x2=&quot;660&quot; y2=&quot;150&quot;/&gt;
&lt;line class=&quot;ctr&quot; x1=&quot;380&quot; y1=&quot;64&quot; x2=&quot;380&quot; y2=&quot;107&quot;/&gt;
&lt;text class=&quot;dim&quot; x=&quot;380&quot; y=&quot;58&quot; text-anchor=&quot;middle&quot;&gt;7.5&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;100&quot; y=&quot;62&quot; text-anchor=&quot;middle&quot;&gt;edge&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;660&quot; y=&quot;62&quot; text-anchor=&quot;middle&quot;&gt;edge&lt;/text&gt;
&lt;line class=&quot;line&quot; x1=&quot;100&quot; y1=&quot;107&quot; x2=&quot;660&quot; y2=&quot;107&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;117.5&quot; cy=&quot;107&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;152.5&quot; cy=&quot;107&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;187.5&quot; cy=&quot;107&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;222.5&quot; cy=&quot;107&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;257.5&quot; cy=&quot;107&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;292.5&quot; cy=&quot;107&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;327.5&quot; cy=&quot;107&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dotA&quot; cx=&quot;362.5&quot; cy=&quot;107&quot; r=&quot;5&quot;/&gt;
&lt;circle class=&quot;dotA&quot; cx=&quot;397.5&quot; cy=&quot;107&quot; r=&quot;5&quot;/&gt;
&lt;circle class=&quot;dotA&quot; cx=&quot;432.5&quot; cy=&quot;107&quot; r=&quot;5&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;467.5&quot; cy=&quot;107&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;502.5&quot; cy=&quot;107&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;537.5&quot; cy=&quot;107&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;572.5&quot; cy=&quot;107&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;607.5&quot; cy=&quot;107&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;642.5&quot; cy=&quot;107&quot; r=&quot;3&quot;/&gt;
&lt;text class=&quot;samp&quot; x=&quot;362.5&quot; y=&quot;90&quot; text-anchor=&quot;middle&quot;&gt;s7&lt;/text&gt;
&lt;text class=&quot;samp&quot; x=&quot;397.5&quot; y=&quot;90&quot; text-anchor=&quot;middle&quot;&gt;s8&lt;/text&gt;
&lt;text class=&quot;samp&quot; x=&quot;432.5&quot; y=&quot;90&quot; text-anchor=&quot;middle&quot;&gt;s9&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;117.5&quot; y=&quot;125&quot; text-anchor=&quot;middle&quot;&gt;0&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;152.5&quot; y=&quot;125&quot; text-anchor=&quot;middle&quot;&gt;1&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;187.5&quot; y=&quot;125&quot; text-anchor=&quot;middle&quot;&gt;2&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;222.5&quot; y=&quot;125&quot; text-anchor=&quot;middle&quot;&gt;3&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;257.5&quot; y=&quot;125&quot; text-anchor=&quot;middle&quot;&gt;4&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;292.5&quot; y=&quot;125&quot; text-anchor=&quot;middle&quot;&gt;5&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;327.5&quot; y=&quot;125&quot; text-anchor=&quot;middle&quot;&gt;6&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;362.5&quot; y=&quot;125&quot; text-anchor=&quot;middle&quot;&gt;7&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;397.5&quot; y=&quot;125&quot; text-anchor=&quot;middle&quot;&gt;8&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;432.5&quot; y=&quot;125&quot; text-anchor=&quot;middle&quot;&gt;9&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;467.5&quot; y=&quot;125&quot; text-anchor=&quot;middle&quot;&gt;10&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;502.5&quot; y=&quot;125&quot; text-anchor=&quot;middle&quot;&gt;11&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;537.5&quot; y=&quot;125&quot; text-anchor=&quot;middle&quot;&gt;12&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;572.5&quot; y=&quot;125&quot; text-anchor=&quot;middle&quot;&gt;13&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;607.5&quot; y=&quot;125&quot; text-anchor=&quot;middle&quot;&gt;14&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;642.5&quot; y=&quot;125&quot; text-anchor=&quot;middle&quot;&gt;15&lt;/text&gt;
&lt;line class=&quot;marg&quot; x1=&quot;343&quot; y1=&quot;162&quot; x2=&quot;103&quot; y2=&quot;162&quot; marker-end=&quot;url(#rxbit-a)&quot;/&gt;
&lt;line class=&quot;marg&quot; x1=&quot;452&quot; y1=&quot;162&quot; x2=&quot;657&quot; y2=&quot;162&quot; marker-end=&quot;url(#rxbit-a)&quot;/&gt;
&lt;text class=&quot;dim&quot; x=&quot;223&quot; y=&quot;156&quot; text-anchor=&quot;middle&quot;&gt;≈8 ticks of margin&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;555&quot; y=&quot;156&quot; text-anchor=&quot;middle&quot;&gt;≈8 ticks of margin&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;397&quot; y=&quot;182&quot; text-anchor=&quot;middle&quot;&gt;the Moore-driven line is flat and settled here&lt;/text&gt;
&lt;line class=&quot;lag&quot; x1=&quot;100&quot; y1=&quot;206&quot; x2=&quot;146&quot; y2=&quot;206&quot; marker-end=&quot;url(#rxbit-aa)&quot;/&gt;
&lt;text class=&quot;acc&quot; x=&quot;152&quot; y=&quot;210&quot; text-anchor=&quot;start&quot;&gt;start-detect lag &amp;lt; 1 tick — absorbed by the center&lt;/text&gt;
&lt;circle class=&quot;dotA&quot; cx=&quot;352&quot; cy=&quot;278&quot; r=&quot;5&quot;/&gt;
&lt;circle class=&quot;dotA&quot; cx=&quot;392&quot; cy=&quot;278&quot; r=&quot;5&quot;/&gt;
&lt;circle class=&quot;dotA&quot; cx=&quot;432&quot; cy=&quot;278&quot; r=&quot;5&quot;/&gt;
&lt;text class=&quot;samp&quot; x=&quot;352&quot; y=&quot;266&quot; text-anchor=&quot;middle&quot;&gt;s7&lt;/text&gt;
&lt;text class=&quot;samp&quot; x=&quot;392&quot; y=&quot;266&quot; text-anchor=&quot;middle&quot;&gt;s8&lt;/text&gt;
&lt;text class=&quot;samp&quot; x=&quot;432&quot; y=&quot;266&quot; text-anchor=&quot;middle&quot;&gt;s9&lt;/text&gt;
&lt;line class=&quot;fan&quot; x1=&quot;359&quot; y1=&quot;278&quot; x2=&quot;494&quot; y2=&quot;271&quot; marker-end=&quot;url(#rxbit-aa)&quot;/&gt;
&lt;line class=&quot;fan&quot; x1=&quot;399&quot; y1=&quot;278&quot; x2=&quot;494&quot; y2=&quot;278&quot; marker-end=&quot;url(#rxbit-aa)&quot;/&gt;
&lt;line class=&quot;fan&quot; x1=&quot;439&quot; y1=&quot;278&quot; x2=&quot;494&quot; y2=&quot;285&quot; marker-end=&quot;url(#rxbit-aa)&quot;/&gt;
&lt;rect class=&quot;box&quot; x=&quot;496&quot; y=&quot;258&quot; width=&quot;82&quot; height=&quot;40&quot; rx=&quot;6&quot;/&gt;
&lt;text class=&quot;lab&quot; x=&quot;537&quot; y=&quot;276&quot; text-anchor=&quot;middle&quot; style=&quot;font-family:var(--mono)&quot;&gt;maj&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;537&quot; y=&quot;291&quot; text-anchor=&quot;middle&quot;&gt;≥ 2 of 3&lt;/text&gt;
&lt;line class=&quot;out&quot; x1=&quot;578&quot; y1=&quot;278&quot; x2=&quot;636&quot; y2=&quot;278&quot; marker-end=&quot;url(#rxbit-af)&quot;/&gt;
&lt;text class=&quot;lab&quot; x=&quot;642&quot; y=&quot;282&quot; text-anchor=&quot;start&quot; style=&quot;font-family:var(--mono)&quot;&gt;bit&#39;&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;300&quot; y=&quot;326&quot; text-anchor=&quot;middle&quot;&gt;three center samples → one voted bit&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;One bit, its sixteen oversample ticks, and the three the receiver actually reads. The geometric center is 7.5 (sixteen is even, so no count sits exactly on it); the design nominates count &lt;strong&gt;8&lt;/strong&gt; and brackets it with 7 and 9 — the accent band. Those three samples sit about eight ticks from &lt;em&gt;both&lt;/em&gt; edge transitions, the maximum possible margin, where the transmitter&#39;s Moore-driven line is guaranteed flat, and where the sub-tick start-detection lag is comfortably absorbed. Why these three and no others: &lt;strong&gt;odd&lt;/strong&gt; so the vote cannot tie, &lt;strong&gt;three&lt;/strong&gt; because it is the smallest odd count that tolerates a glitch, &lt;strong&gt;tight around the center&lt;/strong&gt; so drift never pushes a sample onto an edge, and &lt;strong&gt;cheap&lt;/strong&gt; — three flip-flops and a boolean. The three feed &lt;code&gt;maj&lt;/code&gt;, which returns the bit the majority saw.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;the-majority-vote&quot;&gt;The majority vote&lt;/h2&gt;
&lt;p&gt;The vote itself is one line, and it is pure combinational logic — no
clock, no state, the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt;’s world briefly revisited inside the
receiver:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;maj&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;maj a b c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;a &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&amp;amp;.&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; .|.&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;a &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&amp;amp;.&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; .|.&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&amp;amp;.&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read it as gates. &lt;code&gt;.&amp;amp;.&lt;/code&gt; is bitwise AND, &lt;code&gt;.|.&lt;/code&gt; is bitwise OR, and on three
single &lt;code&gt;Bit&lt;/code&gt;s the expression is &lt;code&gt;(a AND b) OR (a AND c) OR (b AND c)&lt;/code&gt; —
high if and only if at least two of the three inputs are high. Every pair
gets its own AND; if &lt;em&gt;any&lt;/em&gt; pair is both-high, the OR fires. It is the
textbook majority function, and it does exactly what the last section
promised: it returns the value that at least two of the three samples
agreed on, so a lone dissenter — a single glitched sample — is
outvoted.&lt;/p&gt;
&lt;p&gt;Trace the one case that matters, a clean bit corrupted by a single
glitch. Say the true bit is high, so in a clean signal &lt;code&gt;s7 = s8 = s9 = 1&lt;/code&gt;, and &lt;code&gt;maj 1 1 1 = (1 AND 1) OR ... = 1&lt;/code&gt; — correct. Now let one
sample be glitched low, say &lt;code&gt;s8&lt;/code&gt;: the inputs are &lt;code&gt;1 0 1&lt;/code&gt;, and &lt;code&gt;maj 1 0 1 = (1 AND 0) OR (1 AND 1) OR (0 AND 1) = 0 OR 1 OR 0 = 1&lt;/code&gt; — still correct.
The pair &lt;code&gt;s7 AND s9&lt;/code&gt; carried the day. The same holds whichever single
sample flips, and whichever way the true bit points: two good samples
always contain a both-agreeing pair, and that pair drives the OR. Only a
&lt;em&gt;second&lt;/em&gt; simultaneous corruption — two of the three flipped — could
swing the vote, and that is precisely the fault the design declined to
guard against because the link never produces it. One glitch in, correct
bit out. That property has a name in the test suite, and we will watch
Hedgehog flip a sample and demand the byte survive.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;decideBit&lt;/code&gt;, the vote is computed once, in a &lt;code&gt;where&lt;/code&gt; clause, and every
arm reads its result:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  bit&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; maj &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rxS7 s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;rxS8 s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;rxS9 s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;bit&#39;&lt;/code&gt; is &lt;em&gt;the&lt;/em&gt; recovered bit for this cell — whatever the majority of
the three center samples saw — and it is what the rest of &lt;code&gt;decideBit&lt;/code&gt;
acts on. Whether the cell was a start bit, a data bit, or a stop bit,
the reading is the same: three samples near the center, one majority
vote, one bit. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;design doc&lt;/a&gt; calls this &lt;em&gt;reusing one
sampling primitive at every bit center&lt;/em&gt;, and it is why the code has a
single &lt;code&gt;maj&lt;/code&gt; and a single trio of sample fields rather than special-case
logic per phase. The vote does not care what kind of bit it is deciding;
it just decides.&lt;/p&gt;
&lt;figure class=&quot;rxmaj-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;rxmaj&quot; viewBox=&quot;0 0 760 300&quot; role=&quot;img&quot; aria-labelledby=&quot;rxmaj-t rxmaj-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;rxmaj-t&quot;&gt;The majority vote as a gate network&lt;/title&gt;
&lt;desc id=&quot;rxmaj-d&quot;&gt;Three inputs s7, s8 and s9 on the left fan out to three two-input AND gates: s7 and s8, s7 and s9, s8 and s9. The three AND outputs feed a single three-input OR gate, whose output is the decided bit. An annotation shows that a clean high bit reads 1,1,1 giving a vote of 1, and that a single glitch flipping one sample to 0,1,1 still votes 1, so the majority survives one corrupted sample.&lt;/desc&gt;
&lt;style&gt;
.rxmaj{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.rxmaj .gate{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.rxmaj .wire{stroke:var(--fg-main);stroke-width:2;fill:none}
.rxmaj .node{fill:var(--fg-main)}
.rxmaj text{font-family:var(--sans)}
.rxmaj .sig{fill:var(--accent);font-family:var(--mono);font-size:13px}
.rxmaj .gl{fill:var(--fg-main);font-family:var(--mono);font-size:11px}
.rxmaj .lab{fill:var(--fg-main);font-size:12px}
.rxmaj .dim{fill:var(--fg-dim);font-size:11px}
.rxmaj .acc{fill:var(--accent);font-size:11.5px}
.rxmaj .ah{fill:var(--fg-main)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;rxmaj-a&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;text class=&quot;sig&quot; x=&quot;46&quot; y=&quot;76&quot; text-anchor=&quot;start&quot;&gt;s7&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;46&quot; y=&quot;156&quot; text-anchor=&quot;start&quot;&gt;s8&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;46&quot; y=&quot;236&quot; text-anchor=&quot;start&quot;&gt;s9&lt;/text&gt;
&lt;circle class=&quot;node&quot; cx=&quot;120&quot; cy=&quot;72&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;node&quot; cx=&quot;150&quot; cy=&quot;152&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;node&quot; cx=&quot;180&quot; cy=&quot;232&quot; r=&quot;3&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M66,72 H240 V62 H300&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M120,72 V150 H300&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M66,152 H220 V84 H300&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M150,152 V236 H300&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M66,232 H255 V172 H300&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M180,232 V258 H300&quot;/&gt;
&lt;path class=&quot;gate&quot; d=&quot;M300,50 L331,50 A23 23 0 0 1 331,96 L300,96 Z&quot;/&gt;
&lt;path class=&quot;gate&quot; d=&quot;M300,137 L331,137 A23 23 0 0 1 331,183 L300,183 Z&quot;/&gt;
&lt;path class=&quot;gate&quot; d=&quot;M300,224 L331,224 A23 23 0 0 1 331,270 L300,270 Z&quot;/&gt;
&lt;text class=&quot;gl&quot; x=&quot;312&quot; y=&quot;77&quot; text-anchor=&quot;middle&quot;&gt;&amp;amp;&lt;/text&gt;
&lt;text class=&quot;gl&quot; x=&quot;312&quot; y=&quot;164&quot; text-anchor=&quot;middle&quot;&gt;&amp;amp;&lt;/text&gt;
&lt;text class=&quot;gl&quot; x=&quot;312&quot; y=&quot;251&quot; text-anchor=&quot;middle&quot;&gt;&amp;amp;&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;355&quot; y=&quot;46&quot; text-anchor=&quot;middle&quot;&gt;s7·s8&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;355&quot; y=&quot;205&quot; text-anchor=&quot;middle&quot;&gt;s7·s9&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;355&quot; y=&quot;292&quot; text-anchor=&quot;middle&quot;&gt;s8·s9&lt;/text&gt;
&lt;path class=&quot;wire&quot; d=&quot;M354,73 H430 V140 H470&quot; marker-end=&quot;url(#rxmaj-a)&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M354,160 H470&quot; marker-end=&quot;url(#rxmaj-a)&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M354,247 H430 V190 H470&quot; marker-end=&quot;url(#rxmaj-a)&quot;/&gt;
&lt;path class=&quot;gate&quot; d=&quot;M470,120 Q502,120 548,165 Q502,210 470,210 Q486,165 470,120 Z&quot;/&gt;
&lt;text class=&quot;gl&quot; x=&quot;498&quot; y=&quot;169&quot; text-anchor=&quot;middle&quot;&gt;≥1&lt;/text&gt;
&lt;line class=&quot;wire&quot; x1=&quot;548&quot; y1=&quot;165&quot; x2=&quot;628&quot; y2=&quot;165&quot; marker-end=&quot;url(#rxmaj-a)&quot;/&gt;
&lt;text class=&quot;sig&quot; x=&quot;634&quot; y=&quot;169&quot; text-anchor=&quot;start&quot;&gt;bit&#39;&lt;/text&gt;
&lt;text class=&quot;acc&quot; x=&quot;512&quot; y=&quot;250&quot; text-anchor=&quot;middle&quot;&gt;clean 1 1 1 → 1&lt;/text&gt;
&lt;text class=&quot;acc&quot; x=&quot;512&quot; y=&quot;268&quot; text-anchor=&quot;middle&quot;&gt;one glitch 0 1 1 → 1 (survives)&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The vote &lt;code&gt;(s7·s8) + (s7·s9) + (s8·s9)&lt;/code&gt; as gates: each pair of samples meets in a two-input AND, and the three ANDs meet in one OR, so the output is high exactly when at least two of the three inputs are. The point is fault tolerance: a clean high bit reads &lt;code&gt;1 1 1&lt;/code&gt; and votes &lt;code&gt;1&lt;/code&gt;; a single glitch flipping one sample to &lt;code&gt;0 1 1&lt;/code&gt; still votes &lt;code&gt;1&lt;/code&gt;, because the surviving pair &lt;code&gt;s8·s9&lt;/code&gt; holds. Only two simultaneous corruptions could swing it — the fault the link never produces. This is the whole return on sampling three times instead of once.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;decidebit-confirm-assemble-test&quot;&gt;&lt;code&gt;decideBit&lt;/code&gt;: confirm, assemble, test&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;decideBit&lt;/code&gt; is called once per bit, at the tick that resolves the
window, with the three samples already in hand. It is where the voted
&lt;code&gt;bit&#39;&lt;/code&gt; becomes a decision, and it reads as a &lt;code&gt;case&lt;/code&gt; over the phase —
one arm each for start, data, and stop, plus the obligatory unreachable
idle:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;decideBit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RxS&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;RxS&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;decideBit s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; rxState s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  RxStart&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; bit&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; low &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxData&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxIdle&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  RxData&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; sh &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          if&lt;/span&gt;&lt;span&gt; bit&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; high&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            then&lt;/span&gt;&lt;span&gt; setBit &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rxShift s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;fromEnum i&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            else&lt;/span&gt;&lt;span&gt; clearBit &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rxShift s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;fromEnum i&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        s&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxShift&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; sh&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     in&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; maxBound&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          then&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxStop&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          else&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxData&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  RxStop&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; bit&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; high &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxIdle&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;rxShift s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxIdle&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  RxIdle&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; actually unreachable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  bit&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; maj &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rxS7 s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;rxS8 s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;rxS9 s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read the three live arms in order; each is one job the transmitter never
had.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;RxStart&lt;/code&gt; confirms or abandons.&lt;/strong&gt; The receiver reached &lt;code&gt;RxStart&lt;/code&gt;
because it saw a falling edge and &lt;em&gt;believed&lt;/em&gt; a start bit had begun. Now,
at the center of that supposed start bit, it checks: is the voted &lt;code&gt;bit&#39;&lt;/code&gt;
still low? If so, the low level held across the center of the bit — a
real start bit, not a momentary dip — and the machine commits to the
frame, moving to &lt;code&gt;RxData 0&lt;/code&gt; to read the first data bit. If instead &lt;code&gt;bit&#39;&lt;/code&gt;
came back high, then the thing that pulled the line down was &lt;em&gt;not&lt;/em&gt; a start
bit at all — a glitch, a runt, a noise spike that tripped the edge
detector — and the machine abandons it, returning to &lt;code&gt;RxIdle&lt;/code&gt; to wait for
a real edge. This is &lt;strong&gt;glitch rejection at the frame level&lt;/strong&gt;, a second
line of defence behind the majority vote: the edge detector is
deliberately twitchy (it must catch every real start), so the start bit
is &lt;em&gt;confirmed&lt;/em&gt; half a bit later before any data is believed. The
transmitter never needed this because it never doubted its own start bit;
the receiver doubts everything until the center agrees.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;RxData i&lt;/code&gt; assembles the byte.&lt;/strong&gt; Here is the one genuinely pretty
inversion of the transmitter, so it is worth slowing down. The
transmitter serialised a byte &lt;em&gt;out&lt;/em&gt; of a shift register: it held the byte
in &lt;code&gt;txShift&lt;/code&gt;, read &lt;code&gt;lsb (txShift s)&lt;/code&gt; onto the wire, and &lt;code&gt;shiftR&lt;/code&gt;-ed the
register at each boundary so the next bit fell into the bottom
position — the byte walked out the bottom, LSB first. The receiver does
the mirror image, assembling a byte &lt;em&gt;in&lt;/em&gt; — but notice it does not shift
at all:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    let&lt;/span&gt;&lt;span&gt; sh &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;          if&lt;/span&gt;&lt;span&gt; bit&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; high&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            then&lt;/span&gt;&lt;span&gt; setBit &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rxShift s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;fromEnum i&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            else&lt;/span&gt;&lt;span&gt; clearBit &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rxShift s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;fromEnum i&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of sliding the register and always writing the bottom, the
receiver writes bit number &lt;code&gt;i&lt;/code&gt; &lt;em&gt;directly&lt;/em&gt; — &lt;code&gt;setBit&lt;/code&gt; to make position &lt;code&gt;i&lt;/code&gt;
high, &lt;code&gt;clearBit&lt;/code&gt; to make it low, with &lt;code&gt;fromEnum i&lt;/code&gt; turning the &lt;code&gt;Index 8&lt;/code&gt;
into the plain &lt;code&gt;Int&lt;/code&gt; position that &lt;code&gt;setBit&lt;/code&gt;/&lt;code&gt;clearBit&lt;/code&gt; want. At &lt;code&gt;RxData 0&lt;/code&gt; it writes bit 0; at &lt;code&gt;RxData 7&lt;/code&gt;, bit 7; so bit &lt;code&gt;i&lt;/code&gt; of the recovered
&lt;code&gt;rxShift&lt;/code&gt; is exactly the &lt;code&gt;i&lt;/code&gt;-th data bit received, LSB first, matching the
transmitter’s convention wire for wire. Where the transmitter &lt;em&gt;moved the
byte past a fixed read point&lt;/em&gt;, the receiver &lt;em&gt;holds the byte still and
addresses it by index&lt;/em&gt;. Both are LSB-first, both walk bit 0 through bit 7,
but one slides and the other indexes — the same byte, the opposite
direction, and the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; shift-register motif
seen a third time, now assembling rather than emitting.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-assemble-1&quot;&gt;&lt;a href=&quot;#fn-assemble&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt; After
writing, if &lt;code&gt;i == maxBound&lt;/code&gt; — bit 7, the last one, the &lt;code&gt;Index 8&lt;/code&gt;
guaranteeing there is no bit 8 — the machine goes to &lt;code&gt;RxStop&lt;/code&gt;; otherwise
it advances to &lt;code&gt;RxData (i + 1)&lt;/code&gt; for the next data bit. Either way &lt;code&gt;rxCnt&lt;/code&gt;
resets to begin a fresh sixteen-tick window.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;RxStop&lt;/code&gt; delivers the verdict.&lt;/strong&gt; At the center of the stop bit the
receiver votes one last time, and this vote decides whether the whole
frame was well-formed:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  RxStop&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; bit&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; high &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxIdle&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Just&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;rxShift s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxIdle&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A UART stop bit is &lt;em&gt;defined&lt;/em&gt; to be high; the line must return to its idle
level to frame the byte. So if &lt;code&gt;bit&#39;&lt;/code&gt; is high, the frame closed cleanly:
the machine emits &lt;code&gt;Just (rxShift s)&lt;/code&gt; — the fully assembled byte, handed
up as the one-cycle strobe — with the error flag &lt;code&gt;False&lt;/code&gt;, and returns to
idle. But if &lt;code&gt;bit&#39;&lt;/code&gt; is low, the stop bit was &lt;em&gt;missing&lt;/em&gt;: the line was still
low where it should have lifted, which means the framing was wrong —
mismatched baud, a line fault, a spurious mid-noise “frame.” The receiver
refuses to hand up a byte it cannot trust. It emits &lt;code&gt;(Nothing, True)&lt;/code&gt;:
&lt;strong&gt;no byte, framing error raised&lt;/strong&gt;. The assembled &lt;code&gt;rxShift&lt;/code&gt; is simply
dropped, never delivered. This is the framing-error path, the third
output the transmitter had no counterpart for, and it too has made a
test. Both stop arms return to &lt;code&gt;RxIdle&lt;/code&gt;, because whether the frame was
good or bad, it is over.&lt;/p&gt;
&lt;p&gt;That leaves &lt;code&gt;RxIdle -&amp;gt; (s, (Nothing, False))&lt;/code&gt;, the fourth arm, tagged
&lt;code&gt;-- actually unreachable&lt;/code&gt;. As in &lt;code&gt;captureSample&lt;/code&gt;, and as in the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter’s&lt;/a&gt; &lt;code&gt;txAdvance&lt;/code&gt;, it exists to make the &lt;code&gt;case&lt;/code&gt; total: GHC
insists every &lt;code&gt;RxState&lt;/code&gt; constructor be handled, so the idle arm is
written even though &lt;code&gt;decideBit&lt;/code&gt; is only ever &lt;em&gt;called&lt;/em&gt; from a resolving
tick, and idle never resolves. Returning &lt;code&gt;s&lt;/code&gt; unchanged with no output is
the honest do-nothing that keeps the function total and the compiler
quiet.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-unreachable-2&quot;&gt;&lt;a href=&quot;#fn-unreachable&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-outputs-are-genuinely-mealy&quot;&gt;The outputs are genuinely Mealy&lt;/h2&gt;
&lt;p&gt;Now for the sharpest contrast with the transmitter, and a direct callback
to the longest argument in that post. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; spent pages
establishing that although it was lifted with the general &lt;code&gt;mealy&lt;/code&gt;
combinator, its outputs were pure &lt;strong&gt;Moore&lt;/strong&gt; — &lt;code&gt;line&lt;/code&gt; and &lt;code&gt;ready&lt;/code&gt; were
functions of the state alone, no input in sight, so the wire sat
dead-steady between boundaries. We drew a whole figure of it: the Moore
machine whose output reads only the state, next to the Mealy machine with
an accent tap carrying the input up into the output. The transmitter, we
concluded, had no such tap. Its handshake bought a same-cycle
&lt;em&gt;transition&lt;/em&gt;, but never a same-cycle &lt;em&gt;output&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The receiver has the tap. Its outputs are &lt;strong&gt;genuinely Mealy&lt;/strong&gt;, and you
can see it in the first line of &lt;code&gt;rxStep&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;rxStep s &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tick&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; line&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; not tick &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; only move on oversample ticks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read what that guard says about the &lt;em&gt;output&lt;/em&gt;, not the state. On any cycle
where &lt;code&gt;tick&lt;/code&gt; is low — most cycles — &lt;code&gt;rxStep&lt;/code&gt; returns the output
&lt;code&gt;(Nothing, False)&lt;/code&gt; regardless of what state &lt;code&gt;s&lt;/code&gt; holds. On a cycle where
&lt;code&gt;tick&lt;/code&gt; is high and the state happens to be a resolving &lt;code&gt;RxStop&lt;/code&gt;, the same
machine can return &lt;code&gt;(Just byte, False)&lt;/code&gt;. So take a single fixed state —
say &lt;code&gt;s&lt;/code&gt; with &lt;code&gt;rxState = RxStop&lt;/code&gt; and &lt;code&gt;rxCnt = 9&lt;/code&gt; and a winning vote — and
ask what the output is. On a non-tick cycle it is &lt;code&gt;(Nothing, False)&lt;/code&gt;; on a
tick cycle it is &lt;code&gt;(Just byte, False)&lt;/code&gt;. &lt;strong&gt;Same state, different output,
and the thing that differs is the input &lt;code&gt;tick&lt;/code&gt;.&lt;/strong&gt; The output reads an
input. That is the definition of a Mealy output, and it is exactly the
accent tap the transmitter’s figure showed and the transmitter’s code
lacked.&lt;/p&gt;
&lt;p&gt;It is worth being as careful here as the transmitter post was, because
the distinction is easy to fumble. The Mealy-ness is &lt;em&gt;not&lt;/em&gt; that the next
state depends on the input — that is true of every state machine, Moore
included, and says nothing. The Mealy-ness is that the &lt;em&gt;output&lt;/em&gt; depends on
the input: the byte-ready and framing-error strobes are functions of
&lt;code&gt;tick&lt;/code&gt; (and, at the resolving stop tick, of the freshly-sampled &lt;code&gt;line&lt;/code&gt;
folded into the vote), not of the state alone. By the one test that
separates the two machine styles — does the input reach the output within
the cycle? — the transmitter’s outputs are Moore and the receiver’s are
Mealy. The &lt;code&gt;mealy&lt;/code&gt; combinator lifted both; only the receiver actually
uses the generality it offers.&lt;/p&gt;
&lt;p&gt;And this is not an accident of implementation but a difference in the
&lt;em&gt;nature&lt;/em&gt; of the two interfaces, which is the satisfying part. The
transmitter’s outputs are &lt;strong&gt;levels&lt;/strong&gt;: &lt;code&gt;line&lt;/code&gt; is a level the receiver will
sample, &lt;code&gt;ready&lt;/code&gt; is a level the caller will poll, and a level wants to be
Moore — steady, glitch-free, held for as long as the state holds.
Levels and Moore go together. The receiver’s outputs are &lt;strong&gt;events&lt;/strong&gt;: “a
byte just completed,” “a frame just failed,” each true for one cycle and
then gone. An event is a strobe, and a strobe is inherently Mealy —
it fires in response to a condition (this tick, this resolution) and must
be low every other cycle no matter what the state is. Events and Mealy go
together. The transmitter drove levels, so it was Moore; the receiver
raises events, so it is Mealy. The two machines wear the two halves of
the Moore/Mealy distinction the way they wear the two halves of
drive/read — one at each end, by the nature of the job.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-strobe-1&quot;&gt;&lt;a href=&quot;#fn-strobe&quot;&gt;[6]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;One consequence to nail down, because an integrator will care: the
strobes are &lt;strong&gt;exactly one cycle wide&lt;/strong&gt;. &lt;code&gt;Just byte&lt;/code&gt; appears on precisely
the single cycle where &lt;code&gt;tick&lt;/code&gt; is high and &lt;code&gt;RxStop&lt;/code&gt; resolves at count 9,
and on the very next cycle — tick or not — the machine is in &lt;code&gt;RxIdle&lt;/code&gt;
and the output is &lt;code&gt;(Nothing, False)&lt;/code&gt; again. There is no holding, no
latching, no “byte valid until you read it.” A consumer of &lt;code&gt;rxByte&lt;/code&gt; must
catch the strobe on the cycle it fires. That is the mirror of the
transmitter’s rule that a caller must present &lt;code&gt;Just b&lt;/code&gt; while &lt;code&gt;ready&lt;/code&gt; is
high — both are one-cycle contracts — and both fall straight out of the
machines being built from a tick-gated &lt;code&gt;mealy&lt;/code&gt; with no output register
tacked on.&lt;/p&gt;
&lt;h2 id=&quot;resolving-the-stop-bit-early&quot;&gt;Resolving the stop bit early&lt;/h2&gt;
&lt;p&gt;There is one asymmetry in &lt;code&gt;rxStep&lt;/code&gt; that is easy to skate past and worth a
paragraph, because it is a real design decision with a real reason. Start
bits and data bits are resolved at the &lt;em&gt;end&lt;/em&gt; of their window — at &lt;code&gt;rxCnt == maxBound&lt;/code&gt;, count 15, the sixteenth tick — but the stop bit is resolved
&lt;em&gt;early&lt;/em&gt;, at count 9, six ticks before the window would close. The two
dispatch arms make the difference plain:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;            RxStop&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;              let&lt;/span&gt;&lt;span&gt; s1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; captureSample s0 line&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;               in&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; rxCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 9&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                    then&lt;/span&gt;&lt;span&gt; decideBit s1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                    else&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s1&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; rxCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;              --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; RxStart / RxData&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;              let&lt;/span&gt;&lt;span&gt; s1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; captureSample s0 line&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;               in&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; rxCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; maxBound&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                    then&lt;/span&gt;&lt;span&gt; decideBit s1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                    else&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s1&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; rxCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;_&lt;/code&gt; arm — start and data — resolves at &lt;code&gt;maxBound&lt;/code&gt;; the &lt;code&gt;RxStop&lt;/code&gt;
arm resolves at 9. Why not let the stop bit run out its full sixteen
ticks like every other bit? Because by count 9 the receiver already has
everything it needs. Its three samples were taken at 7, 8, and 9; the
moment &lt;code&gt;rxS9&lt;/code&gt; is captured, the vote can be computed and the frame closed.
Waiting out counts 10 through 15 would decide nothing new — it would just
sit on a stop bit it has already read. And those six idle ticks are worth
more spent elsewhere. The code comment lays out the reasoning in full:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              -- Resolve the stop bit at its center sample (count 9) rather than&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              -- waiting out the whole window: returning to idle ~6 ticks early&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              -- gives the falling-edge detector idle-high ticks to arm rxPrev,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              -- so a back-to-back next frame (stop immediately followed by start)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              -- still resyncs.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Think about the worst case for start detection: &lt;strong&gt;back-to-back frames&lt;/strong&gt;,
where a sender transmits one byte’s stop bit and immediately drives the
next byte’s start bit, with no idle gap between. The stop bit is high, the
next start bit is low, so there is a clean high-to-low edge right at the
boundary — but the receiver can only catch that edge if it is &lt;em&gt;back in
&lt;code&gt;RxIdle&lt;/code&gt;&lt;/em&gt;, watching, with &lt;code&gt;rxPrev&lt;/code&gt; already primed to high, by the time the
edge arrives. If the receiver were still grinding through counts 10–15 of
the stop bit when the next start edge fell, it would be in the wrong state
to notice, and the next frame would be missed or misaligned. Resolving at
count 9 returns the machine to &lt;code&gt;RxIdle&lt;/code&gt; roughly six ticks before the stop
bit even ends, and those six ticks are high (it is a stop bit, after all),
so they land on &lt;code&gt;rxPrev&lt;/code&gt; as idle-high observations, arming the edge
detector. By the time the next frame’s falling edge arrives, the receiver
is idle, primed, and ready to catch it. Early resolution is not an
optimisation for its own sake; it is what makes the receiver survive a
sender that does not pause between bytes — and the &lt;a rel=&quot;external&quot; href=&quot;https://hedgehog.qa&quot;&gt;tests&lt;/a&gt;
lean on it, feeding lists of bytes through the loopback with no idle
padding and demanding every one come back.&lt;/p&gt;
&lt;h2 id=&quot;the-whole-rxstep-assembled&quot;&gt;The whole &lt;code&gt;rxStep&lt;/code&gt;, assembled&lt;/h2&gt;
&lt;p&gt;We have read &lt;code&gt;rxStep&lt;/code&gt; in pieces — the freeze, the idle edge-detect, the
early stop resolution, the general start/data resolution. Here it is
whole, the transition function &lt;code&gt;mealy&lt;/code&gt; clocks, and now every line should
land:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxStep&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; RxS&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Bool&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;RxS&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;rxStep s &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tick&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; line&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; not tick &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; only move on oversample ticks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      let&lt;/span&gt;&lt;span&gt; s0 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxPrev&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; line&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; remember this tick&amp;#39;s level for next tick&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;       in&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; rxState s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;            RxIdle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;              |&lt;/span&gt;&lt;span&gt; rxPrev s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; high &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; line &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; low &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s0&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; RxStart&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;              |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;            RxStop&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;              let&lt;/span&gt;&lt;span&gt; s1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; captureSample s0 line&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;               in&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; rxCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 9&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                    then&lt;/span&gt;&lt;span&gt; decideBit s1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                    else&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s1&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; rxCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;              let&lt;/span&gt;&lt;span&gt; s1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; captureSample s0 line&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;               in&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; rxCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; maxBound&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                    then&lt;/span&gt;&lt;span&gt; decideBit s1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;                    else&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s1&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;rxCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; rxCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Its type is &lt;code&gt;RxS -&amp;gt; (Bool, Bit) -&amp;gt; (RxS, (Maybe (BitVector 8), Bool))&lt;/code&gt; —
the primer’s &lt;code&gt;s -&amp;gt; i -&amp;gt; (s, o)&lt;/code&gt; exactly, with the receiver’s state, the
receiver’s &lt;code&gt;(tick, line)&lt;/code&gt; input, and the receiver’s &lt;code&gt;(byte, error)&lt;/code&gt;
output. Like &lt;code&gt;txStep&lt;/code&gt;, it splits into two clean halves, though the seam
runs differently.&lt;/p&gt;
&lt;p&gt;The first half is the same freeze the transmitter had: &lt;code&gt;| not tick = (s, (Nothing, False))&lt;/code&gt;. Off a tick — most cycles — the state is returned
untouched and the output is the quiet &lt;code&gt;(Nothing, False)&lt;/code&gt;. The receiver
does nothing between ticks, exactly as the transmitter did nothing; the
enable gates both. (This is also, as the last section argued, where the
Mealy output lives: the output is forced quiet here &lt;em&gt;by the input&lt;/em&gt;.)&lt;/p&gt;
&lt;p&gt;The second half, on a tick, is the receiver’s own. First &lt;code&gt;s0 = s{rxPrev = line}&lt;/code&gt; records this tick’s line so the &lt;em&gt;next&lt;/em&gt; tick can detect an edge
against it — the one piece of bookkeeping every tick does regardless of
phase. Then a three-way dispatch on the phase, and this is where the
transmitter and receiver finally diverge in shape rather than just in
verb. &lt;code&gt;RxIdle&lt;/code&gt; does not count or sample at all — it only watches for the
falling edge, firing &lt;code&gt;RxStart&lt;/code&gt; on a high-to-low transition and otherwise
sitting idle with &lt;code&gt;rxPrev&lt;/code&gt; refreshed. &lt;code&gt;RxStop&lt;/code&gt; captures its sample and
resolves &lt;em&gt;early&lt;/em&gt; at count 9. The catch-all &lt;code&gt;_&lt;/code&gt; — start and data —
captures its sample and resolves at the window’s end, count 15. Idle
hunts; stop resolves early; start and data resolve late. Three rhythms,
one per kind of bit, where the transmitter had marched every phase to the
identical sixteen-tick drum. The receiver has to be more supple because
it is chasing a frame it did not schedule, and the suppleness is all
right here, in the seams of one &lt;code&gt;case&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-tests&quot;&gt;The tests&lt;/h2&gt;
&lt;p&gt;The receiver shares &lt;code&gt;Test/Uart.hs&lt;/code&gt; with the rest of the UART, and because
it is sequential the tests drive it through Clash simulation — &lt;code&gt;sampleN&lt;/code&gt;,
&lt;code&gt;bundle&lt;/code&gt;, &lt;code&gt;fromList&lt;/code&gt; — rather than the pure-function style. Four of the
properties are the receiver’s own, and they are the four places the
design made a promise specific enough to break: it decodes a clean frame,
it flags a bad one, it shrugs off a single glitch, and it samples the
center and not the edge. Read them and you have read the receiver’s
contract as executable claims.&lt;/p&gt;
&lt;p&gt;They all run through one small harness, so it is worth meeting it first:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;bitAt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Int&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;bitAt b i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span&gt; testBit b i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;then&lt;/span&gt;&lt;span&gt; high &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;else&lt;/span&gt;&lt;span&gt; low&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Bit&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;frame b stop &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  L.&lt;/span&gt;&lt;span&gt;replicate &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;16&lt;/span&gt;&lt;span&gt; low&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    &amp;lt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;concatMap &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;\&lt;/span&gt;&lt;span&gt;i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;replicate &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;16&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;bitAt b i&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ..&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 7&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    &amp;lt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;replicate &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;16&lt;/span&gt;&lt;span&gt; stop&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;runRx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Bit&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;runRx samples &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  sampleN&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; +&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;length samples &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 24&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;bundle &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;uartRx &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pure &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; lineSig&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Dom100&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  lineSig &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; fromList &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;L.&lt;/span&gt;&lt;span&gt;replicate &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;8&lt;/span&gt;&lt;span&gt; high &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span&gt; samples &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;repeat high&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;recovered&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;recovered xs &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Just&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; _&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;-&lt;/span&gt;&lt;span&gt; xs&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;anyErr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;anyErr xs &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;or &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;e &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; e&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;-&lt;/span&gt;&lt;span&gt; xs&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;frame b stop&lt;/code&gt; hand-builds the oversampled line for a byte: sixteen &lt;code&gt;low&lt;/code&gt;
samples for the start bit, then for each &lt;code&gt;i&lt;/code&gt; from 0 to 7 sixteen copies of
that byte’s &lt;code&gt;i&lt;/code&gt;-th bit (&lt;code&gt;bitAt&lt;/code&gt;, LSB-first — the same convention the
transmitter drives and &lt;code&gt;decideBit&lt;/code&gt; assembles), then sixteen copies of
whatever &lt;code&gt;stop&lt;/code&gt; level you pass. Sixteen samples a bit because that is the
oversampling; a whole 8N1 frame is 160 samples. &lt;code&gt;runRx&lt;/code&gt; is the driver: it
sets &lt;code&gt;tick = pure True&lt;/code&gt; so &lt;em&gt;every&lt;/em&gt; cycle is an oversample tick — sixteen
cycles to a bit, the fast cadence, no NCO needed here — prepends eight
idle-high samples (giving the two-flop synchronizer time to fill and
&lt;code&gt;rxPrev&lt;/code&gt; time to arm high before any edge), pads the tail with an infinite
idle-high &lt;code&gt;L.repeat high&lt;/code&gt;, and samples the bundled &lt;code&gt;(byte, error)&lt;/code&gt; output
for enough cycles to see the frame through. &lt;code&gt;recovered&lt;/code&gt; sifts the &lt;code&gt;Just&lt;/code&gt;
strobes out of the output stream into a list of bytes; &lt;code&gt;anyErr&lt;/code&gt; ORs the
error strobes. With that vocabulary the four properties are one or two
lines each.&lt;/p&gt;
&lt;figure class=&quot;rxwf-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;rxwf&quot; viewBox=&quot;0 0 760 208&quot; role=&quot;img&quot; aria-labelledby=&quot;rxwf-t rxwf-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;rxwf-t&quot;&gt;A received 8N1 frame with per-bit center sampling&lt;/title&gt;
&lt;desc id=&quot;rxwf-d&quot;&gt;The received line over time for the byte 0x4B. It idles high, drops low for one start bit, carries the eight data bits least-significant first (1, 1, 0, 1, 0, 0, 1, 0), returns high for one stop bit, and idles high. Each cell is sixteen oversample samples wide. An accent dot marks the center of each cell, where the receiver takes its three-sample majority vote; the recovered bits assemble bit-for-bit into the original byte.&lt;/desc&gt;
&lt;style&gt;
.rxwf{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.rxwf .line{stroke:var(--fg-main);stroke-width:2.5;fill:none}
.rxwf .startseg{stroke:var(--accent);stroke-width:2.5;fill:none}
.rxwf .guide{stroke:var(--fg-dim);stroke-width:1;fill:none;stroke-dasharray:3 4}
.rxwf .dot{fill:var(--accent)}
.rxwf text{font-family:var(--sans)}
.rxwf .lab{fill:var(--fg-main);font-size:13px}
.rxwf .dim{fill:var(--fg-dim);font-size:11px}
.rxwf .acc{fill:var(--accent);font-size:11px}
.rxwf .sig{fill:var(--fg-dim);font-family:var(--mono);font-size:12px}
&lt;/style&gt;
&lt;text class=&quot;lab&quot; x=&quot;382&quot; y=&quot;26&quot; text-anchor=&quot;middle&quot;&gt;recovered byte = 0x4B  (&#39;K&#39;)&lt;/text&gt;
&lt;line class=&quot;guide&quot; x1=&quot;122&quot; y1=&quot;52&quot; x2=&quot;122&quot; y2=&quot;140&quot;/&gt;
&lt;line class=&quot;guide&quot; x1=&quot;174&quot; y1=&quot;52&quot; x2=&quot;174&quot; y2=&quot;140&quot;/&gt;
&lt;line class=&quot;guide&quot; x1=&quot;590&quot; y1=&quot;52&quot; x2=&quot;590&quot; y2=&quot;140&quot;/&gt;
&lt;line class=&quot;guide&quot; x1=&quot;642&quot; y1=&quot;52&quot; x2=&quot;642&quot; y2=&quot;140&quot;/&gt;
&lt;polyline class=&quot;line&quot; points=&quot;70,66 122,66 122,126 174,126 174,66 278,66 278,126 330,126 330,66 382,66 382,126 486,126 486,66 538,66 538,126 590,126 590,66 694,66&quot;/&gt;
&lt;polyline class=&quot;startseg&quot; points=&quot;122,66 122,126 174,126 174,66&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;148&quot; cy=&quot;126&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;200&quot; cy=&quot;66&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;252&quot; cy=&quot;66&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;304&quot; cy=&quot;126&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;356&quot; cy=&quot;66&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;408&quot; cy=&quot;126&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;460&quot; cy=&quot;126&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;512&quot; cy=&quot;66&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;564&quot; cy=&quot;126&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;616&quot; cy=&quot;66&quot; r=&quot;3&quot;/&gt;
&lt;text class=&quot;sig&quot; x=&quot;60&quot; y=&quot;70&quot; text-anchor=&quot;end&quot;&gt;1&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;60&quot; y=&quot;130&quot; text-anchor=&quot;end&quot;&gt;0&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;96&quot; y=&quot;154&quot; text-anchor=&quot;middle&quot;&gt;idle&lt;/text&gt;
&lt;text class=&quot;acc&quot; x=&quot;148&quot; y=&quot;154&quot; text-anchor=&quot;middle&quot;&gt;start&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;200&quot; y=&quot;154&quot; text-anchor=&quot;middle&quot; style=&quot;font-size:11px&quot;&gt;b0&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;252&quot; y=&quot;154&quot; text-anchor=&quot;middle&quot; style=&quot;font-size:11px&quot;&gt;b1&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;304&quot; y=&quot;154&quot; text-anchor=&quot;middle&quot; style=&quot;font-size:11px&quot;&gt;b2&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;356&quot; y=&quot;154&quot; text-anchor=&quot;middle&quot; style=&quot;font-size:11px&quot;&gt;b3&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;408&quot; y=&quot;154&quot; text-anchor=&quot;middle&quot; style=&quot;font-size:11px&quot;&gt;b4&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;460&quot; y=&quot;154&quot; text-anchor=&quot;middle&quot; style=&quot;font-size:11px&quot;&gt;b5&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;512&quot; y=&quot;154&quot; text-anchor=&quot;middle&quot; style=&quot;font-size:11px&quot;&gt;b6&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;564&quot; y=&quot;154&quot; text-anchor=&quot;middle&quot; style=&quot;font-size:11px&quot;&gt;b7&lt;/text&gt;
&lt;text class=&quot;acc&quot; x=&quot;616&quot; y=&quot;154&quot; text-anchor=&quot;middle&quot;&gt;stop&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;668&quot; y=&quot;154&quot; text-anchor=&quot;middle&quot;&gt;idle&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;382&quot; y=&quot;184&quot; text-anchor=&quot;middle&quot;&gt;each dot: 3-sample majority vote at counts 7/8/9 — data read LSB-first&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The same byte the &lt;a href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; sent — ASCII &lt;code&gt;&#39;K&#39;&lt;/code&gt;, &lt;code&gt;0x4B&lt;/code&gt; = &lt;code&gt;0b0100_1011&lt;/code&gt; — now arriving at the receiver. The frame is idle-high, one accent &lt;strong&gt;start&lt;/strong&gt; bit, the eight data bits &lt;strong&gt;LSB-first&lt;/strong&gt; (&lt;code&gt;1 1 0 1 0 0 1 0&lt;/code&gt;), one high &lt;strong&gt;stop&lt;/strong&gt; bit, then idle. Each accent dot marks a cell center, where the receiver&#39;s three samples (counts 7/8/9) are majority-voted into that cell&#39;s bit; &lt;code&gt;decideBit&lt;/code&gt; writes each into &lt;code&gt;rxShift&lt;/code&gt; by index and, on the stop bit&#39;s high vote, strobes out &lt;code&gt;Just 0x4B&lt;/code&gt;. This is the transmitter&#39;s waveform figure read from the sampling end.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The first property is the whole receiver in four lines: build a clean
frame for a random byte, run it in, get the byte back:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;RX decodes a clean 8N1 frame&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genByte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; runRx &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;frame b high&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  recovered out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;===&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  anyErr out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;===&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://hedgehog.qa&quot;&gt;Hedgehog&lt;/a&gt; draws a random byte &lt;code&gt;b&lt;/code&gt;, &lt;code&gt;frame b high&lt;/code&gt; renders it as a clean
160-sample line with a proper high stop bit, and the assertions are
exactly the contract: &lt;code&gt;recovered out === [b]&lt;/code&gt; — one byte came back, and
it was &lt;code&gt;b&lt;/code&gt;, bit-for-bit — and &lt;code&gt;anyErr out === False&lt;/code&gt; — no framing error
along the way. Every claim we have made about the receiver has to hold at
once for this to pass: the synchronizer has to pass the line through, the
edge detector has to catch the start, the center sampling has to land in
each cell, the vote has to read each bit, &lt;code&gt;decideBit&lt;/code&gt; has to assemble them
LSB-first, and the stop bit has to close the frame and strobe the byte
out. It is the counterpart of the transmitter’s waveform test, run in the
opposite direction, and it is the floor the other three build on.&lt;/p&gt;
&lt;p&gt;The second flips the stop bit and demands the failure:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;RX flags a framing error on a low stop bit&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genByte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; runRx &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;frame b low&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  recovered out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;===&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  anyErr out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;===&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; True&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The only change is &lt;code&gt;frame b low&lt;/code&gt; — the same start and data bits, but the
stop bit driven &lt;em&gt;low&lt;/em&gt; instead of high, a malformed frame. Now the
assertions invert: &lt;code&gt;recovered out === []&lt;/code&gt; — &lt;strong&gt;no&lt;/strong&gt; byte comes back, the
assembled &lt;code&gt;rxShift&lt;/code&gt; is dropped and never strobed — and &lt;code&gt;anyErr out === True&lt;/code&gt; — the framing-error flag fired. This is the &lt;code&gt;RxStop&lt;/code&gt; arm’s &lt;code&gt;low&lt;/code&gt;
branch, &lt;code&gt;(Nothing, True)&lt;/code&gt;, made into a property. The receiver does not
quietly hand up a byte from a frame that did not close; it raises the flag
and swallows the byte, and the test pins both halves of that behaviour.
The framing-error path the transmitter had no counterpart for is not
dead code — it has a test that fails if it ever stops working.&lt;/p&gt;
&lt;p&gt;The third is the majority vote made honest. It corrupts exactly one
sample — at a bit center — and demands the byte decode anyway:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;RX majority vote rejects a single glitch&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genByte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; runRx &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;flipAt &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; +&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 3&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 16&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; +&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;frame b high&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  recovered out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;===&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The instrument is &lt;code&gt;flipAt&lt;/code&gt;, which inverts the single sample at one index:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;flipAt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Int&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Bit&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Bit&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;flipAt k xs &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;if&lt;/span&gt;&lt;span&gt; j &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; k &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;then&lt;/span&gt;&lt;span&gt; complement x &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;else&lt;/span&gt;&lt;span&gt; x &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;j&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; x&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;-&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;zip &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ..&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Int&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; xs&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everything is in the index, &lt;code&gt;16 + 3 * 16 + 8&lt;/code&gt;, so read it as the test
author wrote it. The &lt;code&gt;16&lt;/code&gt; skips the start bit’s sixteen samples. The &lt;code&gt;3 * 16&lt;/code&gt; skips three whole data bits — bit 0, bit 1, bit 2. That lands us at
the start of data bit 3’s window, and the &lt;code&gt;+ 8&lt;/code&gt; steps eight samples into
it — to offset 8, the dead center of the cell, where &lt;code&gt;rxCnt&lt;/code&gt; reaches 8,
the middle of the three voted samples. So &lt;code&gt;flipAt (16 + 3*16 + 8)&lt;/code&gt; reaches
into data bit 3 and flips &lt;em&gt;one&lt;/em&gt; of the three samples the vote will read
— &lt;code&gt;s8&lt;/code&gt;, the center one. The other two, &lt;code&gt;s7&lt;/code&gt; and &lt;code&gt;s9&lt;/code&gt; at offsets 7 and 9,
still carry the true value.&lt;/p&gt;
&lt;p&gt;And that is exactly the situation the majority vote was built for. Two of
the three samples of data bit 3 are correct, one is flipped; &lt;code&gt;maj&lt;/code&gt; returns
what the two agree on; bit 3 decodes correctly despite the glitch; and the
whole byte comes back — &lt;code&gt;recovered out === [b]&lt;/code&gt;, with no weakened
assertion, no allowance for the corruption. The test does not check “the
byte is &lt;em&gt;mostly&lt;/em&gt; right”; it checks the byte is &lt;em&gt;exactly&lt;/em&gt; right, because
the vote makes the single glitch vanish. It is also, quietly, the argument
for three samples over one: had the receiver sampled the center only once,
this very flip would have hit that one sample and corrupted bit 3, and the
byte would have come back wrong. The test would fail against a single-sample
design and passes against this one. That is decision 5 of the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;design&lt;/a&gt;
— majority-of-three rejecting a glitch — locked in silicon-shaped amber.&lt;/p&gt;
&lt;p&gt;The fourth is the cleverest, and it guards the one property the first
three cannot see. Every frame the earlier tests built used &lt;em&gt;constant&lt;/em&gt;
cells — sixteen identical samples per bit — so a receiver that sampled
the leading edge of each cell, or the trailing edge, or the center, would
decode them all identically. Those tests cannot tell center sampling from
edge sampling, because on a constant cell there is nothing to tell. This
one builds a cell that is &lt;em&gt;not&lt;/em&gt; constant:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;centerCell&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Bit&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;centerCell v &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 6&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;=&lt;/span&gt;&lt;span&gt; j &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; j &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 10&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; then&lt;/span&gt;&lt;span&gt; v &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;else&lt;/span&gt;&lt;span&gt; complement v &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span&gt; j &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ..&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 15&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Int&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;centerFrame&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Bit&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;centerFrame b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  L.&lt;/span&gt;&lt;span&gt;replicate &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;16&lt;/span&gt;&lt;span&gt; low&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    &amp;lt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;concatMap &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;centerCell &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span&gt; bitAt b&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ..&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 7&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    &amp;lt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;replicate &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;16&lt;/span&gt;&lt;span&gt; high&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;centerCell v&lt;/code&gt; carries the true value &lt;code&gt;v&lt;/code&gt; only in its center — offsets
6 through 10 — and the &lt;em&gt;complement&lt;/em&gt; of &lt;code&gt;v&lt;/code&gt; everywhere else, at the edges.
&lt;code&gt;centerFrame&lt;/code&gt; frames a byte out of these poisoned cells. Now sampling
position is everything: a receiver that reads the center (offsets 6–10,
which is where counts 7, 8, 9 land) recovers &lt;code&gt;v&lt;/code&gt;; a receiver that reads a
cell boundary — say because its &lt;code&gt;rxCnt&lt;/code&gt; were initialised half a bit early,
so it sampled near offset 0 or 15 — recovers &lt;code&gt;complement v&lt;/code&gt;, the exact
opposite byte. The test demands the center reading:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;RX samples the bit center, not the edge&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genByte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; runRx &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;centerFrame b&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  recovered out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;===&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  anyErr out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;===&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; False&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;recovered out === [b]&lt;/code&gt; — the true byte, not its complement — and no
error. This is a &lt;strong&gt;regression guard&lt;/strong&gt;, and a sharp one: it pins the whole
center-sampling design against a plausible mistake. If someone “simplified”
the alignment and started sampling near a bit boundary, every earlier test
would still pass — constant cells don’t care — but this one would fail,
recovering the complement of every byte. The test comment says exactly
this: &lt;em&gt;a half-bit-early &lt;code&gt;rxCnt&lt;/code&gt; init recovers the complement and fails
here, while the constant-cell tests above cannot tell center from edge.&lt;/em&gt;
It is the property that makes “sample the center” a checked invariant
rather than a comment, and it is the reason Figure C’s whole argument —
center, not edge — is load-bearing rather than decorative.&lt;/p&gt;
&lt;figure class=&quot;rxcve-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;rxcve&quot; viewBox=&quot;0 0 760 238&quot; role=&quot;img&quot; aria-labelledby=&quot;rxcve-t rxcve-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;rxcve-t&quot;&gt;Why center sampling matters: a cell that lies at its edges&lt;/title&gt;
&lt;desc id=&quot;rxcve-d&quot;&gt;One data cell built by centerCell for a true bit of 1. Its center, offsets 6 through 10, carries the true value high; its edges, offsets 0 through 5 and 11 through 15, carry the complement low. An accent band highlights the center. The three center samples at offsets 7, 8 and 9 land in the true-value band and read 1, recovering the byte. A hollow marker near offset 1 shows where a boundary sampler would land, in the complement region, reading 0 and recovering garbage. This is the regression the centerFrame test guards.&lt;/desc&gt;
&lt;style&gt;
.rxcve{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.rxcve .line{stroke:var(--fg-main);stroke-width:2.5;fill:none}
.rxcve .band{fill:var(--accent);opacity:0.13}
.rxcve .dot{fill:var(--fg-dim)}
.rxcve .dotA{fill:var(--accent)}
.rxcve .bnd{fill:none;stroke:var(--accent);stroke-width:2}
.rxcve text{font-family:var(--sans)}
.rxcve .num{fill:var(--fg-dim);font-family:var(--mono);font-size:10px}
.rxcve .samp{fill:var(--accent);font-family:var(--mono);font-size:11px}
.rxcve .lab{fill:var(--fg-main);font-size:12px}
.rxcve .acc{fill:var(--accent);font-size:11.5px}
.rxcve .sig{fill:var(--fg-dim);font-family:var(--mono);font-size:11px}
&lt;/style&gt;
&lt;rect class=&quot;band&quot; x=&quot;291&quot; y=&quot;60&quot; width=&quot;165&quot; height=&quot;98&quot;/&gt;
&lt;text class=&quot;acc&quot; x=&quot;373&quot; y=&quot;52&quot; text-anchor=&quot;middle&quot;&gt;centerCell 1: TRUE only at offsets 6–10&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;86&quot; y=&quot;84&quot; text-anchor=&quot;end&quot;&gt;high (v)&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;86&quot; y=&quot;144&quot; text-anchor=&quot;end&quot;&gt;low (¬v)&lt;/text&gt;
&lt;polyline class=&quot;line&quot; points=&quot;95,140 291,140 291,80 456,80 456,140 620,140&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;110&quot; cy=&quot;140&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;bnd&quot; cx=&quot;143&quot; cy=&quot;140&quot; r=&quot;5.5&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;176&quot; cy=&quot;140&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;209&quot; cy=&quot;140&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;242&quot; cy=&quot;140&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;275&quot; cy=&quot;140&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;308&quot; cy=&quot;80&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dotA&quot; cx=&quot;341&quot; cy=&quot;80&quot; r=&quot;5&quot;/&gt;
&lt;circle class=&quot;dotA&quot; cx=&quot;374&quot; cy=&quot;80&quot; r=&quot;5&quot;/&gt;
&lt;circle class=&quot;dotA&quot; cx=&quot;407&quot; cy=&quot;80&quot; r=&quot;5&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;440&quot; cy=&quot;80&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;473&quot; cy=&quot;140&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;506&quot; cy=&quot;140&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;539&quot; cy=&quot;140&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;572&quot; cy=&quot;140&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;605&quot; cy=&quot;140&quot; r=&quot;3&quot;/&gt;
&lt;text class=&quot;samp&quot; x=&quot;341&quot; y=&quot;70&quot; text-anchor=&quot;middle&quot;&gt;s7&lt;/text&gt;
&lt;text class=&quot;samp&quot; x=&quot;374&quot; y=&quot;70&quot; text-anchor=&quot;middle&quot;&gt;s8&lt;/text&gt;
&lt;text class=&quot;samp&quot; x=&quot;407&quot; y=&quot;70&quot; text-anchor=&quot;middle&quot;&gt;s9&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;110&quot; y=&quot;170&quot; text-anchor=&quot;middle&quot;&gt;0&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;143&quot; y=&quot;170&quot; text-anchor=&quot;middle&quot;&gt;1&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;176&quot; y=&quot;170&quot; text-anchor=&quot;middle&quot;&gt;2&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;209&quot; y=&quot;170&quot; text-anchor=&quot;middle&quot;&gt;3&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;242&quot; y=&quot;170&quot; text-anchor=&quot;middle&quot;&gt;4&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;275&quot; y=&quot;170&quot; text-anchor=&quot;middle&quot;&gt;5&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;308&quot; y=&quot;170&quot; text-anchor=&quot;middle&quot;&gt;6&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;341&quot; y=&quot;170&quot; text-anchor=&quot;middle&quot;&gt;7&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;374&quot; y=&quot;170&quot; text-anchor=&quot;middle&quot;&gt;8&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;407&quot; y=&quot;170&quot; text-anchor=&quot;middle&quot;&gt;9&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;440&quot; y=&quot;170&quot; text-anchor=&quot;middle&quot;&gt;10&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;473&quot; y=&quot;170&quot; text-anchor=&quot;middle&quot;&gt;11&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;506&quot; y=&quot;170&quot; text-anchor=&quot;middle&quot;&gt;12&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;539&quot; y=&quot;170&quot; text-anchor=&quot;middle&quot;&gt;13&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;572&quot; y=&quot;170&quot; text-anchor=&quot;middle&quot;&gt;14&lt;/text&gt;
&lt;text class=&quot;num&quot; x=&quot;605&quot; y=&quot;170&quot; text-anchor=&quot;middle&quot;&gt;15&lt;/text&gt;
&lt;text class=&quot;acc&quot; x=&quot;374&quot; y=&quot;196&quot; text-anchor=&quot;middle&quot;&gt;center samples 7/8/9 → read 1 (recover the byte)&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;143&quot; y=&quot;214&quot; text-anchor=&quot;middle&quot; style=&quot;font-size:11px&quot;&gt;boundary sampler → reads 0, the complement (fails)&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The trap the &lt;code&gt;centerFrame&lt;/code&gt; test sets. Each data cell (here a true bit of &lt;code&gt;1&lt;/code&gt;) carries its real value &lt;em&gt;only&lt;/em&gt; in the center band, offsets 6–10, and the &lt;em&gt;complement&lt;/em&gt; at both edges. The receiver&#39;s three samples (counts 7/8/9, accent) land squarely in the true band and read &lt;code&gt;1&lt;/code&gt;, so the byte decodes. A receiver that sampled near a boundary — the hollow marker, where a half-bit-early &lt;code&gt;rxCnt&lt;/code&gt; would land — would read &lt;code&gt;0&lt;/code&gt;, the complement, and recover the opposite byte. Constant-cell tests cannot see this difference; this one can, which is why it pins center sampling as a checked invariant.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;the-keystone-closes&quot;&gt;The keystone closes&lt;/h2&gt;
&lt;p&gt;The remaining three properties are not the receiver’s alone — they are
the whole UART’s, and the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; post met them from its side.
Read now from the receiver’s end, they are the moment the series’ long
promise pays off. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator&lt;/a&gt; closed by saying its one
blunt tick-rate test was only half a keystone, and that we would meet the
other half “when the receiver is built.” The receiver is built. Here is
the other half.&lt;/p&gt;
&lt;p&gt;The fast loopback wires the transmitter’s line straight into the
receiver’s, sharing one tick:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;fastLoop txByte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; rxByte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tick &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; pure &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;txLine&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; _txReady&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; uartTx tick txByte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;rxByte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; _rxErr&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; uartRx tick txLine&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;uartTx&lt;/code&gt;’s output line &lt;em&gt;is&lt;/em&gt; &lt;code&gt;uartRx&lt;/code&gt;’s input line — no wire, no model,
no golden waveform in between, just the two halves of the UART bolted face
to face. Feed a byte to the transmitter and ask the receiver what it got:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;TX-&amp;gt;RX fast loopback recovers the byte&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genByte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; runFastLoop &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 400&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span&gt;b&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; b&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; out&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ===&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The byte comes back, bit-for-bit. This is the test that would catch any
disagreement between the two halves that a one-sided test could miss: an
LSB/MSB slip, an off-by-one on the stop bit, a sampling point a tick too
early. The transmitter serialises out of a right-shifting register
LSB-first; the receiver assembles by indexed writes LSB-first; and the
only way the byte survives the round trip is if those two conventions
agree exactly. They check each other, with no third party to be wrong.&lt;/p&gt;
&lt;p&gt;The second is the transmitter’s handshake, seen from the receiving end.
The caller jams &lt;code&gt;Just b&lt;/code&gt; at the transmitter a hundred cycles running, and
the receiver counts the result:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;TX ignores input while busy (ready gating)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genByte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; runFastLoop &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; :&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;replicate &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;100&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Just&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 400&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span&gt;b&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; b&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; out&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ===&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Exactly one byte comes out of the receiver, not a hundred and not a
garbled smear of restarted frames. From the transmitter’s side this was
“a &lt;code&gt;Just&lt;/code&gt; presented mid-transmission is ignored”; from the receiver’s
side it is “I got precisely one clean frame.” The receiver is the witness
that the transmitter’s no-corruption property actually holds on the wire.&lt;/p&gt;
&lt;p&gt;The third swaps the always-true tick for the real thing:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;fullLoop txByte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; rxByte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;rxByte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; _err&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; txLine&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; _rdy&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; uart &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;SNat&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;2_000_000&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; rxLine txByte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  rxLine &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; txLine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;full UART loopback (real NCO) recovers the byte&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genByte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; runFullLoop &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; :&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;replicate &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;60&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Just&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1200&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span&gt;b&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; b&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; out&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ===&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;fullLoop&lt;/code&gt; calls the umbrella &lt;code&gt;uart (SNat @2_000_000)&lt;/code&gt; — &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud
generator&lt;/a&gt;, transmitter, and receiver all wired together — and
loops the transmitter’s line back to the receiver’s. Now a bit is not
sixteen cycles but fifty, tiled by sixteen NCO ticks whose gaps alternate
three and four clocks, averaging the fractional 3.125 the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud
generator&lt;/a&gt; fought so hard to keep honest. And the byte still
comes back. That single passing assertion is the entire UART’s keystone:
the fractional-3.125 tick spacing, the transmitter’s Moore-steady
sixteen-tick bits, the synchronizer, the falling-edge start detection, the
center sampling, the majority vote, the early stop resolution — every
one of them has to be right &lt;em&gt;at once&lt;/em&gt;, because the byte is recovered only
if the tick lands sixteen times per bit, the transmitter holds each level
flat, and the receiver samples each center. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator’s&lt;/a&gt;
lone tick-rate test measured the heartbeat; this test spends it, end to
end, and gets the byte back. The keystone the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator&lt;/a&gt;
opened, and the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; began to close, is closed here: the
heartbeat carries a byte across, TX line to RX line, and it arrives
whole.&lt;/p&gt;
&lt;h2 id=&quot;what-we-read&quot;&gt;What we read&lt;/h2&gt;
&lt;p&gt;Thirty-odd lines, one exported name, and the transmitter turned inside
out. The skeleton was the transmitter’s — the same four-constructor
&lt;code&gt;RxState&lt;/code&gt;, the same record carried tick to tick, the same &lt;code&gt;Index 16&lt;/code&gt;
counting sixteen ticks a bit, the same &lt;code&gt;mealy&lt;/code&gt; at the top, the same
freeze-off-tick cadence, the same LSB-first byte — so we read all of that
fast, in a mirror. The post’s words went where the mirror breaks. The
line is an &lt;em&gt;input&lt;/em&gt; now, asynchronous from a pin, so a &lt;strong&gt;2-flop
synchronizer&lt;/strong&gt; clocked every cycle catches it before the tick-gated logic
dares look — the one part of the receiver with no transmitter analogue,
because you do not synchronize a wire you drive. The start bit is &lt;em&gt;hunted&lt;/em&gt;
by its falling &lt;strong&gt;edge&lt;/strong&gt;, tracked through &lt;code&gt;rxPrev&lt;/code&gt;, so a line merely held
low can never fake a frame. Each bit is decided at its &lt;strong&gt;center&lt;/strong&gt; by a
&lt;strong&gt;three-sample majority vote&lt;/strong&gt; on counts 7, 8, 9 — odd so it cannot tie,
three because that is the cheapest count that survives a glitch, tight
around the center so drift never strays onto an edge, and cheap enough to
be three flip-flops and a boolean — which is the actual point of
oversampling sixteen times. A low stop bit raises a &lt;strong&gt;framing error&lt;/strong&gt; and
drops the byte. The stop bit resolves &lt;strong&gt;early&lt;/strong&gt;, at count 9, so
back-to-back frames resync. And the outputs are &lt;strong&gt;genuinely Mealy&lt;/strong&gt;
strobes — one-cycle events that read the &lt;code&gt;tick&lt;/code&gt; input — where the
transmitter’s were steady Moore levels, because levels want Moore and
events want Mealy, one machine at each end.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; taught us the silhouette — find the &lt;code&gt;mealy&lt;/code&gt;, find
the step function, split it into outputs and next state, decide Moore or
Mealy by whether the &lt;em&gt;output&lt;/em&gt; reads the input. The receiver wears that
silhouette and fills in the half the transmitter left blank: the input
side of a UART, where you do not own the wire and must &lt;em&gt;recover&lt;/em&gt; what
someone else put on it. Between the two of them the byte-exact loopback
finally closes — the heartbeat the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator&lt;/a&gt; started now
carries a byte from one end to the other and back, with the real
fractional tick, the two halves checking each other with no reference
model in sight.&lt;/p&gt;
&lt;p&gt;Which is the whole UART, and it is behind us. What waited on the far side
of the transport — the thing the transport exists to serve — is the
Engine: the stateful difficulty spike the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;design&lt;/a&gt; kept
deferring, the eSPI machine the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; was a limb of and the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;introduction&lt;/a&gt; promised. It has the same silhouette we have now
read four times — a &lt;code&gt;mealy&lt;/code&gt;, a pure step, a sum for the phase and a
record for the rest — but it is bigger than a frame, and it is where the
project has been heading all along. The link is up; next we start sending
it something worth carrying.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-metastability&quot;&gt;
&lt;p&gt;&lt;strong&gt;Metastability&lt;/strong&gt; is the failure a synchronous flip-flop
suffers when its data input violates the &lt;em&gt;setup/hold&lt;/em&gt; window — the small
interval around the clock edge during which the input must be stable. A
signal that changes inside that window can leave the flop’s output
balanced between &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;1&lt;/code&gt;, at an invalid intermediate voltage, for an
unbounded time before it resolves &lt;em&gt;randomly&lt;/em&gt; to one rail or the other. It
is not a bug you can code around; it is physics, the metastable point of a
bistable element being a genuine (if unstable) equilibrium. You cannot
&lt;em&gt;prevent&lt;/em&gt; an asynchronous input from occasionally hitting the window —
the RX line comes from a host whose clock bears no relation to &lt;code&gt;Dom100&lt;/code&gt;,
so eventually a line edge coincides with a Tamal clock edge — so instead
you &lt;em&gt;contain&lt;/em&gt; it: sample into &lt;code&gt;sync1&lt;/code&gt;, then give it a whole clock period,
untouched, to fall off the knife-edge before &lt;code&gt;synced&lt;/code&gt; reads it. The
governing figure of merit is &lt;strong&gt;mean time between failures&lt;/strong&gt;, which grows
roughly exponentially with the settling time you allow; one extra
flip-flop turns an MTBF of seconds into one of centuries. Two flops is the
industry-standard minimum for a slow-ish single-bit crossing like a UART
line, which is exactly what &lt;code&gt;register high rxLine&lt;/code&gt; twice provides. The
&lt;code&gt;high&lt;/code&gt; initial value is not about metastability but about &lt;em&gt;belief&lt;/em&gt;: the
idle line is high, so the synchronizer and &lt;code&gt;rxPrev&lt;/code&gt; both power up assuming
the world was quiet, which stops a phantom start edge on the first tick.
Multi-bit crossings (a whole byte at once) need more than a 2-flop
synchronizer — gray coding, or a handshake, or an asynchronous FIFO —
which is one more reason the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;design&lt;/a&gt; kept the UART in a single
clock domain and paid the synchronizer only on the one genuinely
asynchronous bit, the incoming line. &lt;a href=&quot;#fr-metastability-1&quot;&gt;↩&lt;/a&gt; &lt;a href=&quot;#fr-metastability-2&quot;&gt;↩2&lt;/a&gt; &lt;a href=&quot;#fr-metastability-3&quot;&gt;↩3&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-edge&quot;&gt;
&lt;p&gt;Edge detection on a synchronized signal is the standard idiom:
register the signal, then compare the registered copy against the live
one, and a difference is an edge — &lt;code&gt;rxPrev s == high &amp;amp;&amp;amp; line == low&lt;/code&gt; is
precisely “the previous sample was high and this one is low,” a falling
edge. Doing it on the &lt;em&gt;synchronized&lt;/em&gt; line rather than the raw pin matters:
comparing two samples of a metastable signal could see an edge that isn’t
there, so the synchronizer must come first. The sub-tick latency is the
price of checking only once per tick rather than continuously: the true
edge can fall anywhere in a tick interval, so the observing tick lags it
by up to one tick. A design that cared could halve this by sampling the
edge on the fast clock instead of the tick, but the UART does not care,
because the whole point of aligning to the bit &lt;em&gt;center&lt;/em&gt; is to put eight
ticks of slack between the sample and the nearest edge — far more than
the sub-tick start error and the accumulated baud drift combined. The lag
is real and the center makes it free. &lt;a href=&quot;#fr-edge-1&quot;&gt;↩&lt;/a&gt; &lt;a href=&quot;#fr-edge-2&quot;&gt;↩2&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-vote&quot;&gt;
&lt;p&gt;The odd-count requirement is intrinsic to majority voting: with
an even number of voters a tie is possible and “the majority” is
undefined, so you either add a tie-break (complexity, and an arbitrary
bias) or use an odd count and avoid the question. Among odd counts, the
returns diminish fast. One sample tolerates zero faults; three tolerate
one; five tolerate two; in general &lt;code&gt;2k+1&lt;/code&gt; samples tolerate &lt;code&gt;k&lt;/code&gt;. The fault
being guarded against is a glitch landing on a sample &lt;em&gt;at the bit center&lt;/em&gt;,
within the three-tick window 7–9 — a rare event to begin with, since the
center is the quietest part of the settled line — and the probability of
&lt;em&gt;two&lt;/em&gt; independent such glitches in the same window of the same bit is that
rare event squared, which at 2 Mbaud over a short board trace is never.
Three samples buy the entire realistic benefit; five would spend more
flip-flops and a wider vote to guard a case that does not occur. This is
decision 5 of the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;design&lt;/a&gt;, and it is a nice instance of sizing a
mechanism to the actual threat rather than to the most general one
imaginable — the same instinct that chose a one-way &lt;code&gt;ready&lt;/code&gt; handshake
over full backpressure in the transmitter. &lt;a href=&quot;#fr-vote-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-unreachable&quot;&gt;
&lt;p&gt;Both &lt;code&gt;captureSample&lt;/code&gt;’s &lt;code&gt;_ -&amp;gt; s&lt;/code&gt; and &lt;code&gt;decideBit&lt;/code&gt;’s &lt;code&gt;RxIdle -&amp;gt; (s, (Nothing, False))&lt;/code&gt; carry the comment &lt;code&gt;-- actually unreachable&lt;/code&gt;, and
both are the same totality tax the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter&lt;/a&gt; paid for &lt;code&gt;TxIdle -&amp;gt; s&lt;/code&gt;. Haskell sanctions partial functions — &lt;code&gt;head&lt;/code&gt;, &lt;code&gt;fromJust&lt;/code&gt;, and their
kin are non-exhaustive by design — so it treats totality as an opt-in
diagnostic (&lt;code&gt;-Wincomplete-patterns&lt;/code&gt;) rather than a law, and Tamal opts in
via the &lt;code&gt;-Wall&lt;/code&gt; its cabal turns on. A &lt;code&gt;case&lt;/code&gt; that omits a constructor is a
warning; escalated with &lt;code&gt;-Werror=incomplete-patterns&lt;/code&gt; it is a build
failure. For gateware that escalation is the right call, because silicon
has no &lt;code&gt;PatternMatchFail&lt;/code&gt; to throw — an unhandled case would lower to a
don’t-care, a silent wrong answer in the netlist rather than a catchable
exception. So the unreachable arms are written out, returning the safe
do-nothing, to keep every &lt;code&gt;case&lt;/code&gt; total and the compiler satisfied that
there are no holes even in the corners control never reaches. The word
“unreachable” is a claim about the &lt;em&gt;caller&lt;/em&gt; — &lt;code&gt;captureSample&lt;/code&gt; is only
called at counts that could be 7/8/9, &lt;code&gt;decideBit&lt;/code&gt; only from a resolving
tick — that the &lt;em&gt;type&lt;/em&gt; cannot express, so the wildcard stands in for the
proof the compiler cannot check. &lt;a href=&quot;#fr-unreachable-1&quot;&gt;↩&lt;/a&gt; &lt;a href=&quot;#fr-unreachable-2&quot;&gt;↩2&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-assemble&quot;&gt;
&lt;p&gt;The receiver’s indexed assembly and the transmitter’s shift
are two ways to (de)serialise the same LSB-first byte, and it is worth
seeing why the receiver picks the one it does. The transmitter &lt;em&gt;shifted&lt;/em&gt;:
it held the byte in a register, always read the bottom bit, and slid the
register right each bit so the next bit fell into the read position — the
byte moved past a fixed tap. The receiver instead holds the byte still and
writes bit &lt;code&gt;i&lt;/code&gt; directly with &lt;code&gt;setBit&lt;/code&gt;/&lt;code&gt;clearBit (rxShift s) (fromEnum i)&lt;/code&gt;,
addressing the register by index. Either would work for either direction;
the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;design doc&lt;/a&gt; even offers both in one breath — “shift the
sampled bit into the MSB and shift right, &lt;em&gt;or&lt;/em&gt; index by &lt;code&gt;i&lt;/code&gt;.” Indexing
reads a hair more clearly on the receive side, where the phase &lt;code&gt;RxData i&lt;/code&gt;
already carries the index in hand, so writing bit &lt;code&gt;i&lt;/code&gt; is a direct
transcription of “this is data bit &lt;code&gt;i&lt;/code&gt;.” It is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt;’s and the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-tx/&quot;&gt;transmitter’s&lt;/a&gt; shift-register motif a third time — the eSPI CRC
shifted &lt;em&gt;left&lt;/em&gt;, MSB-first, feeding a polynomial division; the transmitter
shifted &lt;em&gt;right&lt;/em&gt;, LSB-first, feeding the wire; the receiver indexes,
LSB-first, filling the byte — the same eight bits handled three ways by
three standards’ conventions, a shift or an index apart. &lt;a href=&quot;#fr-assemble-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-strobe&quot;&gt;
&lt;p&gt;One could make the receiver’s byte output Moore instead — hold
the completed byte in a state field and expose a separate “valid for one
cycle” flag, or present the byte on a register that updates only at frame
end — and some UART IP does exactly that, offering a &lt;em&gt;held&lt;/em&gt; received byte
plus a separate strobe. Tamal chooses the Mealy strobe: &lt;code&gt;Just byte&lt;/code&gt; on
exactly the resolving cycle and &lt;code&gt;Nothing&lt;/code&gt; otherwise, the byte carried
&lt;em&gt;inside&lt;/em&gt; the strobe rather than held beside it. It is cheaper (no output
register, no held-byte field) and it composes cleanly with the shell’s
load FSM, which will consume &lt;code&gt;rxByte&lt;/code&gt; strobes as they fire. The cost is
the one-cycle contract: a consumer must latch the byte the cycle it
appears, because it is gone the next. That is a perfectly ordinary
discipline in synchronous logic — it is the same shape as the
transmitter’s “present &lt;code&gt;Just b&lt;/code&gt; while &lt;code&gt;ready&lt;/code&gt;” — and it is why the output
is honestly Mealy rather than Moore: the strobe is an event, and events
read the clock-enable that times them. &lt;a href=&quot;#fr-strobe-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Tamal: The transmitter</title>
    <published>2026-07-22T09:00:00+00:00</published>
    <updated>2026-07-22T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-uart-tx/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-uart-tx/</id>
    <summary type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator&lt;/a&gt; we read last time produces a &lt;em&gt;heartbeat&lt;/em&gt; —
a &lt;code&gt;Bool&lt;/code&gt; that says &lt;em&gt;now&lt;/em&gt; sixteen times a bit — and nothing else. It had
no state machine at all, just a phase accumulator counting against a
modulus. That was the point: the smallest possible clocked block, a
&lt;code&gt;register&lt;/code&gt; fed back through some arithmetic, so we could meet sequential
logic without also meeting a finite-state machine on the same day. The
post closed by promising that the next block would start &lt;em&gt;spending&lt;/em&gt; that
heartbeat. This is that block.&lt;/p&gt;
&lt;p&gt;The transmitter takes the tick and a byte and drives a wire high and low
in an 8N1 frame — and it is where &lt;code&gt;mealy&lt;/code&gt; finally climbs to the top of
a module, exactly as the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt; said it would and the baud-generator
post repeated on its way out. Every block so far has been a warm-up for
this line:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;uartTx tick mbyte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; unbundle &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mealy txStep initTx &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;bundle &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tick&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; mbyte&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The primer told you to read a &lt;code&gt;mealy&lt;/code&gt; at the top of a module as “clocked
state machine, its brain the pure function beside it.” Here is the
module; here is the brain. But there is a twist the code’s own comment
insists on, and it is a better lesson than a plain Mealy machine would
have been: the thing is &lt;em&gt;lifted&lt;/em&gt; with &lt;code&gt;mealy&lt;/code&gt;, its transition has the
textbook Mealy &lt;em&gt;shape&lt;/em&gt; — and yet its outputs are pure &lt;strong&gt;Moore&lt;/strong&gt;. Sorting
out that apparent contradiction is most of what this post is for.&lt;/p&gt;
&lt;p&gt;Like the CRC and the baud generator, the whole transmitter fits in a
screenful and change.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator&lt;/a&gt; we read last time produces a &lt;em&gt;heartbeat&lt;/em&gt; —
a &lt;code&gt;Bool&lt;/code&gt; that says &lt;em&gt;now&lt;/em&gt; sixteen times a bit — and nothing else. It had
no state machine at all, just a phase accumulator counting against a
modulus. That was the point: the smallest possible clocked block, a
&lt;code&gt;register&lt;/code&gt; fed back through some arithmetic, so we could meet sequential
logic without also meeting a finite-state machine on the same day. The
post closed by promising that the next block would start &lt;em&gt;spending&lt;/em&gt; that
heartbeat. This is that block.&lt;/p&gt;
&lt;p&gt;The transmitter takes the tick and a byte and drives a wire high and low
in an 8N1 frame — and it is where &lt;code&gt;mealy&lt;/code&gt; finally climbs to the top of
a module, exactly as the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt; said it would and the baud-generator
post repeated on its way out. Every block so far has been a warm-up for
this line:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;uartTx tick mbyte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; unbundle &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mealy txStep initTx &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;bundle &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tick&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; mbyte&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The primer told you to read a &lt;code&gt;mealy&lt;/code&gt; at the top of a module as “clocked
state machine, its brain the pure function beside it.” Here is the
module; here is the brain. But there is a twist the code’s own comment
insists on, and it is a better lesson than a plain Mealy machine would
have been: the thing is &lt;em&gt;lifted&lt;/em&gt; with &lt;code&gt;mealy&lt;/code&gt;, its transition has the
textbook Mealy &lt;em&gt;shape&lt;/em&gt; — and yet its outputs are pure &lt;strong&gt;Moore&lt;/strong&gt;. Sorting
out that apparent contradiction is most of what this post is for.&lt;/p&gt;
&lt;p&gt;Like the CRC and the baud generator, the whole transmitter fits in a
screenful and change.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;the-entire-source&quot;&gt;The entire source&lt;/h2&gt;
&lt;p&gt;Minus the license header and the doc-comments, here is
&lt;code&gt;src/Tamal/Uart/Tx.hs&lt;/code&gt; in full:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Uart.Tx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; uartTx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Clash.Prelude&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; TxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxIdle&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxStart&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxData&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxStop&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; TxS&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; TxState&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txShift&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 16&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;uartTx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;HiddenClockResetEnable&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;uartTx tick mbyte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; unbundle &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mealy txStep initTx &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;bundle &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tick&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; mbyte&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;  initTx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; TxS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  initTx &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxS&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxIdle&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txStep&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; TxS&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Bool&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;TxS&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Bit&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;txStep s &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tick&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; mbyte&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; ready&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ready &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; txState s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxIdle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  line &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; txState s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    TxIdle&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; high&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    TxStart&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; low&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    TxData&lt;/span&gt;&lt;span&gt; _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; lsb &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;txShift s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    TxStop&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; high&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  s&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxIdle&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;-&lt;/span&gt;&lt;span&gt; txState s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; mbyte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;        Just&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxStart&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txShift&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;        Nothing&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; not tick &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; txCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;/=&lt;/span&gt;&lt;span&gt; maxBound &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; txCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; txAdvance s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txAdvance&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; TxS&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; TxS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;txAdvance s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; txState s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  TxStart&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxData&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  TxData&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; maxBound &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxStop&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxData&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txShift&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; txShift s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;shiftR&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  TxStop&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxIdle&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  TxIdle&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Four things, top to bottom: two &lt;code&gt;data&lt;/code&gt; declarations that &lt;em&gt;are&lt;/em&gt; the state
machine’s memory, a five-line &lt;code&gt;uartTx&lt;/code&gt; that is nothing but the &lt;code&gt;mealy&lt;/code&gt;
lift and its plumbing, the pure &lt;code&gt;txStep&lt;/code&gt; that is the machine’s whole
brain, and a &lt;code&gt;txAdvance&lt;/code&gt; helper that walks the frame forward one bit at a
time. We will read them roughly in that order, but the two types come
first, because everything else is written in terms of them.&lt;/p&gt;
&lt;h2 id=&quot;one-exported-name&quot;&gt;One exported name&lt;/h2&gt;
&lt;p&gt;The opening beat is the CRC module’s and the baud generator’s, played a
third time, so I will be quick:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Uart.Tx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; uartTx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Clash.Prelude&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;module Tamal.Uart.Tx&lt;/code&gt; names the module after its path on disk —
&lt;code&gt;src/Tamal/Uart/Tx.hs&lt;/code&gt;, a dot per directory — and the parenthesised
export list is the one door in the wall. Only &lt;code&gt;uartTx&lt;/code&gt; leaves the file;
&lt;code&gt;txStep&lt;/code&gt;, &lt;code&gt;txAdvance&lt;/code&gt;, &lt;code&gt;TxState&lt;/code&gt;, and &lt;code&gt;TxS&lt;/code&gt; are all sealed behind it, not
because the list declines to mention them but because everything private
is written where nothing else can name it. &lt;code&gt;import Clash.Prelude&lt;/code&gt; is the
same prelude swap the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC post&lt;/a&gt; dwelt on — the line that throws out
ordinary Haskell’s furniture and moves in &lt;code&gt;Signal&lt;/code&gt;, &lt;code&gt;Bit&lt;/code&gt;, &lt;code&gt;BitVector&lt;/code&gt;,
&lt;code&gt;register&lt;/code&gt;, &lt;code&gt;mealy&lt;/code&gt;, and the rest of the vocabulary that lowers to gates.
I will not re-derive either; the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud-generator&lt;/a&gt;
posts did that at length.&lt;/p&gt;
&lt;p&gt;One thing is &lt;em&gt;missing&lt;/em&gt; that both earlier modules had: there is no
&lt;code&gt;{-# LANGUAGE NumericUnderscores #-}&lt;/code&gt; pragma, because this file never
writes a twelve-digit literal. The transmitter has no clock frequency to
name — the frequency lives in the baud generator, upstream, folded into
the tick this module simply consumes. That absence is worth noticing,
because it is the first hint of how the UART is factored: each block owns
exactly one idea, and the transmitter’s idea is not &lt;em&gt;timing&lt;/em&gt;, it is
&lt;em&gt;framing&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-type-a-byte-in-a-line-out&quot;&gt;The type: a byte in, a line out&lt;/h2&gt;
&lt;p&gt;Now the signature, which under the primer’s reading is already half the
documentation:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;uartTx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;HiddenClockResetEnable&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read it against the baud generator’s and the first thing you notice is
what is &lt;em&gt;gone&lt;/em&gt;. &lt;code&gt;oversampleTick&lt;/code&gt; needed a &lt;code&gt;forall&lt;/code&gt;, an &lt;code&gt;SNat baud&lt;/code&gt;
argument, and a &lt;code&gt;KnownNat&lt;/code&gt;/&lt;code&gt;KnownDomain&lt;/code&gt; pile of constraints, because it
had to read a clock frequency out of the domain type and turn a baud rate
into an increment. The transmitter needs none of that. It has one
constraint, &lt;code&gt;HiddenClockResetEnable dom&lt;/code&gt; — there is a clock (and reset,
and enable) threaded implicitly, which is what &lt;code&gt;mealy&lt;/code&gt;’s hidden
&lt;code&gt;register&lt;/code&gt; will draw on — and then two plain arrows in and a pair out.
No numbers in the type at all. The transmitter does not know or care what
the baud rate is; it knows only “a bit is sixteen ticks,” and the ticks
arrive from elsewhere.&lt;/p&gt;
&lt;p&gt;Take the two inputs in turn.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Signal dom Bool&lt;/code&gt; is the oversample tick — the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator’s&lt;/a&gt;
output wire, plugged straight in. Under the primer’s reading it is “a
&lt;code&gt;Bool&lt;/code&gt; that may change on every clock tick,” and we spent a whole post
establishing that this particular &lt;code&gt;Bool&lt;/code&gt; is an &lt;em&gt;enable&lt;/em&gt;, true on the
cycles the UART should advance and false on the rest. The transmitter is
one of the two things that enable was built to gate. Every bit-advancing
decision below is downstream of this wire being &lt;code&gt;True&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Signal dom (Maybe (BitVector 8))&lt;/code&gt; is the send request, and the &lt;code&gt;Maybe&lt;/code&gt;
is doing exactly the job the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt; gave it. There, a step returning
&lt;code&gt;Maybe Ring&lt;/code&gt; stated a hardware fact — &lt;em&gt;at most one trace-RAM write
happens this cycle&lt;/em&gt; — a write-enable promoted to a type. Here the
direction is reversed but the idea is identical: &lt;code&gt;Just b&lt;/code&gt; on some cycle
means “please transmit this byte,” and &lt;code&gt;Nothing&lt;/code&gt; means “I have nothing
for you.” The absence lives in the type, out in the open; there is no
sentinel byte, no separate valid line to forget to check. A send request
is a byte that might not be there, and its type says so.&lt;/p&gt;
&lt;p&gt;That leaves the result, &lt;code&gt;(Signal dom Bit, Signal dom Bool)&lt;/code&gt; — a pair of
output wires:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Signal dom Bit&lt;/code&gt; is &lt;code&gt;line&lt;/code&gt;, the serial output, one &lt;code&gt;Bit&lt;/code&gt; per cycle. It
idles high and is driven low and high through a frame. This is the
physical wire that, on the board, runs to the USB-UART bridge and out
to the host.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Signal dom Bool&lt;/code&gt; is &lt;code&gt;ready&lt;/code&gt;, a flag that is high exactly when the
transmitter is idle and can accept a new byte. It is one half of a
one-cycle handshake; the caller is meant to present &lt;code&gt;Just b&lt;/code&gt; only on a
cycle when &lt;code&gt;ready&lt;/code&gt; is high.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the whole interface is: &lt;em&gt;here is a heartbeat and maybe a byte; take
the wire I drive and a flag that says whether I’m listening.&lt;/em&gt; This is the
“strobe/handshake” interface the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;UART design&lt;/a&gt; chose over full
valid/ready backpressure, and it is cheap because it can afford to be —
at 2 Mbaud a byte spans roughly five hundred &lt;code&gt;Dom100&lt;/code&gt; cycles, so a
consumer draining bytes has five hundred cycles of slack and never needs
to stall the line.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-backpressure-1&quot;&gt;&lt;a href=&quot;#fn-backpressure&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id=&quot;two-types-that-are-the-state&quot;&gt;Two types that are the state&lt;/h2&gt;
&lt;p&gt;Everything the transmitter remembers lives in two &lt;code&gt;data&lt;/code&gt; declarations,
and they are worth reading slowly, because they are the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt;’s
sum-and-product story told in hardware. The user of this UART never sees
either type — they are sealed behind the export list — but they &lt;em&gt;are&lt;/em&gt;
the machine.&lt;/p&gt;
&lt;p&gt;The first is the phase:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; TxState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxIdle&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxStart&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxData&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxStop&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read the &lt;code&gt;|&lt;/code&gt; as “or”: a &lt;code&gt;TxState&lt;/code&gt; is &lt;code&gt;TxIdle&lt;/code&gt; &lt;strong&gt;or&lt;/strong&gt; &lt;code&gt;TxStart&lt;/code&gt; &lt;strong&gt;or&lt;/strong&gt;
&lt;code&gt;TxData&lt;/code&gt; (carrying an &lt;code&gt;Index 8&lt;/code&gt;) &lt;strong&gt;or&lt;/strong&gt; &lt;code&gt;TxStop&lt;/code&gt;, and nothing else. This
is the primer’s &lt;strong&gt;sum type&lt;/strong&gt; — the FSM’s state register written as a
type, the same shape as the engine’s &lt;code&gt;Phase&lt;/code&gt; from that post. Four of a
UART frame’s five ideas are here by name: the line is idle, or driving
the start bit, or driving one of the data bits, or driving the stop bit.&lt;/p&gt;
&lt;p&gt;The interesting constructor is &lt;code&gt;TxData (Index 8)&lt;/code&gt;. It is a
&lt;em&gt;data-bearing&lt;/em&gt; constructor, the primer’s &lt;code&gt;Circle Double&lt;/code&gt; pattern: the
tag &lt;code&gt;TxData&lt;/code&gt; travels with a payload, and the payload’s type is &lt;code&gt;Index 8&lt;/code&gt;
— a number in the range &lt;code&gt;0..7&lt;/code&gt; and, by construction, &lt;em&gt;never&lt;/em&gt; 8. So
&lt;code&gt;TxData&lt;/code&gt; is not one state but eight, one per data bit, and the index that
distinguishes them cannot stray out of range because the type will not
let the value &lt;code&gt;8&lt;/code&gt; exist in the first place. Count the inhabitants and the
whole state space is exactly &lt;code&gt;1 + 1 + 8 + 1 = 11&lt;/code&gt;: idle, start, eight
data, stop. The compiler knows that number, which is why a &lt;code&gt;case&lt;/code&gt; over a
&lt;code&gt;TxState&lt;/code&gt; that forgets a constructor is a warning at build time — the
primer’s exhaustiveness check, standing guard over the state machine, for
free.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-index-1&quot;&gt;&lt;a href=&quot;#fn-index&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;deriving&lt;/code&gt; block is the primer’s incantation, and by now it should
read as ordinary furniture rather than noise:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Show&lt;/code&gt; so a failing test can print the state it choked on; &lt;code&gt;Eq&lt;/code&gt; so
&lt;code&gt;txState s == TxIdle&lt;/code&gt; a few lines down is legal; &lt;code&gt;Generic&lt;/code&gt; as the
structural plumbing other machinery builds on; and &lt;code&gt;NFDataX&lt;/code&gt;, the one
that is really about hardware — Clash’s way of saying &lt;em&gt;this type is
allowed to sit in a register&lt;/em&gt;, its values, including “undefined at
power-up,” well-defined enough to be stored in flip-flops. A &lt;code&gt;TxState&lt;/code&gt;
is going into a register in a moment, so it must earn &lt;code&gt;NFDataX&lt;/code&gt;, and
&lt;code&gt;deriving anyclass&lt;/code&gt; is how it does.&lt;/p&gt;
&lt;p&gt;The second type is everything the machine carries from one tick to the
next:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; TxS&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; TxState&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txShift&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 16&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the primer’s &lt;strong&gt;product type&lt;/strong&gt; — a record, a struct, a bundle
that holds one field &lt;em&gt;and&lt;/em&gt; another &lt;em&gt;and&lt;/em&gt; another. Where the engine’s
state was a big &lt;code&gt;State { phase, pc, regs, ... }&lt;/code&gt;, the transmitter’s is a
small &lt;code&gt;TxS { txState, txShift, txCnt }&lt;/code&gt;, and the record syntax hands us
three accessor functions for free: &lt;code&gt;txState :: TxS -&amp;gt; TxState&lt;/code&gt;,
&lt;code&gt;txShift :: TxS -&amp;gt; BitVector 8&lt;/code&gt;, &lt;code&gt;txCnt :: TxS -&amp;gt; Index 16&lt;/code&gt;. Each field
earns its place:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;txState&lt;/code&gt;&lt;/strong&gt; is the phase we just met — where in the frame we are.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;txShift&lt;/code&gt;&lt;/strong&gt; is the latched byte, held in a &lt;code&gt;BitVector 8&lt;/code&gt; and shifted
right as the frame progresses so its least-significant bit always
presents the &lt;em&gt;next&lt;/em&gt; bit to send. It is eight wires with no arithmetic
meaning — the CRC post’s reason for &lt;code&gt;BitVector&lt;/code&gt; over &lt;code&gt;Unsigned&lt;/code&gt; —
because we only ever shift it and read its bottom bit, never add to it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;txCnt&lt;/code&gt;&lt;/strong&gt; is the position &lt;em&gt;within&lt;/em&gt; the current bit, an &lt;code&gt;Index 16&lt;/code&gt;
counting the sixteen oversample ticks that make one bit-time. Again the
width is load-bearing: &lt;code&gt;0..15&lt;/code&gt;, never 16, so “have we held this bit for
a full sixteen ticks?” is exactly “has &lt;code&gt;txCnt&lt;/code&gt; reached &lt;code&gt;maxBound&lt;/code&gt;?”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add it up and the entire memory of the transmitter is a phase (four bits
would over-cover its eleven states), a byte, and a four-bit counter —
call it fifteen bits of flip-flop. That is the whole &lt;code&gt;s&lt;/code&gt; that &lt;code&gt;mealy&lt;/code&gt; is
about to clock. Hold the shape of it in your head; the rest of the file
is just the pure function that turns one &lt;code&gt;TxS&lt;/code&gt; into the next.&lt;/p&gt;
&lt;figure class=&quot;txfsm-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;txfsm&quot; viewBox=&quot;0 0 760 236&quot; role=&quot;img&quot; aria-labelledby=&quot;txfsm-t txfsm-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;txfsm-t&quot;&gt;The transmitter&#39;s TxState finite-state machine&lt;/title&gt;
&lt;desc id=&quot;txfsm-d&quot;&gt;Four state nodes in a row: TxIdle (line high), TxStart (line low), TxData i (line equals data bit i), and TxStop (line high). TxIdle has a dashed self-loop labelled Nothing, meaning it stays idle when no byte is offered, and an accented arrow to TxStart labelled Just b, the input-driven handshake that is not tick-gated. TxStart advances to TxData 0 after sixteen ticks; TxData has a self-loop that increments the data-bit index and shifts the latched byte right while i is less than seven; after data bit seven it advances to TxStop; TxStop returns to TxIdle after sixteen ticks. The line value shown under each state is a Moore output, a pure function of the state.&lt;/desc&gt;
&lt;style&gt;
.txfsm{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.txfsm .st{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.txfsm .wire{stroke:var(--fg-main);stroke-width:2;fill:none}
.txfsm .hs{stroke:var(--accent);stroke-width:2.5;fill:none}
.txfsm .loop{stroke:var(--fg-main);stroke-width:2;fill:none}
.txfsm .idleloop{stroke:var(--fg-dim);stroke-width:2;fill:none;stroke-dasharray:4 4}
.txfsm text{font-family:var(--sans)}
.txfsm .name{fill:var(--fg-main);font-family:var(--mono);font-size:15px}
.txfsm .lab{fill:var(--fg-main);font-size:12px}
.txfsm .labA{fill:var(--accent);font-size:12px}
.txfsm .dim{fill:var(--fg-dim);font-size:11.5px}
.txfsm .ah{fill:var(--fg-main)}
.txfsm .aha{fill:var(--accent)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;txfsm-a&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;txfsm-aa&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;aha&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;rect class=&quot;st&quot; x=&quot;51&quot; y=&quot;92&quot; width=&quot;88&quot; height=&quot;54&quot; rx=&quot;8&quot;/&gt;
&lt;rect class=&quot;st&quot; x=&quot;241&quot; y=&quot;92&quot; width=&quot;88&quot; height=&quot;54&quot; rx=&quot;8&quot;/&gt;
&lt;rect class=&quot;st&quot; x=&quot;431&quot; y=&quot;92&quot; width=&quot;88&quot; height=&quot;54&quot; rx=&quot;8&quot;/&gt;
&lt;rect class=&quot;st&quot; x=&quot;621&quot; y=&quot;92&quot; width=&quot;88&quot; height=&quot;54&quot; rx=&quot;8&quot;/&gt;
&lt;path class=&quot;idleloop&quot; d=&quot;M82,92 C79,63 111,63 108,92&quot; marker-end=&quot;url(#txfsm-a)&quot;/&gt;
&lt;path class=&quot;loop&quot; d=&quot;M462,92 C458,58 492,58 488,92&quot; marker-end=&quot;url(#txfsm-a)&quot;/&gt;
&lt;line class=&quot;hs&quot; x1=&quot;139&quot; y1=&quot;119&quot; x2=&quot;239&quot; y2=&quot;119&quot; marker-end=&quot;url(#txfsm-aa)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;329&quot; y1=&quot;119&quot; x2=&quot;429&quot; y2=&quot;119&quot; marker-end=&quot;url(#txfsm-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;519&quot; y1=&quot;119&quot; x2=&quot;619&quot; y2=&quot;119&quot; marker-end=&quot;url(#txfsm-a)&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M665,146 L665,200 L95,200 L95,148&quot; marker-end=&quot;url(#txfsm-a)&quot;/&gt;
&lt;text class=&quot;name&quot; x=&quot;95&quot; y=&quot;114&quot; text-anchor=&quot;middle&quot;&gt;TxIdle&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;285&quot; y=&quot;114&quot; text-anchor=&quot;middle&quot;&gt;TxStart&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;475&quot; y=&quot;114&quot; text-anchor=&quot;middle&quot;&gt;TxData i&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;665&quot; y=&quot;114&quot; text-anchor=&quot;middle&quot;&gt;TxStop&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;95&quot; y=&quot;133&quot; text-anchor=&quot;middle&quot;&gt;line high&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;285&quot; y=&quot;133&quot; text-anchor=&quot;middle&quot;&gt;line low&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;475&quot; y=&quot;133&quot; text-anchor=&quot;middle&quot;&gt;line = bit i&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;665&quot; y=&quot;133&quot; text-anchor=&quot;middle&quot;&gt;line high&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;95&quot; y=&quot;55&quot; text-anchor=&quot;middle&quot;&gt;Nothing&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;475&quot; y=&quot;50&quot; text-anchor=&quot;middle&quot;&gt;i &amp;lt; 7 → i+1, shiftR&lt;/text&gt;
&lt;text class=&quot;labA&quot; x=&quot;190&quot; y=&quot;107&quot; text-anchor=&quot;middle&quot;&gt;Just b → latch&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;190&quot; y=&quot;134&quot; text-anchor=&quot;middle&quot;&gt;handshake,&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;190&quot; y=&quot;148&quot; text-anchor=&quot;middle&quot;&gt;no tick&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;380&quot; y=&quot;108&quot; text-anchor=&quot;middle&quot;&gt;16 ticks&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;570&quot; y=&quot;108&quot; text-anchor=&quot;middle&quot;&gt;16 ticks&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;380&quot; y=&quot;217&quot; text-anchor=&quot;middle&quot;&gt;stop bit done, 16 ticks → idle&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The transmitter&#39;s eleven states as an FSM. Every transition but one advances on the sixteenth oversample &lt;code&gt;tick&lt;/code&gt; of a bit (plain arrows); the lone accented transition — accepting a byte out of &lt;code&gt;TxIdle&lt;/code&gt; on a &lt;code&gt;Just b&lt;/code&gt; — is driven by the &lt;em&gt;input&lt;/em&gt; and is &lt;em&gt;not&lt;/em&gt; tick-gated, which is what lets the &lt;code&gt;ready&lt;/code&gt; handshake resolve in a single cycle. &lt;code&gt;TxData (Index 8)&lt;/code&gt; packs eight states into one constructor; its self-loop shifts the latched byte right (&lt;code&gt;shiftR&lt;/code&gt;) so &lt;code&gt;lsb&lt;/code&gt; walks it out LSB-first. The line value under each state is the Moore output: a pure function of the state, steady for the whole bit.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;mealy-climbs-to-the-top&quot;&gt;&lt;code&gt;mealy&lt;/code&gt; climbs to the top&lt;/h2&gt;
&lt;p&gt;Here is the line the entire series has been walking toward:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;uartTx tick mbyte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; unbundle &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mealy txStep initTx &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;bundle &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tick&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; mbyte&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;  initTx&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; TxS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  initTx &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxS&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxIdle&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt; gave us the type of &lt;code&gt;mealy&lt;/code&gt; and asked us to hold it:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; mealy :: (s -&amp;gt; i -&amp;gt; (s, o)) -&amp;gt; s -&amp;gt; (Signal dom i -&amp;gt; Signal dom o)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read it slowly against the code. &lt;code&gt;mealy&lt;/code&gt; takes two things — a pure step
function &lt;code&gt;s -&amp;gt; i -&amp;gt; (s, o)&lt;/code&gt; and an initial state &lt;code&gt;s&lt;/code&gt; — and hands back a
&lt;em&gt;function on signals&lt;/em&gt;, &lt;code&gt;Signal dom i -&amp;gt; Signal dom o&lt;/code&gt;. Feed it &lt;code&gt;txStep&lt;/code&gt;
(the step) and &lt;code&gt;initTx&lt;/code&gt; (the initial state) and you get a machine that
consumes a stream of inputs and produces a stream of outputs, with a real
register, clocked, holding the state between cycles. That register is the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator’s&lt;/a&gt; &lt;code&gt;register&lt;/code&gt; seen again — the primer’s line
that “a Mealy machine is nothing but a &lt;code&gt;register&lt;/code&gt; holding the state and a
pure function computing the next one,” now assembled rather than used
bare. &lt;code&gt;initTx = TxS TxIdle 0 0&lt;/code&gt; is the power-up state, the idle phase with
a zeroed shift register and counter; because &lt;code&gt;Dom100&lt;/code&gt; carries no reset
port, that value is set by the flip-flops’ initial state at
configuration, exactly as the baud generator’s &lt;code&gt;register 0&lt;/code&gt; was.&lt;/p&gt;
&lt;p&gt;But &lt;code&gt;mealy&lt;/code&gt; wants a &lt;em&gt;single&lt;/em&gt; input signal and yields a &lt;em&gt;single&lt;/em&gt; output
signal, and our transmitter has two of each — two inputs (&lt;code&gt;tick&lt;/code&gt; and
&lt;code&gt;mbyte&lt;/code&gt;) and two outputs (&lt;code&gt;line&lt;/code&gt; and &lt;code&gt;ready&lt;/code&gt;). That mismatch is what
&lt;code&gt;bundle&lt;/code&gt; and &lt;code&gt;unbundle&lt;/code&gt; are for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bundle (tick, mbyte)&lt;/code&gt; takes the pair of signals
&lt;code&gt;(Signal dom Bool, Signal dom (Maybe (BitVector 8)))&lt;/code&gt; and zips them
into one signal of pairs,
&lt;code&gt;Signal dom (Bool, Maybe (BitVector 8))&lt;/code&gt; — the single &lt;code&gt;Signal dom i&lt;/code&gt;
that &lt;code&gt;mealy&lt;/code&gt; accepts, with &lt;code&gt;i = (Bool, Maybe (BitVector 8))&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unbundle&lt;/code&gt; does the reverse on the way out: the machine produces one
&lt;code&gt;Signal dom (Bit, Bool)&lt;/code&gt;, and &lt;code&gt;unbundle&lt;/code&gt; splits it back into the pair
&lt;code&gt;(Signal dom Bit, Signal dom Bool)&lt;/code&gt; that the signature promises.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The two are witnesses to a small, obvious isomorphism: a &lt;em&gt;signal of
pairs&lt;/em&gt; and a &lt;em&gt;pair of signals&lt;/em&gt; carry the same information, because a
bundle of wires is just several wires side by side, watched over
time.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-bundle-1&quot;&gt;&lt;a href=&quot;#fn-bundle&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; They add no gates; they are pure re-pairing at the
boundary, the adapters that let a two-in, two-out interface meet
&lt;code&gt;mealy&lt;/code&gt;’s one-in, one-out shape. Strip them away and the sentence is
plain: &lt;em&gt;run &lt;code&gt;txStep&lt;/code&gt; as a clocked machine starting from idle.&lt;/em&gt;&lt;/p&gt;
&lt;figure class=&quot;mly-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;mly&quot; viewBox=&quot;0 0 760 300&quot; role=&quot;img&quot; aria-labelledby=&quot;mly-t mly-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;mly-t&quot;&gt;How the mealy combinator lowers uartTx to hardware&lt;/title&gt;
&lt;desc id=&quot;mly-d&quot;&gt;Two input signals, tick and mbyte, enter a bundle block that combines them into a single signal i. Inside a dashed boundary labelled the mealy combinator sit a register holding the state TxS and the pure function txStep. The register feeds the current state s into txStep; txStep computes the next state s prime, drawn as an accented feedback wire returning to the register, and an output o. The output o leaves the mealy boundary into an unbundle block that splits it back into the two output signals line and ready.&lt;/desc&gt;
&lt;style&gt;
.mly{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.mly .box{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.mly .mbox{fill:none;stroke:var(--fg-dim);stroke-width:1.5;stroke-dasharray:6 5}
.mly .wire{stroke:var(--fg-main);stroke-width:2;fill:none}
.mly .fb{stroke:var(--accent);stroke-width:2;fill:none}
.mly text{font-family:var(--sans)}
.mly .name{fill:var(--fg-main);font-family:var(--mono);font-size:14px}
.mly .lab{fill:var(--fg-main);font-size:12px}
.mly .dim{fill:var(--fg-dim);font-size:11.5px}
.mly .mlab{fill:var(--fg-dim);font-family:var(--mono);font-size:12.5px}
.mly .sig{fill:var(--fg-dim);font-family:var(--mono);font-size:12px}
.mly .sigA{fill:var(--accent);font-family:var(--mono);font-size:12px}
.mly .ah{fill:var(--fg-main)}
.mly .aha{fill:var(--accent)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;mly-a&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;mly-aa&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;aha&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;rect class=&quot;mbox&quot; x=&quot;222&quot; y=&quot;64&quot; width=&quot;326&quot; height=&quot;178&quot; rx=&quot;10&quot;/&gt;
&lt;text class=&quot;mlab&quot; x=&quot;385&quot; y=&quot;55&quot; text-anchor=&quot;middle&quot;&gt;mealy txStep initTx&lt;/text&gt;
&lt;rect class=&quot;box&quot; x=&quot;240&quot; y=&quot;144&quot; width=&quot;100&quot; height=&quot;52&quot; rx=&quot;6&quot;/&gt;
&lt;rect class=&quot;box&quot; x=&quot;410&quot; y=&quot;110&quot; width=&quot;120&quot; height=&quot;120&quot; rx=&quot;6&quot;/&gt;
&lt;rect class=&quot;box&quot; x=&quot;235&quot; y=&quot;250&quot; width=&quot;120&quot; height=&quot;36&quot; rx=&quot;6&quot;/&gt;
&lt;rect class=&quot;box&quot; x=&quot;578&quot; y=&quot;144&quot; width=&quot;90&quot; height=&quot;52&quot; rx=&quot;6&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;340&quot; y1=&quot;170&quot; x2=&quot;408&quot; y2=&quot;170&quot; marker-end=&quot;url(#mly-a)&quot;/&gt;
&lt;path class=&quot;fb&quot; d=&quot;M470,110 V84 H290 V142&quot; marker-end=&quot;url(#mly-aa)&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M355,268 H470 V232&quot; marker-end=&quot;url(#mly-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;530&quot; y1=&quot;170&quot; x2=&quot;576&quot; y2=&quot;170&quot; marker-end=&quot;url(#mly-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;60&quot; y1=&quot;260&quot; x2=&quot;233&quot; y2=&quot;260&quot; marker-end=&quot;url(#mly-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;60&quot; y1=&quot;278&quot; x2=&quot;233&quot; y2=&quot;278&quot; marker-end=&quot;url(#mly-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;668&quot; y1=&quot;159&quot; x2=&quot;714&quot; y2=&quot;159&quot; marker-end=&quot;url(#mly-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;668&quot; y1=&quot;181&quot; x2=&quot;714&quot; y2=&quot;181&quot; marker-end=&quot;url(#mly-a)&quot;/&gt;
&lt;text class=&quot;name&quot; x=&quot;290&quot; y=&quot;170&quot; text-anchor=&quot;middle&quot;&gt;TxS&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;290&quot; y=&quot;186&quot; text-anchor=&quot;middle&quot;&gt;init: idle&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;290&quot; y=&quot;212&quot; text-anchor=&quot;middle&quot;&gt;register&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;470&quot; y=&quot;152&quot; text-anchor=&quot;middle&quot;&gt;txStep&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;470&quot; y=&quot;172&quot; text-anchor=&quot;middle&quot;&gt;pure&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;470&quot; y=&quot;198&quot; text-anchor=&quot;middle&quot; style=&quot;font-size:12px&quot;&gt;(s,i)→(s′,o)&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;295&quot; y=&quot;272&quot; text-anchor=&quot;middle&quot;&gt;bundle&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;623&quot; y=&quot;174&quot; text-anchor=&quot;middle&quot;&gt;unbundle&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;374&quot; y=&quot;162&quot; text-anchor=&quot;middle&quot;&gt;s&lt;/text&gt;
&lt;text class=&quot;sigA&quot; x=&quot;380&quot; y=&quot;78&quot; text-anchor=&quot;middle&quot;&gt;s′  (next state)&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;470&quot; y=&quot;248&quot; text-anchor=&quot;middle&quot;&gt;i&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;554&quot; y=&quot;162&quot; text-anchor=&quot;middle&quot;&gt;o&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;52&quot; y=&quot;263&quot; text-anchor=&quot;end&quot;&gt;tick&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;52&quot; y=&quot;281&quot; text-anchor=&quot;end&quot;&gt;mbyte&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;719&quot; y=&quot;163&quot; text-anchor=&quot;start&quot;&gt;line&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;719&quot; y=&quot;185&quot; text-anchor=&quot;start&quot;&gt;ready&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;How &lt;code&gt;mealy&lt;/code&gt; lowers to hardware. The dashed box is exactly what &lt;code&gt;mealy txStep initTx&lt;/code&gt; builds: a &lt;code&gt;register&lt;/code&gt; holding the state &lt;code&gt;TxS&lt;/code&gt; (powering up idle) wired to the pure &lt;code&gt;txStep&lt;/code&gt;, with the next state &lt;code&gt;s′&lt;/code&gt; fed back through the register — the accent loop that makes this sequential rather than a combinational knot. &lt;code&gt;bundle&lt;/code&gt; gathers the two input signals &lt;code&gt;tick&lt;/code&gt; and &lt;code&gt;mbyte&lt;/code&gt; into the single &lt;code&gt;i&lt;/code&gt; the combinator consumes; &lt;code&gt;unbundle&lt;/code&gt; splits the single output back into &lt;code&gt;line&lt;/code&gt; and &lt;code&gt;ready&lt;/code&gt;. The register is the &lt;a href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator&#39;s&lt;/a&gt; &lt;code&gt;register&lt;/code&gt; seen again — the primer&#39;s &quot;a Mealy machine is a register plus a pure function,&quot; assembled.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;What makes this the milestone the primer promised is that &lt;code&gt;mealy&lt;/code&gt; has
finally climbed to the &lt;em&gt;top&lt;/em&gt; of a module — it is the thing &lt;code&gt;uartTx&lt;/code&gt;
returns, the outermost structure of the block, not a helper buried
inside. The CRC was a pure function; the baud generator was a &lt;code&gt;register&lt;/code&gt;
and some arithmetic in a &lt;code&gt;where&lt;/code&gt; block. This is the first Tamal module
whose very shape is “a state machine,” and every remaining block —
the receiver, and the engine that is the whole point of the project —
will have this same silhouette. Learn to read it here and you can read
all of them: find the &lt;code&gt;mealy&lt;/code&gt;, find the step function beside it, and the
rest is detail.&lt;/p&gt;
&lt;h2 id=&quot;txstep-the-pure-transition&quot;&gt;&lt;code&gt;txStep&lt;/code&gt;: the pure transition&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;txStep&lt;/code&gt; is the brain. It is an ordinary, pure, total function — no
&lt;code&gt;Signal&lt;/code&gt;, no clock, no simulator — and that is exactly what the
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;introduction post&lt;/a&gt; meant when it said the tests run in under a
second: you can hammer a function like this with thousands of inputs in
milliseconds, and only once you trust it does &lt;code&gt;mealy&lt;/code&gt; make it hardware.
Here it is again, on its own:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txStep&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; TxS&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Bool&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;TxS&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Bit&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;txStep s &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tick&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; mbyte&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; ready&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ready &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; txState s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxIdle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  line &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; txState s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    TxIdle&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; high&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    TxStart&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; low&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    TxData&lt;/span&gt;&lt;span&gt; _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; lsb &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;txShift s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    TxStop&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; high&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  s&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxIdle&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;-&lt;/span&gt;&lt;span&gt; txState s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; mbyte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;        Just&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxStart&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txShift&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;        Nothing&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; not tick &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; txCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;/=&lt;/span&gt;&lt;span&gt; maxBound &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; txCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; txAdvance s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Look first at the &lt;em&gt;type&lt;/em&gt;, because it is the primer’s promise made
literal:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txStep&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; TxS&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Bool&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;TxS&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Bit&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is &lt;code&gt;s -&amp;gt; i -&amp;gt; (s, o)&lt;/code&gt; exactly, with &lt;code&gt;s = TxS&lt;/code&gt;,
&lt;code&gt;i = (Bool, Maybe (BitVector 8))&lt;/code&gt;, and &lt;code&gt;o = (Bit, Bool)&lt;/code&gt;. Current state
and input on the left; next state and output on the right. This is the
shape &lt;code&gt;mealy&lt;/code&gt; demands and the shape the primer told you to look for. The
whole clocked machine is this one function with a register wrapped around
it, and this function is where all the behaviour lives.&lt;/p&gt;
&lt;p&gt;The body splits cleanly into the two halves of the pair it returns: the
&lt;strong&gt;outputs&lt;/strong&gt; &lt;code&gt;(line, ready)&lt;/code&gt;, and the &lt;strong&gt;next state&lt;/strong&gt; &lt;code&gt;s&#39;&lt;/code&gt;. Read them
separately, because they are where the Mealy-versus-Moore question is
decided.&lt;/p&gt;
&lt;h3 id=&quot;the-outputs-are-moore&quot;&gt;The outputs are Moore&lt;/h3&gt;
&lt;p&gt;Both outputs are computed from &lt;code&gt;s&lt;/code&gt; alone. &lt;code&gt;ready&lt;/code&gt; is literally
&lt;code&gt;txState s == TxIdle&lt;/code&gt; — a comparison against the state, nothing else in
sight. &lt;code&gt;line&lt;/code&gt; is a &lt;code&gt;case&lt;/code&gt; on &lt;code&gt;txState s&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  line &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; txState s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    TxIdle&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; high      &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; idle line sits high&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    TxStart&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; low      &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; start bit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    TxData&lt;/span&gt;&lt;span&gt; _ &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; lsb &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;txShift s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; current data bit, LSB-first&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    TxStop&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; high      &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; stop bit&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Idle and stop drive &lt;code&gt;high&lt;/code&gt;, start drives &lt;code&gt;low&lt;/code&gt;, and a data bit drives
&lt;code&gt;lsb (txShift s)&lt;/code&gt; — the least-significant bit of the latched, shifted
byte. Every arm reads only the state (and &lt;code&gt;txShift&lt;/code&gt;, which &lt;em&gt;is&lt;/em&gt; part of
the state). Neither &lt;code&gt;tick&lt;/code&gt; nor &lt;code&gt;mbyte&lt;/code&gt; — the inputs — appears
anywhere in &lt;code&gt;line&lt;/code&gt; or &lt;code&gt;ready&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That is the definition of a &lt;strong&gt;Moore&lt;/strong&gt; output: a function of the current
state only.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-mealymoore-1&quot;&gt;&lt;a href=&quot;#fn-mealymoore&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt; And it has a consequence you can see on a scope.
Because &lt;code&gt;line&lt;/code&gt; ignores the input and depends only on &lt;code&gt;txState&lt;/code&gt; and
&lt;code&gt;txShift&lt;/code&gt;, and because neither of those changes except at a bit boundary,
the line is &lt;em&gt;rock-steady for the whole sixteen-tick bit&lt;/em&gt; — it cannot
twitch when a byte is offered, cannot glitch when the tick fires, cannot
do anything but sit at its level until the state moves. For a wire whose
entire job is to be sampled dead-center by a receiver sixteen ticks
later, that stability is not an accident; it is the reason to make the
outputs Moore.&lt;/p&gt;
&lt;p&gt;So where is the Mealy?&lt;/p&gt;
&lt;h3 id=&quot;the-next-state-and-the-one-place-the-input-reaches-in&quot;&gt;The next state, and the one place the input reaches in&lt;/h3&gt;
&lt;p&gt;The next-state block is a chain of guards — boolean tests tried top to
bottom, first match wins:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  s&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxIdle&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;-&lt;/span&gt;&lt;span&gt; txState s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; mbyte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;        Just&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxStart&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txShift&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;        Nothing&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; not tick &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; txCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;/=&lt;/span&gt;&lt;span&gt; maxBound &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; txCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; txAdvance s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first guard is a &lt;strong&gt;pattern guard&lt;/strong&gt;: &lt;code&gt;TxIdle &amp;lt;- txState s&lt;/code&gt; succeeds
when the state matches &lt;code&gt;TxIdle&lt;/code&gt;. So the whole first arm is “if we are
idle.” And notice what it does — and does &lt;em&gt;not&lt;/em&gt; do. It looks at
&lt;code&gt;mbyte&lt;/code&gt;, the input. If a byte is offered (&lt;code&gt;Just b&lt;/code&gt;), it latches that byte
into &lt;code&gt;txShift&lt;/code&gt;, resets the counter, and moves to &lt;code&gt;TxStart&lt;/code&gt;, all &lt;em&gt;this
cycle&lt;/em&gt;. If nothing is offered (&lt;code&gt;Nothing&lt;/code&gt;), it stays idle. Crucially,
there is no mention of &lt;code&gt;tick&lt;/code&gt; in this arm: &lt;strong&gt;accepting a byte is not
tick-gated.&lt;/strong&gt; The instant a &lt;code&gt;Just b&lt;/code&gt; arrives while idle, the machine
takes it, regardless of where the oversample tick happens to be in its
cadence.&lt;/p&gt;
&lt;p&gt;That is the one place the live input reaches into the logic, and it is
the whole reason the interface is a &lt;em&gt;handshake&lt;/em&gt;. &lt;code&gt;ready&lt;/code&gt; is high (because
we are idle), the caller sees it and presents &lt;code&gt;Just b&lt;/code&gt;, and the byte is
absorbed on that very cycle rather than waiting up to sixteen ticks for
the next tick edge. It is a genuine same-cycle dependence of the
transition on the input — the ingredient that makes the general &lt;code&gt;mealy&lt;/code&gt;
combinator the right tool rather than its Moore-only sibling.&lt;/p&gt;
&lt;p&gt;Here is the subtlety worth being precise about, because it is easy to
overstate. A dependence of the &lt;em&gt;next state&lt;/em&gt; on the input is &lt;strong&gt;not&lt;/strong&gt; what
makes a machine Mealy — a Moore machine’s next-state function takes the
input too; that is how any state machine responds to anything. What
distinguishes Mealy from Moore is strictly whether the &lt;em&gt;output&lt;/em&gt; depends
on the input. By that test, &lt;code&gt;txStep&lt;/code&gt;’s outputs are Moore, full stop.
What the un-tick-gated handshake buys is not a Mealy &lt;em&gt;output&lt;/em&gt; but a
&lt;em&gt;same-cycle transition&lt;/em&gt; — the machine changes state in response to the
input without waiting for its own cadence, so that the &lt;code&gt;ready&lt;/code&gt; handshake
can complete in a single cycle. The module is, honestly stated, a
&lt;strong&gt;Moore machine lifted through the &lt;code&gt;mealy&lt;/code&gt; combinator&lt;/strong&gt;, with one
input-driven, un-clocked-cadence transition out of idle.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-whymealy-1&quot;&gt;&lt;a href=&quot;#fn-whymealy&quot;&gt;[5]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;figure class=&quot;mvm-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;mvm&quot; viewBox=&quot;0 0 760 300&quot; role=&quot;img&quot; aria-labelledby=&quot;mvm-t mvm-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;mvm-t&quot;&gt;Moore versus Mealy outputs&lt;/title&gt;
&lt;desc id=&quot;mvm-d&quot;&gt;Two side-by-side machines. On the left, a Moore machine: a state register feeds output logic g, whose output depends on the state only; the input arrow enters the register (next state) and does not reach the output logic. On the right, a Mealy machine: the same layout, but an accented dashed wire taps the input up into the output logic as well, so the output depends on both state and input. The caption notes that txStep&#39;s outputs, line and ready, sit on the Moore side: no input reaches them.&lt;/desc&gt;
&lt;style&gt;
.mvm{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.mvm .box{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.mvm .wire{stroke:var(--fg-main);stroke-width:2;fill:none}
.mvm .tap{stroke:var(--accent);stroke-width:2;fill:none;stroke-dasharray:5 4}
.mvm .div{stroke:var(--fg-dim);stroke-width:1.5;stroke-dasharray:4 5;fill:none}
.mvm text{font-family:var(--sans)}
.mvm .title{fill:var(--fg-main);font-size:16px;font-weight:600}
.mvm .name{fill:var(--fg-main);font-size:13px}
.mvm .form{fill:var(--fg-main);font-family:var(--mono);font-size:12.5px}
.mvm .formA{fill:var(--accent);font-family:var(--mono);font-size:12.5px}
.mvm .dim{fill:var(--fg-dim);font-size:11.5px}
.mvm .sig{fill:var(--fg-dim);font-family:var(--mono);font-size:12px}
.mvm .note{fill:var(--accent);font-size:12px}
.mvm .ah{fill:var(--fg-main)}
.mvm .aha{fill:var(--accent)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;mvm-a&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;mvm-aa&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;aha&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;line class=&quot;div&quot; x1=&quot;382&quot; y1=&quot;40&quot; x2=&quot;382&quot; y2=&quot;266&quot;/&gt;
&lt;text class=&quot;title&quot; x=&quot;190&quot; y=&quot;52&quot; text-anchor=&quot;middle&quot;&gt;Moore&lt;/text&gt;
&lt;rect class=&quot;box&quot; x=&quot;55&quot; y=&quot;95&quot; width=&quot;95&quot; height=&quot;50&quot; rx=&quot;6&quot;/&gt;
&lt;rect class=&quot;box&quot; x=&quot;215&quot; y=&quot;95&quot; width=&quot;112&quot; height=&quot;50&quot; rx=&quot;6&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;150&quot; y1=&quot;120&quot; x2=&quot;213&quot; y2=&quot;120&quot; marker-end=&quot;url(#mvm-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;327&quot; y1=&quot;120&quot; x2=&quot;357&quot; y2=&quot;120&quot; marker-end=&quot;url(#mvm-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;102&quot; y1=&quot;232&quot; x2=&quot;102&quot; y2=&quot;147&quot; marker-end=&quot;url(#mvm-a)&quot;/&gt;
&lt;text class=&quot;name&quot; x=&quot;102&quot; y=&quot;116&quot; text-anchor=&quot;middle&quot;&gt;state&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;102&quot; y=&quot;133&quot; text-anchor=&quot;middle&quot;&gt;register&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;271&quot; y=&quot;114&quot; text-anchor=&quot;middle&quot;&gt;output g&lt;/text&gt;
&lt;text class=&quot;form&quot; x=&quot;271&quot; y=&quot;133&quot; text-anchor=&quot;middle&quot;&gt;g(state)&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;181&quot; y=&quot;112&quot; text-anchor=&quot;middle&quot;&gt;state&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;348&quot; y=&quot;112&quot; text-anchor=&quot;middle&quot;&gt;o&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;102&quot; y=&quot;248&quot; text-anchor=&quot;middle&quot;&gt;input&lt;/text&gt;
&lt;text class=&quot;note&quot; x=&quot;190&quot; y=&quot;283&quot; text-anchor=&quot;middle&quot;&gt;txStep: line, ready live here&lt;/text&gt;
&lt;text class=&quot;title&quot; x=&quot;575&quot; y=&quot;52&quot; text-anchor=&quot;middle&quot;&gt;Mealy&lt;/text&gt;
&lt;rect class=&quot;box&quot; x=&quot;440&quot; y=&quot;95&quot; width=&quot;95&quot; height=&quot;50&quot; rx=&quot;6&quot;/&gt;
&lt;rect class=&quot;box&quot; x=&quot;600&quot; y=&quot;95&quot; width=&quot;112&quot; height=&quot;50&quot; rx=&quot;6&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;535&quot; y1=&quot;120&quot; x2=&quot;598&quot; y2=&quot;120&quot; marker-end=&quot;url(#mvm-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;712&quot; y1=&quot;120&quot; x2=&quot;742&quot; y2=&quot;120&quot; marker-end=&quot;url(#mvm-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;487&quot; y1=&quot;232&quot; x2=&quot;487&quot; y2=&quot;147&quot; marker-end=&quot;url(#mvm-a)&quot;/&gt;
&lt;path class=&quot;tap&quot; d=&quot;M487,205 H656 V147&quot; marker-end=&quot;url(#mvm-aa)&quot;/&gt;
&lt;circle class=&quot;aha&quot; cx=&quot;487&quot; cy=&quot;205&quot; r=&quot;3.5&quot;/&gt;
&lt;text class=&quot;name&quot; x=&quot;487&quot; y=&quot;116&quot; text-anchor=&quot;middle&quot;&gt;state&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;487&quot; y=&quot;133&quot; text-anchor=&quot;middle&quot;&gt;register&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;656&quot; y=&quot;114&quot; text-anchor=&quot;middle&quot;&gt;output g&lt;/text&gt;
&lt;text class=&quot;formA&quot; x=&quot;656&quot; y=&quot;133&quot; text-anchor=&quot;middle&quot;&gt;g(state, in)&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;566&quot; y=&quot;112&quot; text-anchor=&quot;middle&quot;&gt;state&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;733&quot; y=&quot;112&quot; text-anchor=&quot;middle&quot;&gt;o&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;487&quot; y=&quot;248&quot; text-anchor=&quot;middle&quot;&gt;input&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;575&quot; y=&quot;283&quot; text-anchor=&quot;middle&quot;&gt;txStep has no input → output tap&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The sole distinction between the two machine styles. In a &lt;strong&gt;Moore&lt;/strong&gt; machine the output is a function of the state alone; the input reaches only the next-state logic inside the register. In a &lt;strong&gt;Mealy&lt;/strong&gt; machine an extra path (accent) taps the input into the output too, so the output can change the moment the input does. By this test &lt;code&gt;txStep&lt;/code&gt;&#39;s &lt;code&gt;line&lt;/code&gt; and &lt;code&gt;ready&lt;/code&gt; are Moore — neither reads &lt;code&gt;tick&lt;/code&gt; or &lt;code&gt;mbyte&lt;/code&gt; — even though the block is lifted with the general &lt;code&gt;mealy&lt;/code&gt; combinator. What the handshake adds is a same-cycle &lt;em&gt;next-state&lt;/em&gt; edge, not an input-driven output.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;the-cadence-freeze-count-advance&quot;&gt;The cadence: freeze, count, advance&lt;/h3&gt;
&lt;p&gt;Once we are past the idle arm — that is, once the machine is actually
transmitting — the remaining three guards are the bit-timing, and every
one of them is gated on the tick:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; not tick &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s                        &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; between ticks: hold everything&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; txCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;/=&lt;/span&gt;&lt;span&gt; maxBound &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; txCnt s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;  --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; within a bit: count&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; txAdvance s             &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; 16th tick of the bit: next phase&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;not tick = s&lt;/code&gt; is the freeze: on any cycle where the oversample tick is
low — which is most of them, since at 2 Mbaud the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud
generator&lt;/a&gt; fires the tick only about once every 3.125 clocks, so
the machine sits frozen roughly two cycles in every three — the state is
returned unchanged. The transmitter does nothing between ticks. This is
the enable in action: the baud generator hands out permission to move,
and off a tick the machine simply declines to.&lt;/p&gt;
&lt;p&gt;On a tick, the next guard asks whether the bit is done. &lt;code&gt;txCnt&lt;/code&gt; counts
&lt;code&gt;0..15&lt;/code&gt;; while it has not yet reached &lt;code&gt;maxBound&lt;/code&gt; (15) we increment it and
stay in the same phase — another tick of the same bit. The guard order
is doing quiet safety work here: &lt;code&gt;txCnt s + 1&lt;/code&gt; is bounded arithmetic on
an &lt;code&gt;Index 16&lt;/code&gt;, and adding one to &lt;code&gt;maxBound&lt;/code&gt; would be an overflow, but we
only reach the increment when &lt;code&gt;txCnt s /= maxBound&lt;/code&gt;, so the &lt;code&gt;+ 1&lt;/code&gt; can
never run off the end. The type forbids the illegal value and the guard
order guarantees we never ask for it — belt and suspenders, both
supplied by the primer’s “numbers can live in types.”&lt;/p&gt;
&lt;p&gt;When the tick fires and &lt;code&gt;txCnt&lt;/code&gt; &lt;em&gt;has&lt;/em&gt; reached 15 — the sixteenth tick of
the bit — the &lt;code&gt;otherwise&lt;/code&gt; arm calls &lt;code&gt;txAdvance&lt;/code&gt;, which moves the frame
to its next phase and resets the counter to begin a new bit. Sixteen
ticks per bit, exactly, counted off by an &lt;code&gt;Index 16&lt;/code&gt; rolling from 0 to
its maximum. That is how “hold each level for sixteen oversample ticks”
is written.&lt;/p&gt;
&lt;p&gt;Step back and the shape of the whole step is visible: two Moore outputs
read straight off the state, and a next-state function that is idle-driven
by the input (the handshake) and otherwise tick-driven by the counter
(the cadence). One function, two clean halves.&lt;/p&gt;
&lt;figure class=&quot;dp-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;dp&quot; viewBox=&quot;0 0 760 300&quot; role=&quot;img&quot; aria-labelledby=&quot;dp-t dp-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;dp-t&quot;&gt;The internal datapath of one txStep&lt;/title&gt;
&lt;desc id=&quot;dp-d&quot;&gt;A register holding the state s fans out to three consumers. The top consumer is a comparator txState s equals TxIdle producing the ready output. The middle consumer is a case on txState s producing the line output. Both read the state only and are Moore outputs. The bottom consumer is the next-state logic, which additionally takes the tick input (gating the cadence: freeze, count, advance) and the mbyte input (accent, driving the idle handshake); it produces the next state s prime, which feeds back to the register.&lt;/desc&gt;
&lt;style&gt;
.dp{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.dp .box{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.dp .obox{fill:var(--bg-main);stroke:var(--fg-main);stroke-width:2}
.dp .wire{stroke:var(--fg-main);stroke-width:2;fill:none}
.dp .fb{stroke:var(--accent);stroke-width:2;fill:none}
.dp .in{stroke:var(--accent);stroke-width:2;fill:none}
.dp text{font-family:var(--sans)}
.dp .name{fill:var(--fg-main);font-size:12.5px}
.dp .form{fill:var(--fg-main);font-family:var(--mono);font-size:12.5px}
.dp .dim{fill:var(--fg-dim);font-size:11px}
.dp .sig{fill:var(--fg-dim);font-family:var(--mono);font-size:12px}
.dp .sigA{fill:var(--accent);font-family:var(--mono);font-size:12px}
.dp .ah{fill:var(--fg-main)}
.dp .aha{fill:var(--accent)}
.dp .node{fill:var(--fg-main)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;dp-a&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;dp-aa&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;aha&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;rect class=&quot;box&quot; x=&quot;40&quot; y=&quot;123&quot; width=&quot;105&quot; height=&quot;54&quot; rx=&quot;6&quot;/&gt;
&lt;rect class=&quot;obox&quot; x=&quot;290&quot; y=&quot;52&quot; width=&quot;205&quot; height=&quot;42&quot; rx=&quot;4&quot;/&gt;
&lt;rect class=&quot;obox&quot; x=&quot;290&quot; y=&quot;129&quot; width=&quot;205&quot; height=&quot;42&quot; rx=&quot;4&quot;/&gt;
&lt;rect class=&quot;box&quot; x=&quot;290&quot; y=&quot;203&quot; width=&quot;205&quot; height=&quot;62&quot; rx=&quot;6&quot;/&gt;
&lt;circle class=&quot;node&quot; cx=&quot;175&quot; cy=&quot;150&quot; r=&quot;3.5&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;145&quot; y1=&quot;150&quot; x2=&quot;175&quot; y2=&quot;150&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M175,150 V73 H288&quot; marker-end=&quot;url(#dp-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;175&quot; y1=&quot;150&quot; x2=&quot;288&quot; y2=&quot;150&quot; marker-end=&quot;url(#dp-a)&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M175,150 V214 H288&quot; marker-end=&quot;url(#dp-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;495&quot; y1=&quot;73&quot; x2=&quot;700&quot; y2=&quot;73&quot; marker-end=&quot;url(#dp-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;495&quot; y1=&quot;150&quot; x2=&quot;700&quot; y2=&quot;150&quot; marker-end=&quot;url(#dp-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;72&quot; y1=&quot;230&quot; x2=&quot;288&quot; y2=&quot;230&quot; marker-end=&quot;url(#dp-a)&quot;/&gt;
&lt;line class=&quot;in&quot; x1=&quot;72&quot; y1=&quot;248&quot; x2=&quot;288&quot; y2=&quot;248&quot; marker-end=&quot;url(#dp-aa)&quot;/&gt;
&lt;path class=&quot;fb&quot; d=&quot;M495,234 H520 V286 H28 V150 H38&quot; marker-end=&quot;url(#dp-aa)&quot;/&gt;
&lt;text class=&quot;form&quot; x=&quot;92&quot; y=&quot;147&quot; text-anchor=&quot;middle&quot;&gt;s : TxS&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;92&quot; y=&quot;163&quot; text-anchor=&quot;middle&quot;&gt;register&lt;/text&gt;
&lt;text class=&quot;form&quot; x=&quot;392&quot; y=&quot;77&quot; text-anchor=&quot;middle&quot;&gt;txState s == TxIdle&lt;/text&gt;
&lt;text class=&quot;form&quot; x=&quot;392&quot; y=&quot;154&quot; text-anchor=&quot;middle&quot;&gt;case txState s of …&lt;/text&gt;
&lt;text class=&quot;name&quot; x=&quot;392&quot; y=&quot;226&quot; text-anchor=&quot;middle&quot;&gt;next-state logic&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;392&quot; y=&quot;245&quot; text-anchor=&quot;middle&quot;&gt;handshake · count · advance&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;162&quot; y=&quot;143&quot; text-anchor=&quot;end&quot;&gt;s&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;706&quot; y=&quot;77&quot; text-anchor=&quot;start&quot;&gt;ready&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;706&quot; y=&quot;154&quot; text-anchor=&quot;start&quot;&gt;line&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;66&quot; y=&quot;233&quot; text-anchor=&quot;end&quot;&gt;tick&lt;/text&gt;
&lt;text class=&quot;sigA&quot; x=&quot;66&quot; y=&quot;251&quot; text-anchor=&quot;end&quot;&gt;mbyte&lt;/text&gt;
&lt;text class=&quot;sigA&quot; x=&quot;505&quot; y=&quot;228&quot; text-anchor=&quot;start&quot;&gt;s′&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;610&quot; y=&quot;112&quot; text-anchor=&quot;middle&quot;&gt;state-only (Moore)&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;Inside one &lt;code&gt;txStep&lt;/code&gt;: the state register &lt;code&gt;s&lt;/code&gt; fans out to three consumers. Two are the Moore outputs — &lt;code&gt;ready&lt;/code&gt;, a bare comparison &lt;code&gt;txState s == TxIdle&lt;/code&gt;, and &lt;code&gt;line&lt;/code&gt;, a &lt;code&gt;case&lt;/code&gt; on the state — and neither reads an input, so both are steady functions of &lt;code&gt;s&lt;/code&gt;. The third is the next-state logic, the &lt;em&gt;only&lt;/em&gt; place the inputs enter: &lt;code&gt;tick&lt;/code&gt; gates the cadence (freeze off-tick, count to sixteen, then advance) and &lt;code&gt;mbyte&lt;/code&gt; (accent) drives the un-gated idle handshake. Its result &lt;code&gt;s′&lt;/code&gt; is the value the register latches for the next cycle.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;txadvance-walking-the-frame&quot;&gt;&lt;code&gt;txAdvance&lt;/code&gt;: walking the frame&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;txAdvance&lt;/code&gt; is called on exactly the ticks that end a bit, and its job is
to move the phase forward:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txAdvance&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; TxS&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; TxS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;txAdvance s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; txState s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  TxStart&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxData&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  TxData&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; maxBound &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxStop&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxData&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;i &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txShift&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; txShift s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;shiftR&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  TxStop&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;txState&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; TxIdle&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; txCnt&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  TxIdle&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Trace the phases in order and it is the whole 8N1 frame:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;TxStart -&amp;gt; TxData 0&lt;/code&gt;.&lt;/strong&gt; The start bit is finished; begin the first
data bit. Note what is &lt;em&gt;not&lt;/em&gt; touched: &lt;code&gt;txShift&lt;/code&gt; is left exactly as it
was latched, so the first data bit driven is &lt;code&gt;lsb&lt;/code&gt; of the original
byte — bit 0, the least-significant bit. UART is &lt;strong&gt;LSB-first&lt;/strong&gt;, and
this is where that convention is set.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;TxData i&lt;/code&gt;, when &lt;code&gt;i&lt;/code&gt; is not &lt;code&gt;maxBound&lt;/code&gt;.&lt;/strong&gt; Move to &lt;code&gt;TxData (i + 1)&lt;/code&gt;
&lt;em&gt;and&lt;/em&gt; shift the latched byte right by one:
&lt;code&gt;txShift = txShift s `shiftR` 1&lt;/code&gt;. The right shift slides the next bit
down into the &lt;code&gt;lsb&lt;/code&gt; position, so that when &lt;code&gt;line&lt;/code&gt; reads
&lt;code&gt;lsb (txShift s)&lt;/code&gt; during the next data phase, it presents bit &lt;code&gt;i + 1&lt;/code&gt;.
The byte walks out of the bottom of the shift register, one bit per
data phase, least-significant first.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;TxData i&lt;/code&gt;, when &lt;code&gt;i == maxBound&lt;/code&gt;.&lt;/strong&gt; Bit 7 is done; there is no bit 8
(the &lt;code&gt;Index 8&lt;/code&gt; guaranteed that), so go to &lt;code&gt;TxStop&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;TxStop -&amp;gt; TxIdle&lt;/code&gt;.&lt;/strong&gt; The stop bit is finished; return to idle,
where &lt;code&gt;ready&lt;/code&gt; goes high again and the next byte can be accepted.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every arm also resets &lt;code&gt;txCnt&lt;/code&gt; to 0, because each new phase gets its own
fresh count of sixteen ticks.&lt;/p&gt;
&lt;p&gt;Two details reward a second look. The first is that the data bits are
serialised with the &lt;em&gt;same&lt;/em&gt; &lt;code&gt;txShift&lt;/code&gt; register the outputs read — there
is no separate bit index driving a multiplexer over the byte. &lt;code&gt;line&lt;/code&gt;
reads the bottom bit; &lt;code&gt;txAdvance&lt;/code&gt; shifts a new bottom bit into place at
each boundary; between them the byte is dealt out like cards off the
bottom of a deck. It is the CRC post’s shift register seen from the other
direction: there the byte was shifted &lt;em&gt;left&lt;/em&gt;, MSB-first, feeding a
polynomial division; here it is shifted &lt;em&gt;right&lt;/em&gt;, LSB-first, feeding a
wire. Same primitive, opposite convention, because eSPI’s CRC is defined
MSB-first and a UART frame is defined LSB-first — two standards, two
directions, one &lt;code&gt;shiftR&lt;/code&gt;/&lt;code&gt;shiftL&lt;/code&gt; apart.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-lsbfirst-1&quot;&gt;&lt;a href=&quot;#fn-lsbfirst&quot;&gt;[6]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The second is that final arm, &lt;code&gt;TxIdle -&amp;gt; s&lt;/code&gt;. It can never actually run:
&lt;code&gt;txAdvance&lt;/code&gt; is only ever reached from &lt;code&gt;txStep&lt;/code&gt;’s &lt;code&gt;otherwise&lt;/code&gt; guard, and
that guard is only reached &lt;em&gt;after&lt;/em&gt; the idle case has already been handled
by the first pattern guard. If the state is &lt;code&gt;TxIdle&lt;/code&gt;, control never gets
as far as &lt;code&gt;txAdvance&lt;/code&gt;. So why write the arm at all? Because a &lt;code&gt;case&lt;/code&gt; over
&lt;code&gt;TxState&lt;/code&gt; that omits &lt;code&gt;TxIdle&lt;/code&gt; is non-exhaustive, and the compiler will
say so — the primer’s exhaustiveness check again, insisting that every
constructor be handled even when the programmer can prove one is
dead.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-exhaustive-1&quot;&gt;&lt;a href=&quot;#fn-exhaustive&quot;&gt;[7]&lt;/a&gt;&lt;/sup&gt;
Returning &lt;code&gt;s&lt;/code&gt; unchanged is the honest way to satisfy it: if we somehow
&lt;em&gt;were&lt;/em&gt; idle here, doing nothing is the only safe answer. A total function
has no holes, even the unreachable ones.&lt;/p&gt;
&lt;figure class=&quot;wf-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;wf&quot; viewBox=&quot;0 0 760 200&quot; role=&quot;img&quot; aria-labelledby=&quot;wf-t wf-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;wf-t&quot;&gt;The 8N1 line waveform for one transmitted byte&lt;/title&gt;
&lt;desc id=&quot;wf-d&quot;&gt;The serial line over time for the byte 0x4B. It idles high, drops low for one start bit, then drives the eight data bits least-significant-bit first (1, 1, 0, 1, 0, 0, 1, 0), returns high for one stop bit, and idles high again. Each cell is held for sixteen oversample ticks. Accent dots mark the bit centers where a receiver samples.&lt;/desc&gt;
&lt;style&gt;
.wf{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.wf .line{stroke:var(--fg-main);stroke-width:2.5;fill:none}
.wf .startseg{stroke:var(--accent);stroke-width:2.5;fill:none}
.wf .guide{stroke:var(--fg-dim);stroke-width:1;fill:none;stroke-dasharray:3 4}
.wf .brace{stroke:var(--fg-dim);stroke-width:1.5;fill:none}
.wf .lsb{stroke:var(--fg-dim);stroke-width:1.5;fill:none}
.wf .dot{fill:var(--accent)}
.wf text{font-family:var(--sans)}
.wf .lab{fill:var(--fg-main);font-size:13px}
.wf .dim{fill:var(--fg-dim);font-size:11px}
.wf .acc{fill:var(--accent);font-size:11px}
.wf .sig{fill:var(--fg-dim);font-family:var(--mono);font-size:12px}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;wf-d&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; fill=&quot;var(--fg-dim)&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;text class=&quot;lab&quot; x=&quot;382&quot; y=&quot;30&quot; text-anchor=&quot;middle&quot;&gt;byte = 0x4B  (&#39;K&#39;)&lt;/text&gt;
&lt;line class=&quot;guide&quot; x1=&quot;122&quot; y1=&quot;58&quot; x2=&quot;122&quot; y2=&quot;142&quot;/&gt;
&lt;line class=&quot;guide&quot; x1=&quot;174&quot; y1=&quot;58&quot; x2=&quot;174&quot; y2=&quot;142&quot;/&gt;
&lt;line class=&quot;guide&quot; x1=&quot;590&quot; y1=&quot;58&quot; x2=&quot;590&quot; y2=&quot;142&quot;/&gt;
&lt;line class=&quot;guide&quot; x1=&quot;642&quot; y1=&quot;58&quot; x2=&quot;642&quot; y2=&quot;142&quot;/&gt;
&lt;polyline class=&quot;line&quot; points=&quot;70,70 122,70 122,130 174,130 174,70 278,70 278,130 330,130 330,70 382,70 382,130 486,130 486,70 538,70 538,130 590,130 590,70 694,70&quot;/&gt;
&lt;polyline class=&quot;startseg&quot; points=&quot;122,70 122,130 174,130 174,70&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;148&quot; cy=&quot;130&quot; r=&quot;2.5&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;200&quot; cy=&quot;70&quot; r=&quot;2.5&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;252&quot; cy=&quot;70&quot; r=&quot;2.5&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;304&quot; cy=&quot;130&quot; r=&quot;2.5&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;356&quot; cy=&quot;70&quot; r=&quot;2.5&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;408&quot; cy=&quot;130&quot; r=&quot;2.5&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;460&quot; cy=&quot;130&quot; r=&quot;2.5&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;512&quot; cy=&quot;70&quot; r=&quot;2.5&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;564&quot; cy=&quot;130&quot; r=&quot;2.5&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;616&quot; cy=&quot;70&quot; r=&quot;2.5&quot;/&gt;
&lt;text class=&quot;sig&quot; x=&quot;60&quot; y=&quot;74&quot; text-anchor=&quot;end&quot;&gt;1&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;60&quot; y=&quot;134&quot; text-anchor=&quot;end&quot;&gt;0&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;96&quot; y=&quot;156&quot; text-anchor=&quot;middle&quot;&gt;idle&lt;/text&gt;
&lt;text class=&quot;acc&quot; x=&quot;148&quot; y=&quot;156&quot; text-anchor=&quot;middle&quot;&gt;start&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;200&quot; y=&quot;156&quot; text-anchor=&quot;middle&quot; style=&quot;font-size:11px&quot;&gt;b0&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;252&quot; y=&quot;156&quot; text-anchor=&quot;middle&quot; style=&quot;font-size:11px&quot;&gt;b1&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;304&quot; y=&quot;156&quot; text-anchor=&quot;middle&quot; style=&quot;font-size:11px&quot;&gt;b2&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;356&quot; y=&quot;156&quot; text-anchor=&quot;middle&quot; style=&quot;font-size:11px&quot;&gt;b3&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;408&quot; y=&quot;156&quot; text-anchor=&quot;middle&quot; style=&quot;font-size:11px&quot;&gt;b4&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;460&quot; y=&quot;156&quot; text-anchor=&quot;middle&quot; style=&quot;font-size:11px&quot;&gt;b5&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;512&quot; y=&quot;156&quot; text-anchor=&quot;middle&quot; style=&quot;font-size:11px&quot;&gt;b6&lt;/text&gt;
&lt;text class=&quot;lab&quot; x=&quot;564&quot; y=&quot;156&quot; text-anchor=&quot;middle&quot; style=&quot;font-size:11px&quot;&gt;b7&lt;/text&gt;
&lt;text class=&quot;acc&quot; x=&quot;616&quot; y=&quot;156&quot; text-anchor=&quot;middle&quot;&gt;stop&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;668&quot; y=&quot;156&quot; text-anchor=&quot;middle&quot;&gt;idle&lt;/text&gt;
&lt;path class=&quot;brace&quot; d=&quot;M122,167 V173 H174 V167&quot;/&gt;
&lt;text class=&quot;dim&quot; x=&quot;148&quot; y=&quot;187&quot; text-anchor=&quot;middle&quot;&gt;16 ticks&lt;/text&gt;
&lt;line class=&quot;lsb&quot; x1=&quot;180&quot; y1=&quot;172&quot; x2=&quot;584&quot; y2=&quot;172&quot; marker-end=&quot;url(#wf-d)&quot;/&gt;
&lt;text class=&quot;dim&quot; x=&quot;382&quot; y=&quot;187&quot; text-anchor=&quot;middle&quot;&gt;data bits, LSB-first&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The &lt;code&gt;line&lt;/code&gt; waveform for one byte — ASCII &lt;code&gt;&#39;K&#39;&lt;/code&gt;, &lt;code&gt;0x4B&lt;/code&gt; = &lt;code&gt;0b0100_1011&lt;/code&gt;. The frame is idle-high, one accent &lt;strong&gt;start&lt;/strong&gt; bit (low), the eight data bits &lt;strong&gt;least-significant first&lt;/strong&gt; (so &lt;code&gt;0b0100_1011&lt;/code&gt; leaves as &lt;code&gt;1 1 0 1 0 0 1 0&lt;/code&gt;), one &lt;strong&gt;stop&lt;/strong&gt; bit (high), then idle. Each cell is held for sixteen oversample ticks. The accent dots mark the bit centers, where the receiver samples: because &lt;code&gt;line&lt;/code&gt; is a Moore output it is dead-steady across the whole cell, so the sample always lands on solid ground. Note that the stop bit and the idle line sit at the same level — a frame is delimited by &lt;em&gt;counting&lt;/em&gt;, not by a distinct symbol.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The waveform above is the entire module, seen from outside: idle high,
one low start bit, eight data bits with the byte’s least-significant bit
leading, one high stop bit, and back to idle. Ten frame cells, each held
for sixteen ticks, walked out by the ten states from &lt;code&gt;TxStart&lt;/code&gt; through
&lt;code&gt;TxStop&lt;/code&gt; — with &lt;code&gt;TxIdle&lt;/code&gt; the resting level either side, held not for
sixteen ticks but for however long it takes the next byte to arrive. If
you overlaid the receiver’s sampling instants — one at the center of
each frame cell, sixteen ticks being enough oversampling to land there
reliably — every sample would fall squarely in the middle of a
rock-steady level. That is the Moore output paying off: nothing moves at
the sampling instant except by design.&lt;/p&gt;
&lt;h2 id=&quot;the-one-cycle-handshake&quot;&gt;The one-cycle handshake&lt;/h2&gt;
&lt;p&gt;We have seen both halves of the handshake now; it is worth assembling
them into the protocol the caller actually follows, because it is the
part most likely to bite an integrator who gets it wrong.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ready&lt;/code&gt; is high &lt;strong&gt;only&lt;/strong&gt; when &lt;code&gt;txState s == TxIdle&lt;/code&gt;. The moment a byte is
accepted — the idle arm firing on a &lt;code&gt;Just b&lt;/code&gt; — the state becomes
&lt;code&gt;TxStart&lt;/code&gt;, so on the very next cycle &lt;code&gt;ready&lt;/code&gt; is already low, and it stays
low through start, all eight data bits, and stop: some five hundred
&lt;code&gt;Dom100&lt;/code&gt; cycles at 2 Mbaud. Only when &lt;code&gt;txAdvance&lt;/code&gt; finally returns the
machine to &lt;code&gt;TxIdle&lt;/code&gt; does &lt;code&gt;ready&lt;/code&gt; rise again. So the contract is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Present &lt;code&gt;Just b&lt;/code&gt; on a cycle when &lt;code&gt;ready&lt;/code&gt; is high, and the byte is
latched that cycle. Present it any other time and it is silently
dropped.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That “silently dropped” is not sloppiness; it is enforced by the shape of
the guards. Once the machine is out of idle, &lt;em&gt;no&lt;/em&gt; arm of &lt;code&gt;s&#39;&lt;/code&gt; so much as
looks at &lt;code&gt;mbyte&lt;/code&gt; — the &lt;code&gt;not tick&lt;/code&gt;, counting, and &lt;code&gt;txAdvance&lt;/code&gt; arms are
functions of &lt;code&gt;tick&lt;/code&gt; and the state alone. A &lt;code&gt;Just b&lt;/code&gt; offered mid-frame
falls through every guard and changes nothing. The byte is not queued,
not latched, not remembered; it simply has no effect, because there is no
wire by which it could have one. That is a deliberate safety property: a
caller that violates the handshake cannot &lt;em&gt;corrupt&lt;/em&gt; a frame in flight,
only fail to send. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;design&lt;/a&gt; calls this out as its own line
— “a &lt;code&gt;Just&lt;/code&gt; presented mid-transmission is ignored (no corruption)” —
and the guard structure is what makes it true by construction rather than
by vigilance.&lt;/p&gt;
&lt;p&gt;On the drain side of the real UART, this is exactly the discipline the
trace-drain FSM will follow: present the next ring byte on &lt;code&gt;mbyte&lt;/code&gt; only
while &lt;code&gt;ready&lt;/code&gt; is high, and let &lt;code&gt;ready&lt;/code&gt; falling be the backpressure that
paces the whole readout. One flag, one rule, and the transmitter can
never be overrun.&lt;/p&gt;
&lt;h2 id=&quot;the-tests&quot;&gt;The tests&lt;/h2&gt;
&lt;p&gt;The transmitter shares a test module with the rest of the UART,
&lt;code&gt;Test/Uart.hs&lt;/code&gt;, and because it is sequential the tests drive it through
Clash simulation — &lt;code&gt;sampleN&lt;/code&gt;, &lt;code&gt;bundle&lt;/code&gt;, &lt;code&gt;fromList&lt;/code&gt; — rather than the
pure-function style the CRC used. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt;’s reading of &lt;code&gt;Signal&lt;/code&gt; as
“an endless stream, one sample per cycle” is what makes that tractable:
&lt;code&gt;sampleN n&lt;/code&gt; is &lt;code&gt;take n&lt;/code&gt; on the stream, so “simulate four hundred cycles”
is a list traversal, not a simulator invocation.&lt;/p&gt;
&lt;p&gt;Three of the properties are the transmitter’s own. The first is the one
that matters most, and it is the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator&lt;/a&gt; post’s
promised keystone finally starting to close:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;TX-&amp;gt;RX fast loopback recovers the byte&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genByte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; runFastLoop &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 400&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span&gt;b&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; b&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; out&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ===&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;runFastLoop&lt;/code&gt; wires the transmitter’s &lt;code&gt;line&lt;/code&gt; straight into the receiver’s
input, shares one tick between them, and — for speed — runs with the
tick held permanently &lt;code&gt;True&lt;/code&gt;, so each bit is sixteen cycles rather than
the sixteen &lt;em&gt;ticks&lt;/em&gt; the real NCO spreads over fifty. &lt;a rel=&quot;external&quot; href=&quot;https://hedgehog.qa&quot;&gt;Hedgehog&lt;/a&gt; draws a
random byte, feeds it to the transmitter as a single &lt;code&gt;Just b&lt;/code&gt; while
&lt;code&gt;ready&lt;/code&gt; is high, and asserts that the receiver hands back that exact
byte, bit-for-bit. No reference model, no golden waveform: the two halves
of the UART check each other. Any disagreement about bit order, frame
shape, or timing — an MSB-first slip, an off-by-one on the stop bit —
shows up as a byte that comes back wrong, and Hedgehog shrinks the
failing case to the smallest byte that still breaks it.&lt;/p&gt;
&lt;p&gt;The second pins the handshake:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;TX ignores input while busy (ready gating)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genByte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; runFastLoop &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; :&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;replicate &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;100&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Just&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 400&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span&gt;b&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; b&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; out&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ===&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here the caller &lt;em&gt;misbehaves on purpose&lt;/em&gt;: after one idle cycle it jams
&lt;code&gt;Just b&lt;/code&gt; onto the input a hundred cycles in a row, straight through the
transmission. If mid-frame requests were latched, the line would be a
mess of restarted frames and the receiver would recover garbage or a
fistful of bytes. The assertion is that exactly &lt;strong&gt;one&lt;/strong&gt; byte comes back
— the machine accepted the first &lt;code&gt;Just b&lt;/code&gt; while idle and ignored the
other ninety-nine while busy, precisely as the guards promise. This is
the “no corruption” property, stated as a test.&lt;/p&gt;
&lt;p&gt;The third runs the same loopback through the &lt;em&gt;real&lt;/em&gt; baud generator rather
than the always-true tick:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;full UART loopback (real NCO) recovers the byte&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  b &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll genByte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; out &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; runFullLoop &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Nothing&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; :&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;replicate &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;60&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Just&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1200&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span&gt;b&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span&gt; b&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; out&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ===&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;runFullLoop&lt;/code&gt; calls the umbrella &lt;code&gt;uart (SNat @2_000_000)&lt;/code&gt; — baud
generator, transmitter, and receiver all wired together — with the
transmitter’s line looped back to the receiver’s. Now a bit really is
fifty clocks wide, tiled by sixteen NCO ticks whose gaps alternate three
and four clocks, and the receiver must still sample each bit dead-center.
That it recovers the byte is the whole UART’s keystone: the fractional
3.125 the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud generator&lt;/a&gt; fought to average correctly, the
sixteen-tick bits the transmitter holds, and the center-sampling the
receiver performs, all have to agree at once or the byte comes back
wrong. The baud-generator post said we would meet the other end of its
one blunt tick-rate test “when the receiver is built”; this is that other
end, and the transmitter is the piece in the middle that turns a cadence
into a frame.&lt;/p&gt;
&lt;h2 id=&quot;what-we-read&quot;&gt;What we read&lt;/h2&gt;
&lt;p&gt;Eighty-odd lines, one exported name, and underneath it the first real
state machine in the series: two &lt;code&gt;data&lt;/code&gt; types that are its entire memory
— a sum for the frame phase, &lt;code&gt;TxData (Index 8)&lt;/code&gt; folding eight data-bit
states into one bounded constructor, and a record for the byte and the
tick counter carried alongside it — lifted into hardware by a &lt;code&gt;mealy&lt;/code&gt;
at the very top of the module, its state held in the register the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;baud
generator&lt;/a&gt; showed us bare. The brain beside it, &lt;code&gt;txStep&lt;/code&gt;, is a
pure &lt;code&gt;s -&amp;gt; i -&amp;gt; (s, o)&lt;/code&gt; you can test in milliseconds, and reading it
closely settled the question the module’s own comment raised: the outputs
&lt;code&gt;line&lt;/code&gt; and &lt;code&gt;ready&lt;/code&gt; are &lt;strong&gt;Moore&lt;/strong&gt; — functions of the state alone, so the
serial line never twitches between bit boundaries — while the single
input-driven, un-tick-gated transition out of idle is what makes the
&lt;code&gt;ready&lt;/code&gt; handshake resolve in one cycle. &lt;code&gt;txAdvance&lt;/code&gt; walks the phases
start-to-stop and deals the latched byte out of a right-shifting register
LSB-first, the CRC’s shift register run backwards. And a byte-exact
loopback checks the whole thing against its own other half.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt; promised that spotting a &lt;code&gt;mealy&lt;/code&gt; at the top of a module
would one day mean something concrete rather than looking like an
incantation. This is the module where it pays off, and the silhouette we
read here — find the &lt;code&gt;mealy&lt;/code&gt;, find the step function, split it into
outputs and next state, decide Moore or Mealy by whether the &lt;em&gt;output&lt;/em&gt;
looks at the input — is the one every remaining block wears. Next is
the receiver: the transmitter’s mirror, where the line is an &lt;em&gt;input&lt;/em&gt;
arriving asynchronously from a pin, where a start edge has to be &lt;em&gt;found&lt;/em&gt;
rather than driven, and where the majority-vote center sampling that the
loopback test quietly depends on gets built in the open. Between the two
of them the UART closes, and the heartbeat the baud generator started
finally carries a byte end to end.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-backpressure&quot;&gt;
&lt;p&gt;The alternative is full &lt;strong&gt;valid/ready backpressure&lt;/strong&gt;,
where producer and consumer each raise a flag and a byte moves only on a
cycle when both are high — the discipline you reach for when either side
might stall indefinitely. A UART transmitter at 2 Mbaud inside a 100 MHz
fabric is not that situation: once a byte is latched the line takes
roughly five hundred cycles to clock it out, so any consumer of &lt;code&gt;ready&lt;/code&gt;
has enormous slack and a one-way &lt;code&gt;ready&lt;/code&gt; flag suffices. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-uart-baudgen/&quot;&gt;UART
design&lt;/a&gt; records this as an explicit decision — “at 2 Mbaud a
byte spans ~500 &lt;code&gt;Dom100&lt;/code&gt; cycles, so the consumer always keeps up — full
valid/ready backpressure is overkill” — and it is a good example of
sizing the mechanism to the actual timing rather than reaching for the
most general handshake reflexively. &lt;a href=&quot;#fr-backpressure-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-index&quot;&gt;
&lt;p&gt;&lt;code&gt;Index n&lt;/code&gt; is the primer’s counting-in-the-type feature doing
double duty. As a &lt;em&gt;state&lt;/em&gt;, &lt;code&gt;TxData (Index 8)&lt;/code&gt; has exactly eight
inhabitants, so the compiler’s tally of &lt;code&gt;TxState&lt;/code&gt;’s constructors is
exact and its exhaustiveness warnings are trustworthy. As a &lt;em&gt;value&lt;/em&gt;,
&lt;code&gt;txCnt :: Index 16&lt;/code&gt; and the &lt;code&gt;i&lt;/code&gt; inside &lt;code&gt;TxData&lt;/code&gt; can only hold legal
positions — &lt;code&gt;0..15&lt;/code&gt; and &lt;code&gt;0..7&lt;/code&gt; respectively — so the arithmetic
&lt;code&gt;txCnt s + 1&lt;/code&gt; and &lt;code&gt;TxData (i + 1)&lt;/code&gt; cannot silently produce an
out-of-range count the way an &lt;code&gt;Int&lt;/code&gt; would; an overflow is a runtime
error in simulation and a wrapped value the type is designed to prevent
you from reaching, which is why both increments sit behind a
&lt;code&gt;/= maxBound&lt;/code&gt; guard. The width in the type is not documentation; it is
the thing that makes “sixteen ticks per bit” and “eight data bits” facts
the compiler helps enforce rather than comments it cannot check. &lt;a href=&quot;#fr-index-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-bundle&quot;&gt;
&lt;p&gt;&lt;code&gt;bundle&lt;/code&gt; and &lt;code&gt;unbundle&lt;/code&gt; come from Clash’s &lt;code&gt;Bundle&lt;/code&gt; class,
which witnesses the isomorphism &lt;code&gt;Signal dom (a, b) ≅ (Signal dom a, Signal dom b)&lt;/code&gt; — and more generally between a &lt;code&gt;Signal&lt;/code&gt; of some product
(a tuple, a &lt;code&gt;Vec&lt;/code&gt;, a record) and the product of &lt;code&gt;Signal&lt;/code&gt;s. The intuition
is pure hardware: a wire carrying a pair &lt;em&gt;is&lt;/em&gt; two wires, and whether you
think of “one bus of pairs over time” or “two wires over time” is a
point of view, not a difference in the circuit. &lt;code&gt;mealy&lt;/code&gt; is written to
take a single input &lt;code&gt;Signal&lt;/code&gt; and produce a single output &lt;code&gt;Signal&lt;/code&gt;, so
when your interface is several signals — as almost every real block’s is
— &lt;code&gt;bundle&lt;/code&gt; gathers them going in and &lt;code&gt;unbundle&lt;/code&gt; scatters them coming
out. They synthesise to nothing at all; they are the type-level
bookkeeping that lets a one-in/one-out combinator serve a many-in/many-out
world. &lt;a href=&quot;#fr-bundle-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-mealymoore&quot;&gt;
&lt;p&gt;The two machine styles are named for the engineers who
formalised them a year apart. A &lt;strong&gt;Moore machine&lt;/strong&gt; (Edward F. Moore,
&lt;em&gt;Gedanken-experiments on Sequential Machines&lt;/em&gt;, 1956) has outputs that are
a function of the current state &lt;em&gt;only&lt;/em&gt;: &lt;code&gt;output = g(state)&lt;/code&gt;. A &lt;strong&gt;Mealy
machine&lt;/strong&gt; (George H. Mealy, &lt;em&gt;A Method for Synthesizing Sequential
Circuits&lt;/em&gt;, 1955) has outputs that are a function of the current state
&lt;em&gt;and&lt;/em&gt; the current input: &lt;code&gt;output = g(state, input)&lt;/code&gt;. Both have a
next-state function &lt;code&gt;f(state, input)&lt;/code&gt; — taking the input is how either
kind reacts to anything — so the &lt;em&gt;sole&lt;/em&gt; distinction is whether the
input can reach the output within the same cycle. The practical trade is
latency versus stability: a Mealy output can respond in the cycle its
input arrives (fewer states, quicker reaction) but inherits the input’s
glitches and combinational timing; a Moore output lags by up to a state
transition but is stable and glitch-free for as long as the state holds.
The two are equivalent in power — any Mealy machine has a Moore
equivalent, generally with more states, and vice versa — so the choice
is engineering, not expressiveness. &lt;a href=&quot;#fr-mealymoore-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-whymealy&quot;&gt;
&lt;p&gt;Which raises a fair question: if the outputs are Moore, why
lift with &lt;code&gt;mealy&lt;/code&gt; and not Clash’s &lt;code&gt;moore&lt;/code&gt;, whose type
&lt;code&gt;moore :: (s -&amp;gt; i -&amp;gt; s) -&amp;gt; (s -&amp;gt; o) -&amp;gt; s -&amp;gt; Signal dom i -&amp;gt; Signal dom o&lt;/code&gt;
splits the next-state function from a separate, state-only output
function — a perfect fit for a Moore machine? You could; the module
would be equivalent. &lt;code&gt;mealy&lt;/code&gt; wins on locality: &lt;code&gt;txStep&lt;/code&gt; computes the next
state and both outputs in a single &lt;code&gt;where&lt;/code&gt; block that shares &lt;code&gt;txState s&lt;/code&gt;,
&lt;code&gt;txShift s&lt;/code&gt;, and a couple of &lt;code&gt;case&lt;/code&gt;s, and returning them together as
&lt;code&gt;(s&#39;, (line, ready))&lt;/code&gt; keeps that shared reading of the state in one
place. Splitting into a &lt;code&gt;moore&lt;/code&gt;-shaped pair would duplicate the “look at
&lt;code&gt;txState s&lt;/code&gt;” logic across two functions for the sake of a label. &lt;code&gt;mealy&lt;/code&gt;
is the more general combinator, it costs nothing to use where a Moore
machine is what you have, and it keeps the brain in one piece — so the
codebase uses it uniformly, here and in the engine, and lets the &lt;em&gt;shape&lt;/em&gt;
of &lt;code&gt;txStep&lt;/code&gt; (whether its outputs happen to read the input) decide what
kind of machine each block really is. &lt;a href=&quot;#fr-whymealy-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-lsbfirst&quot;&gt;
&lt;p&gt;The bit-ordering split between the two blocks is not
arbitrary; each follows its own standard. A UART frame is transmitted
&lt;strong&gt;least-significant-bit first&lt;/strong&gt; — a convention inherited from the
teletype and enshrined in every 8N1 implementation — so the transmitter
reads &lt;code&gt;lsb&lt;/code&gt; and shifts &lt;em&gt;right&lt;/em&gt;, dealing bit 0, then 1, up to 7. The
&lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC&lt;/a&gt; the eSPI link uses is defined &lt;strong&gt;most-significant-bit first&lt;/strong&gt;
(as eSPI and SMBus require), so that module reads &lt;code&gt;msb&lt;/code&gt; and shifts
&lt;em&gt;left&lt;/em&gt;. The pleasing thing is how little the code has to say about it:
the entire difference between “LSB-first serialiser” and “MSB-first CRC”
is the direction of a shift and which end you read, &lt;code&gt;shiftR&lt;/code&gt;/&lt;code&gt;lsb&lt;/code&gt; versus
&lt;code&gt;shiftL&lt;/code&gt;/&lt;code&gt;msb&lt;/code&gt;. Two wire conventions from two unrelated standards, a
single mirror-image apart in the source. &lt;a href=&quot;#fr-lsbfirst-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-exhaustive&quot;&gt;
&lt;p&gt;“The compiler will say so” is worth qualifying, because how
&lt;em&gt;loudly&lt;/em&gt; it says so is a knob. Left alone, GHC is silent about a
non-exhaustive &lt;code&gt;case&lt;/code&gt;; the diagnostic is &lt;code&gt;-Wincomplete-patterns&lt;/code&gt;, and
Tamal gets it only because its cabal turns on &lt;code&gt;-Wall&lt;/code&gt; — one of the
&lt;code&gt;ghc-options&lt;/code&gt; the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;introduction post&lt;/a&gt; flagged as load-bearing. Even
then it is by default just a &lt;strong&gt;warning&lt;/strong&gt;: the module compiles, and in
ordinary Haskell a branch that “cannot happen” but does raises a
&lt;code&gt;PatternMatchFail&lt;/code&gt; exception at &lt;em&gt;run&lt;/em&gt; time rather than being caught at
compile time. So the check is real but toothless unless you escalate it,
which you can: &lt;code&gt;-Werror=incomplete-patterns&lt;/code&gt; (or the pair
&lt;code&gt;-Wincomplete-patterns -Werror&lt;/code&gt;), set per-module with an
&lt;code&gt;{-# OPTIONS_GHC #-}&lt;/code&gt; pragma or project-wide in &lt;code&gt;ghc-options&lt;/code&gt;, turns a
missing constructor into a hard build failure — I confirmed as much on
the exact GHC this project uses, where the warning becomes
&lt;code&gt;error: [-Wincomplete-patterns, Werror=incomplete-patterns]&lt;/code&gt; and the
compile stops. That is the instructive contrast with Rust, whose
recollection you may share: Rust makes &lt;code&gt;match&lt;/code&gt; exhaustiveness a
&lt;em&gt;type-checker requirement&lt;/em&gt;, so a non-exhaustive match is a compile error
(&lt;code&gt;E0004&lt;/code&gt;) out of the box, with &lt;code&gt;_&lt;/code&gt; the one explicit escape hatch. Haskell
went the other way because it has always sanctioned partial functions —
&lt;code&gt;head&lt;/code&gt;, &lt;code&gt;fromJust&lt;/code&gt;, and their kin are non-exhaustive &lt;em&gt;by design&lt;/em&gt; — so it
treats totality as an opt-in diagnostic layered on top of the language
rather than a law baked into it. For gateware the strict setting is the
easy call: silicon has no exceptions to throw, so a “dead” arm that turned
out to be live would be a silent wrong answer — a don’t-care lowered into
the netlist — not a catchable &lt;code&gt;PatternMatchFail&lt;/code&gt;, which is a good argument
for compiling hardware with &lt;code&gt;-Werror=incomplete-patterns&lt;/code&gt;. Writing the
&lt;code&gt;TxIdle -&amp;gt; s&lt;/code&gt; arm keeps even the warning quiet, which is why it is there. &lt;a href=&quot;#fr-exhaustive-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Tamal: The baud generator</title>
    <published>2026-07-21T09:00:00+00:00</published>
    <updated>2026-07-21T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-uart-baudgen/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-uart-baudgen/</id>
    <summary type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC unit&lt;/a&gt; we read last time was pure combinational logic — a
truth table wearing a Haskell type, the same output for the same input,
no memory, no clock. It was the gentlest possible first block precisely
because time never entered into it. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt; closed by promising
that the next rung up was a block &lt;em&gt;with a clock inside it&lt;/em&gt;, where
&lt;code&gt;Signal&lt;/code&gt; stops being a footnote and becomes the whole substance of the
file. That block is the UART, Tamal’s link to the host, and this is
where we start cashing the promise.&lt;/p&gt;
&lt;p&gt;The UART is not one module but three small ones — a baud generator, a
transmitter, and a receiver — so the series takes them one at a time,
in the order they were built and the order they lean on one another.
First the baud generator: the smallest of the three, with no state
machine at all, just a clocked oscillator that produces the &lt;em&gt;heartbeat&lt;/em&gt;
every other part of the UART marches to. It is also where we meet
&lt;code&gt;register&lt;/code&gt;, the lone flip-flop that &lt;code&gt;mealy&lt;/code&gt; — the machine from the
primer — is built out of. &lt;code&gt;mealy&lt;/code&gt; itself waits for the transmitter;
here we meet its atom.&lt;/p&gt;
&lt;p&gt;Like the CRC, the whole thing fits in a screenful.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC unit&lt;/a&gt; we read last time was pure combinational logic — a
truth table wearing a Haskell type, the same output for the same input,
no memory, no clock. It was the gentlest possible first block precisely
because time never entered into it. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt; closed by promising
that the next rung up was a block &lt;em&gt;with a clock inside it&lt;/em&gt;, where
&lt;code&gt;Signal&lt;/code&gt; stops being a footnote and becomes the whole substance of the
file. That block is the UART, Tamal’s link to the host, and this is
where we start cashing the promise.&lt;/p&gt;
&lt;p&gt;The UART is not one module but three small ones — a baud generator, a
transmitter, and a receiver — so the series takes them one at a time,
in the order they were built and the order they lean on one another.
First the baud generator: the smallest of the three, with no state
machine at all, just a clocked oscillator that produces the &lt;em&gt;heartbeat&lt;/em&gt;
every other part of the UART marches to. It is also where we meet
&lt;code&gt;register&lt;/code&gt;, the lone flip-flop that &lt;code&gt;mealy&lt;/code&gt; — the machine from the
primer — is built out of. &lt;code&gt;mealy&lt;/code&gt; itself waits for the transmitter;
here we meet its atom.&lt;/p&gt;
&lt;p&gt;Like the CRC, the whole thing fits in a screenful.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;the-entire-source&quot;&gt;The entire source&lt;/h2&gt;
&lt;p&gt;Minus the license header and the doc-comments, here is the module in
full:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;{-#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; LANGUAGE&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; NumericUnderscores&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt; #-}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Uart.BaudGen&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; oversampleTick&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Clash.Prelude&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;oversampleTick&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  forall&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; baud&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;HiddenClockResetEnable&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; KnownDomain&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; KnownNat&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; baud&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  SNat&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; baud&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;oversampleTick baud &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; tick&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;  periodPs&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Integer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  periodPs &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; snatToNum &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;SNat&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;DomainPeriod&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;  fClk&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  fClk &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; fromInteger &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1_000_000_000_000&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; `&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span&gt; periodPs&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;  inc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  inc &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; fromInteger &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; *&lt;/span&gt;&lt;span&gt; snatToNum baud&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;  sums&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  sums &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span&gt; inc&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; acc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;  tick&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tick &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;gt;=&lt;/span&gt;&lt;span&gt; fClk&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; sums&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;  acc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  acc &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; register &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt; acc&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;  acc&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  acc&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; mux tick &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;subtract fClk &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; sums&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; sums&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Roughly fifteen lines of logic under a familiar frame. The frame first,
then the problem it exists to solve, then the five wires that solve it.&lt;/p&gt;
&lt;h2 id=&quot;one-exported-name&quot;&gt;One exported name&lt;/h2&gt;
&lt;p&gt;The top of the file is the CRC module’s opening beat, played again.
&lt;code&gt;module Tamal.Uart.BaudGen&lt;/code&gt; names the module after its path on disk —
&lt;code&gt;src/Tamal/Uart/BaudGen.hs&lt;/code&gt;, a dot per directory — and the parentheses
are the export list, the one door in the wall:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Uart.BaudGen&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; oversampleTick&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Only &lt;code&gt;oversampleTick&lt;/code&gt; leaves the file — and, as in the CRC unit, the
export list is only half the story of what stays private. There, the
real bit-twiddling lived in &lt;code&gt;step&lt;/code&gt;, tucked into &lt;code&gt;crc8Update&lt;/code&gt;’s &lt;code&gt;where&lt;/code&gt;
block; here the entire mechanism — &lt;code&gt;fClk&lt;/code&gt;, &lt;code&gt;inc&lt;/code&gt;, &lt;code&gt;sums&lt;/code&gt;, &lt;code&gt;tick&lt;/code&gt;,
&lt;code&gt;acc&lt;/code&gt;, and &lt;code&gt;acc&#39;&lt;/code&gt; — lives the same way, nested inside &lt;code&gt;oversampleTick&lt;/code&gt;.
A &lt;code&gt;where&lt;/code&gt; binding is not a name the export list politely declines to
mention. It is a name with no existence at all outside the one function
body it sits in: no other module could reach &lt;code&gt;acc&lt;/code&gt;, or &lt;code&gt;step&lt;/code&gt;, even if
it tried, because there is no syntax with which to spell it. The export
list guards the module’s one top-level door; where the work is &lt;em&gt;written&lt;/em&gt;
seals the rest by construction.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;import Clash.Prelude&lt;/code&gt; is the same prelude swap the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-crc/&quot;&gt;CRC post&lt;/a&gt;
dwelt on — the line that throws out ordinary Haskell’s furniture and
moves in the hardware vocabulary, &lt;code&gt;Signal&lt;/code&gt;, &lt;code&gt;Unsigned&lt;/code&gt;, &lt;code&gt;register&lt;/code&gt;,
&lt;code&gt;mux&lt;/code&gt;, the versions of the everyday names that lower to gates. It is
still the line that says &lt;em&gt;compile me to hardware&lt;/em&gt;; I will not re-derive
it here.&lt;/p&gt;
&lt;p&gt;The one new piece of ceremony is the pragma up top:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt;{-#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; LANGUAGE&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; NumericUnderscores&lt;/span&gt;&lt;span class=&quot;z-l-7 z-d-7&quot;&gt; #-}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is the switch that lets you write &lt;code&gt;1_000_000_000_000&lt;/code&gt; with
underscores grouping the digits, the way you would write a hundred
billion on paper instead of squinting at a run of twelve zeros. It is
pure legibility, and you will want it the moment a clock frequency
shows up as a literal.&lt;/p&gt;
&lt;h2 id=&quot;the-problem-is-3-125&quot;&gt;The problem is 3.125&lt;/h2&gt;
&lt;p&gt;Everything strange about this module comes from one number, so it is
worth deriving before reading a line of the body.&lt;/p&gt;
&lt;p&gt;Tamal runs on an Arty A7 at 100 MHz; the host link runs at
2 Mbaud, eight data bits, no parity, one stop bit. Divide
the two and a bit is&lt;/p&gt;
&lt;p&gt;$$ \frac{100 \times 10^6}{2 \times 10^6} = 50 $$&lt;/p&gt;
&lt;p&gt;exactly fifty system clocks wide. That division comes out whole, which
is a small mercy and also a trap, because it tempts you to think the
timing is easy. It is not, and the reason is the receiver.&lt;/p&gt;
&lt;p&gt;A transmitter has it easy: it &lt;em&gt;owns&lt;/em&gt; the clock, so it can hold each bit
for fifty cycles and call it a day. A receiver does not. The line
arriving from the host is asynchronous — its bit edges fall wherever
they fall, with no relation to Tamal’s clock — and the receiver has to
&lt;em&gt;find&lt;/em&gt; the middle of a bit it never scheduled. The standard cure, the
one every robust UART uses, is to &lt;strong&gt;oversample&lt;/strong&gt;: sample the line many
times per bit, watch for the start edge, and then read each bit at its
center where it is most settled. Tamal oversamples sixteen times a bit.
Sixteen samples across a fifty-clock bit means a sample every&lt;/p&gt;
&lt;p&gt;$$ \frac{50}{16} = 3.125 $$&lt;/p&gt;
&lt;p&gt;clocks — and &lt;em&gt;there&lt;/em&gt; is the number. The bit period divides the clock
evenly; the thing we actually need, the 16x oversample period, does
not. You cannot count off 3.125 clocks on an integer counter. Some
ticks have to be three clocks apart and some four, arranged so the
long-run average lands exactly on 3.125. Producing that stream — an
enable that fires, on average, every 3.125 clocks — is the baud
generator’s entire job.&lt;/p&gt;
&lt;h2 id=&quot;not-a-divider-not-a-pll&quot;&gt;Not a divider, not a PLL&lt;/h2&gt;
&lt;p&gt;There are two obvious ways to make a 16x-oversample strobe, and the
module takes neither. Both refusals are worth understanding, because
the road not taken is where the design’s philosophy shows.&lt;/p&gt;
&lt;p&gt;The first idea is a plain &lt;strong&gt;clock divider&lt;/strong&gt;: a counter that rolls over
every so many clocks and pulses. But a divider can only divide by whole
numbers. Divide 100 MHz by three and you get
33.3 MHz; by four, 25 MHz; the target,
16 × 2 Mbaud = 32 MHz, sits between two
integer divisors and no counter reaches it. You could special-case &lt;em&gt;this&lt;/em&gt;
baud, hand-alternating threes and fours, but retune the link to some
other rate for signal-integrity debugging and the fraction turns
uglier still. A divider is the wrong tool for a fractional ratio.&lt;/p&gt;
&lt;p&gt;The second idea is a &lt;strong&gt;PLL&lt;/strong&gt;: ask the FPGA’s clock hardware to
synthesize a real 32 MHz clock and run the UART on it. This
works, and it is exactly the move to resist, because it manufactures a
&lt;em&gt;second clock domain&lt;/em&gt;. The moment the UART ticks on its own clock,
every byte crossing from the receiver into the 100 MHz
engine has to cross a clock boundary — and clock-domain crossings are
where the sneaky bugs live: two-flop synchronizers, gray-coded
pointers, an asynchronous FIFO, a pile of hardware whose only purpose
is to survive two clocks that do not agree. More gates, more failure
modes, harder to verify, all to gain a strobe you can get for free
inside the clock you already have.&lt;/p&gt;
&lt;p&gt;So the module produces neither a divided clock nor a synthesized one.
It produces an &lt;strong&gt;enable&lt;/strong&gt;: a boolean, one per system cycle, that is
&lt;code&gt;True&lt;/code&gt; on the cycles the rest of the UART should advance and &lt;code&gt;False&lt;/code&gt; on
the rest. The receiver and transmitter stay clocked by the one
100 MHz clock the whole chip runs on; they simply &lt;em&gt;do
nothing&lt;/em&gt; on the cycles the enable is low.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The baud generator hands out a cadence, not a clock — a &lt;code&gt;Bool&lt;/code&gt; that
says &lt;em&gt;now&lt;/em&gt; a few times per bit, inside the single clock the rest of
the design already lives in.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hold that distinction; it is the whole reason the return type is what
it is.&lt;/p&gt;
&lt;h2 id=&quot;the-type&quot;&gt;The type&lt;/h2&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;oversampleTick&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  forall&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; baud&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;HiddenClockResetEnable&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; KnownDomain&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; KnownNat&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; baud&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  SNat&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; baud&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;  Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read it back to front and it confirms everything the last two sections
argued. The result is &lt;code&gt;Signal dom Bool&lt;/code&gt; — under the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt;’s reading,
“a &lt;code&gt;Bool&lt;/code&gt; that may change on every clock tick in domain &lt;code&gt;dom&lt;/code&gt;,” a wire
carrying one yes-or-no per cycle. Not a &lt;code&gt;Clock&lt;/code&gt;, not a divided
&lt;code&gt;Signal dom Bit&lt;/code&gt; masquerading as timing: an enable, stated in the type.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A &lt;code&gt;Signal dom Bool&lt;/code&gt; returned as timing is the type of an &lt;em&gt;enable&lt;/em&gt; —
the one-domain alternative to handing back a second clock.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The single argument is &lt;code&gt;SNat baud&lt;/code&gt;. An &lt;code&gt;SNat&lt;/code&gt; is a &lt;strong&gt;singleton&lt;/strong&gt; — the
lone value that inhabits the type of a type-level number, so &lt;code&gt;baud&lt;/code&gt;
lives in the type &lt;em&gt;and&lt;/em&gt; can be passed as an argument without losing its
type-level identity.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-singleton-1&quot;&gt;&lt;a href=&quot;#fn-singleton&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; The caller writes
&lt;code&gt;oversampleTick (SNat @2_000_000)&lt;/code&gt;, and the &lt;code&gt;2_000_000&lt;/code&gt; travels as
something the compiler can both compute with and check. This is the
primer’s “numbers can live in types” seen from the other side: here a
number is the input, but it is the type-level number made touchable.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;forall baud dom.&lt;/code&gt; in front is not decoration. It pulls the two
type variables into scope inside the body, so the definitions below can
mention &lt;code&gt;dom&lt;/code&gt; and &lt;code&gt;baud&lt;/code&gt; by name — which they must, because one of them
is about to read a number straight out of the type &lt;code&gt;dom&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That leaves the three constraints before the &lt;code&gt;=&amp;gt;&lt;/code&gt;, each buying one
capability the body spends:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;HiddenClockResetEnable dom&lt;/code&gt;&lt;/strong&gt; — there is a clock (and reset, and
enable) for &lt;code&gt;dom&lt;/code&gt;, threaded implicitly so the code needn’t pass it by
hand. This is what &lt;code&gt;register&lt;/code&gt;, further down, quietly draws on: a
flip-flop needs a clock, and this is where it comes from.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;KnownDomain dom&lt;/code&gt;&lt;/strong&gt; — the domain’s static configuration, its clock
period among it, is known at compile time. We are about to read that
period.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;KnownNat baud&lt;/code&gt;&lt;/strong&gt; — the baud number is a known type-level natural,
so it can be reflected down to an ordinary value with &lt;code&gt;snatToNum&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;reading-the-clock-out-of-the-type&quot;&gt;Reading the clock out of the type&lt;/h2&gt;
&lt;p&gt;The first two &lt;code&gt;where&lt;/code&gt; bindings compute a constant most designs would
have made you pass in — and get wrong:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;periodPs &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; snatToNum &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;SNat&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;DomainPeriod&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;fClk     &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; fromInteger &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1_000_000_000_000&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; `&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span&gt; periodPs&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;DomainPeriod dom&lt;/code&gt; is a type-level number: the clock period, in
picoseconds, baked into the definition of the domain itself. For
Tamal’s &lt;code&gt;Dom100&lt;/code&gt; it is 10,000 — ten nanoseconds, a hundred megahertz.
&lt;code&gt;SNat @(DomainPeriod dom)&lt;/code&gt; conjures the singleton for that type-level
number and &lt;code&gt;snatToNum&lt;/code&gt; reflects it down to an &lt;code&gt;Integer&lt;/code&gt;, so &lt;code&gt;periodPs&lt;/code&gt;
is 10,000 computed by the compiler, not typed by a human.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;fClk&lt;/code&gt; then turns a period into a frequency the obvious way: there are
$10^{12}$ picoseconds in a second, so the number of cycles per second
is $10^{12}$ divided by the period. For &lt;code&gt;Dom100&lt;/code&gt;,
$10^{12} / 10^{4} = 10^{8}$, one hundred million — &lt;code&gt;fClk = 100_000_000&lt;/code&gt;,
the clock frequency in hertz, and the modulus the oscillator will count
against.&lt;/p&gt;
&lt;p&gt;The point is what did &lt;em&gt;not&lt;/em&gt; happen. Nobody passed the clock frequency
in as a parameter. It was read off the clock domain, at compile time,
from the same type that governs the actual flip-flops. There is no
second knob to keep in sync with the first, no way for a &lt;code&gt;100_000_000&lt;/code&gt;
in one place to drift from a &lt;code&gt;Dom100&lt;/code&gt; in another:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The frequency isn’t configuration you can get wrong; it’s reflected
out of the domain type, so the number and the clock can never
disagree.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It is the primer’s “the compiler is a wire-width checker,” pointed at
time instead of width.&lt;/p&gt;
&lt;p&gt;The third constant is the phase increment:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;inc &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; fromInteger &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; *&lt;/span&gt;&lt;span&gt; snatToNum baud&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;snatToNum baud&lt;/code&gt; reflects the baud rate down to a value, and
$16 \times 2{,}000{,}000 = 32{,}000{,}000$ is the target oversample rate in
hertz. Both &lt;code&gt;inc&lt;/code&gt; and &lt;code&gt;fClk&lt;/code&gt; are ordinary &lt;code&gt;Unsigned 32&lt;/code&gt; — thirty-two
plain wires with no arithmetic surprises — and both are fixed at
compile time. All the interesting behaviour is in how they are used.&lt;/p&gt;
&lt;h2 id=&quot;the-accumulator&quot;&gt;The accumulator&lt;/h2&gt;
&lt;p&gt;Five bindings remain, and together they are a &lt;strong&gt;numerically-controlled
oscillator&lt;/strong&gt; — the name the module’s own comment gives it.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-nco-1&quot;&gt;&lt;a href=&quot;#fn-nco&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt; The
idea is a single running total, the &lt;em&gt;phase accumulator&lt;/em&gt;, that gains
&lt;code&gt;inc&lt;/code&gt; every clock and fires a tick whenever it laps the modulus &lt;code&gt;fClk&lt;/code&gt;.
Here it is, wire by wire:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sums &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span&gt; inc&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; acc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;tick &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;gt;=&lt;/span&gt;&lt;span&gt; fClk&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; sums&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;acc  &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; register &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt; acc&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;acc&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; mux tick &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;subtract fClk &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; sums&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; sums&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before unpacking the operators, read those four lines with the hardware
ceremony stripped away — as ordinary arithmetic on &lt;em&gt;this cycle’s&lt;/em&gt;
numbers:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sums = acc + inc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;tick = sums &amp;gt;= fClk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;acc  = acc&amp;#39; from the cycle before   (0 on the first cycle)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;acc&amp;#39; = if tick then sums - fClk else sums&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Line for line, that is the whole oscillator: add the increment to the
running phase, flag whether the sum has reached the modulus, carry the
register forward from the previous cycle, and set the next phase to
either the wrapped value or the untouched sum. Only the third line is
not plain arithmetic — it is the one that &lt;em&gt;remembers&lt;/em&gt;, and its &lt;code&gt;=&lt;/code&gt; is
really a one-cycle delay rather than an equation. The Haskell computes
exactly these four things; it just has to state them for &lt;em&gt;every&lt;/em&gt; cycle
at once, which is the job of the three unfamiliar operators — &lt;code&gt;&amp;lt;$&amp;gt;&lt;/code&gt;,
&lt;code&gt;register&lt;/code&gt;, and &lt;code&gt;mux&lt;/code&gt;. Take them in turn.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;register 0 acc&#39;&lt;/code&gt; is the flip-flop. &lt;code&gt;register i s&lt;/code&gt; is Clash’s primitive
memory element — a bank of D flip-flops — and it does exactly one
thing: it outputs &lt;code&gt;i&lt;/code&gt; on the first cycle, and on every cycle after that
it outputs whatever &lt;code&gt;s&lt;/code&gt; held &lt;em&gt;the cycle before&lt;/em&gt;. A one-cycle delay with
a power-up value. This is the atom the primer’s &lt;code&gt;mealy&lt;/code&gt; is assembled
from; a Mealy machine is nothing but a &lt;code&gt;register&lt;/code&gt; holding the state and
a pure function computing the next one. Here we use the register bare,
with no machine wrapped around it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;register&lt;/code&gt; is the flip-flop itself — the single clock-cycle delay
that turns a loop of wires into a circuit that remembers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;(+ inc) &amp;lt;$&amp;gt; acc&lt;/code&gt; is where &lt;code&gt;&amp;lt;$&amp;gt;&lt;/code&gt; enters. &lt;code&gt;&amp;lt;$&amp;gt;&lt;/code&gt; is &lt;code&gt;fmap&lt;/code&gt;, and a &lt;code&gt;Signal&lt;/code&gt;
is a functor, so &lt;code&gt;f &amp;lt;$&amp;gt; s&lt;/code&gt; applies the pure function &lt;code&gt;f&lt;/code&gt; to every
sample of the stream &lt;code&gt;s&lt;/code&gt;. &lt;code&gt;(+ inc)&lt;/code&gt; is a curried adder with one operand
strapped to the constant increment (the primer’s partial application,
made of gates), so &lt;code&gt;sums&lt;/code&gt; reads “the accumulator plus &lt;code&gt;inc&lt;/code&gt;, every
cycle” — one adder, its output a fresh stream.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A pure function &lt;code&gt;fmap&lt;/code&gt;ped over a &lt;code&gt;Signal&lt;/code&gt; is a combinational gate
smeared across all of time: &lt;code&gt;sums&lt;/code&gt; is one adder, re-evaluated every
clock.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;(&amp;gt;= fClk) &amp;lt;$&amp;gt; sums&lt;/code&gt; is the same move with a comparator: each cycle,
ask whether the candidate sum has reached or passed the modulus.&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-section-1&quot;&gt;&lt;a href=&quot;#fn-section&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;
The result, &lt;code&gt;tick&lt;/code&gt;, is a &lt;code&gt;Signal dom Bool&lt;/code&gt; — and it is both the module’s
output &lt;em&gt;and&lt;/em&gt;, one line down, a control wire.&lt;/p&gt;
&lt;p&gt;That control wire is &lt;code&gt;mux&lt;/code&gt;. &lt;code&gt;mux sel a b&lt;/code&gt; is the 2:1 multiplexer on
signals: sample by sample, it takes &lt;code&gt;a&lt;/code&gt; where &lt;code&gt;sel&lt;/code&gt; is &lt;code&gt;True&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;
where &lt;code&gt;sel&lt;/code&gt; is &lt;code&gt;False&lt;/code&gt;. So&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;acc&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; mux tick &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;subtract fClk &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;$&amp;gt;&lt;/span&gt;&lt;span&gt; sums&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; sums&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;reads “the next accumulator is &lt;code&gt;sums&lt;/code&gt; with the modulus subtracted off
on a tick cycle, and plain &lt;code&gt;sums&lt;/code&gt; otherwise.” (&lt;code&gt;subtract fClk&lt;/code&gt; is
&lt;code&gt;\x -&amp;gt; x - fClk&lt;/code&gt;; &lt;code&gt;subtract&lt;/code&gt; flips its arguments, which is precisely
the direction — &lt;code&gt;sums&lt;/code&gt; minus &lt;code&gt;fClk&lt;/code&gt; — we want.)&lt;/p&gt;
&lt;p&gt;Now step back and notice something about how those four lines are
&lt;em&gt;written&lt;/em&gt;. &lt;code&gt;sums&lt;/code&gt; is defined above &lt;code&gt;acc&lt;/code&gt;, but &lt;code&gt;sums&lt;/code&gt; uses &lt;code&gt;acc&lt;/code&gt;; &lt;code&gt;acc&lt;/code&gt;
is defined in terms of &lt;code&gt;acc&#39;&lt;/code&gt;, which is defined below it and in terms of
&lt;code&gt;sums&lt;/code&gt; and &lt;code&gt;tick&lt;/code&gt;, which sit above. The definitions refer to each other
in a knot, and their order on the page is irrelevant. That is because
they are not steps in a procedure — they are wires in a schematic, and
a schematic has no first line:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A &lt;code&gt;where&lt;/code&gt; block of &lt;code&gt;Signal&lt;/code&gt; equations is a netlist, not a script; the
order is yours to pick because there is no order.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Trace the knot as a circuit and it is a tidy little loop: the register
&lt;code&gt;acc&lt;/code&gt; feeds the adder &lt;code&gt;sums&lt;/code&gt;; the adder feeds the comparator &lt;code&gt;tick&lt;/code&gt; and
a subtractor; the &lt;code&gt;mux&lt;/code&gt; chooses between the subtractor’s output and the
adder’s, steered by &lt;code&gt;tick&lt;/code&gt;; and its choice, &lt;code&gt;acc&#39;&lt;/code&gt;, feeds back into the
register. Register, adder, comparator, subtractor, mux, and a wire home
— a phase accumulator with a compare-and-subtract wrap.&lt;/p&gt;
&lt;figure class=&quot;ncod-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;ncod&quot; viewBox=&quot;0 0 760 306&quot; role=&quot;img&quot; aria-labelledby=&quot;ncod-t ncod-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;ncod-t&quot;&gt;The baud generator&#39;s datapath as a block diagram&lt;/title&gt;
&lt;desc id=&quot;ncod-d&quot;&gt;A register holding the phase acc feeds an adder, where the increment inc is added to form sums. The sum fans out to a comparator that tests it against the modulus f_clk (producing the oversample tick) and to a subtractor that computes sums minus f_clk. A multiplexer, steered by tick, passes sums when there is no tick and the subtracted value when there is; its output acc&#39; is fed back into the register, forming the accumulator loop.&lt;/desc&gt;
&lt;style&gt;
.ncod{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.ncod .blk{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.ncod .op{fill:var(--bg-main);stroke:var(--fg-main);stroke-width:2}
.ncod .mux{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.ncod .wire{stroke:var(--fg-main);stroke-width:2;fill:none}
.ncod .fb{stroke:var(--accent);stroke-width:2;fill:none}
.ncod .sel{stroke:var(--accent);stroke-width:2;fill:none;stroke-dasharray:5 4}
.ncod text{font-family:var(--sans)}
.ncod .lab{fill:var(--fg-main);font-size:13px}
.ncod .dim{fill:var(--fg-dim);font-size:12px}
.ncod .sig{fill:var(--fg-dim);font-family:var(--mono);font-size:12px}
.ncod .sigA{fill:var(--accent);font-family:var(--mono);font-size:12px}
.ncod .opsym{fill:var(--fg-main);font-family:var(--mono);font-size:15px}
.ncod .ah{fill:var(--fg-main)}
.ncod .aha{fill:var(--accent)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;ncod-a&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;ncod-aa&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;aha&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;rect class=&quot;blk&quot; x=&quot;60&quot; y=&quot;104&quot; width=&quot;92&quot; height=&quot;52&quot; rx=&quot;6&quot;/&gt;
&lt;circle class=&quot;op&quot; cx=&quot;206&quot; cy=&quot;130&quot; r=&quot;20&quot;/&gt;
&lt;rect class=&quot;op&quot; x=&quot;346&quot; y=&quot;70&quot; width=&quot;92&quot; height=&quot;40&quot; rx=&quot;4&quot;/&gt;
&lt;rect class=&quot;op&quot; x=&quot;346&quot; y=&quot;176&quot; width=&quot;92&quot; height=&quot;40&quot; rx=&quot;4&quot;/&gt;
&lt;polygon class=&quot;mux&quot; points=&quot;540,106 576,130 576,170 540,194&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;152&quot; y1=&quot;130&quot; x2=&quot;185&quot; y2=&quot;130&quot; marker-end=&quot;url(#ncod-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;206&quot; y1=&quot;72&quot; x2=&quot;206&quot; y2=&quot;109&quot; marker-end=&quot;url(#ncod-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;226&quot; y1=&quot;130&quot; x2=&quot;286&quot; y2=&quot;130&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M286,130 V90 H345&quot; marker-end=&quot;url(#ncod-a)&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M286,130 V196 H345&quot; marker-end=&quot;url(#ncod-a)&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M286,130 H539&quot; marker-end=&quot;url(#ncod-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;438&quot; y1=&quot;90&quot; x2=&quot;662&quot; y2=&quot;90&quot; marker-end=&quot;url(#ncod-a)&quot;/&gt;
&lt;path class=&quot;wire&quot; d=&quot;M439,196 H512 V178 H539&quot; marker-end=&quot;url(#ncod-a)&quot;/&gt;
&lt;path class=&quot;sel&quot; d=&quot;M556,90 V116&quot; marker-end=&quot;url(#ncod-aa)&quot;/&gt;
&lt;line class=&quot;fb&quot; x1=&quot;576&quot; y1=&quot;150&quot; x2=&quot;604&quot; y2=&quot;150&quot;/&gt;
&lt;path class=&quot;fb&quot; d=&quot;M604,150 V272 H106 V157&quot; marker-end=&quot;url(#ncod-aa)&quot;/&gt;
&lt;circle class=&quot;ah&quot; cx=&quot;286&quot; cy=&quot;130&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;aha&quot; cx=&quot;556&quot; cy=&quot;90&quot; r=&quot;3&quot;/&gt;
&lt;text class=&quot;lab&quot; x=&quot;106&quot; y=&quot;98&quot; text-anchor=&quot;middle&quot;&gt;register&lt;/text&gt;
&lt;text class=&quot;opsym&quot; x=&quot;106&quot; y=&quot;128&quot; text-anchor=&quot;middle&quot;&gt;acc&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;106&quot; y=&quot;146&quot; text-anchor=&quot;middle&quot;&gt;init 0&lt;/text&gt;
&lt;text class=&quot;opsym&quot; x=&quot;206&quot; y=&quot;136&quot; text-anchor=&quot;middle&quot;&gt;+&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;206&quot; y=&quot;66&quot; text-anchor=&quot;middle&quot;&gt;inc&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;168&quot; y=&quot;122&quot; text-anchor=&quot;middle&quot;&gt;acc&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;256&quot; y=&quot;122&quot; text-anchor=&quot;middle&quot;&gt;sums&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;512&quot; y=&quot;122&quot; text-anchor=&quot;middle&quot;&gt;keep&lt;/text&gt;
&lt;text class=&quot;opsym&quot; x=&quot;392&quot; y=&quot;95&quot; text-anchor=&quot;middle&quot;&gt;≥ fClk&lt;/text&gt;
&lt;text class=&quot;sig&quot; x=&quot;668&quot; y=&quot;94&quot; text-anchor=&quot;start&quot;&gt;tick&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;562&quot; y=&quot;110&quot; text-anchor=&quot;start&quot;&gt;sel&lt;/text&gt;
&lt;text class=&quot;opsym&quot; x=&quot;392&quot; y=&quot;201&quot; text-anchor=&quot;middle&quot;&gt;− fClk&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;524&quot; y=&quot;172&quot; text-anchor=&quot;middle&quot;&gt;wrap&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;558&quot; y=&quot;206&quot; text-anchor=&quot;middle&quot;&gt;mux&lt;/text&gt;
&lt;text class=&quot;sigA&quot; x=&quot;600&quot; y=&quot;144&quot; text-anchor=&quot;start&quot;&gt;acc&#39;&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;336&quot; y=&quot;290&quot; text-anchor=&quot;middle&quot;&gt;acc&#39; feeds back (the loop that remembers)&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The same four equations as a datapath. The register holds the phase &lt;code&gt;acc&lt;/code&gt;; every clock it is summed with the constant &lt;code&gt;inc&lt;/code&gt; to make &lt;code&gt;sums&lt;/code&gt;. A comparator tests &lt;code&gt;sums&lt;/code&gt; against the modulus &lt;code&gt;f_clk&lt;/code&gt;; that boolean &lt;em&gt;is&lt;/em&gt; the oversample &lt;code&gt;tick&lt;/code&gt;, and it also steers the &lt;code&gt;mux&lt;/code&gt;. A subtractor forms the wrapped phase &lt;code&gt;sums − f_clk&lt;/code&gt;. The &lt;code&gt;mux&lt;/code&gt; &lt;em&gt;keeps&lt;/em&gt; &lt;code&gt;sums&lt;/code&gt; when there is no tick and takes the wrapped value when there is; its output &lt;code&gt;acc&#39;&lt;/code&gt; loops back into the register: the accent path that makes this sequential logic rather than a lone combinational knot.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The recursion is legal for the same reason the circuit is
well-behaved: the loop passes &lt;em&gt;through&lt;/em&gt; the register. &lt;code&gt;acc&lt;/code&gt; at cycle
$n$ depends on &lt;code&gt;acc&#39;&lt;/code&gt; at cycle $n-1$, never on itself within a cycle,
because &lt;code&gt;register&lt;/code&gt; puts a clock edge in the path. A loop of pure signal
functions with no register in it would be a combinational cycle — the
one thing you may not build — and the &lt;code&gt;register 0&lt;/code&gt; is exactly what
saves it. The initial value &lt;code&gt;0&lt;/code&gt; is the power-up phase; &lt;code&gt;Dom100&lt;/code&gt; carries
no reset port, so that &lt;code&gt;0&lt;/code&gt; is set by the flip-flops’ initial state
rather than by a reset pulse, and the oscillator simply starts counting
from zero the instant the chip comes up.&lt;/p&gt;
&lt;h2 id=&quot;wrap-don-t-reset&quot;&gt;Wrap, don’t reset&lt;/h2&gt;
&lt;p&gt;One detail in &lt;code&gt;acc&#39;&lt;/code&gt; is the difference between a baud generator that
holds its rate forever and one that slowly slides off it. On a tick,
the accumulator is set to &lt;code&gt;sums - fClk&lt;/code&gt; — the overshoot — and &lt;strong&gt;not&lt;/strong&gt;
to zero.&lt;/p&gt;
&lt;p&gt;Because the accumulator climbs by &lt;code&gt;inc&lt;/code&gt; and &lt;code&gt;inc&lt;/code&gt; does not divide
&lt;code&gt;fClk&lt;/code&gt;, it almost never lands exactly on the modulus; it &lt;em&gt;crosses&lt;/em&gt; it,
overshooting by a little. That is why the test is &lt;code&gt;&amp;gt;=&lt;/code&gt; and not &lt;code&gt;==&lt;/code&gt;:
you are catching a crossing, not a coincidence. And the leftover above
the modulus is not noise to be discarded — it is the fractional part of
a bit-time that an integer counter has no other way to hold. Subtract
the modulus and you carry that fraction into the next interval, where it
nudges the following tick a clock earlier. Zero the accumulator instead
and you would throw the fraction away every tick, and the small errors
would pile up until the ticks visibly drifted.&lt;/p&gt;
&lt;p&gt;Keeping the remainder is what makes the average exact. Over one
2 Mbaud bit — fifty clocks — the accumulator gains&lt;/p&gt;
&lt;p&gt;$$ 50 \times 32 \times 10^6 = 1.6 \times 10^9 = 16 \times \left(100 \times 10^6\right) $$&lt;/p&gt;
&lt;p&gt;exactly sixteen times the modulus, so it laps exactly sixteen times and
fires exactly sixteen ticks. Not sixteen on average with a wandering
phase — sixteen, every bit, indefinitely. The individual gaps between
ticks are three clocks or four, never the fractional 3.125 any single
time, but the carried remainder does the bookkeeping so their average
is 3.125 to the clock.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Subtract the modulus, never zero the accumulator: the carried
overshoot is the fractional bit-time the integer hardware can’t
otherwise represent, kept honest from one tick to the next.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The jitter this leaves — a tick landing a single 100 MHz
clock early or late, twenty nanoseconds — is nothing by the time
sixteen of them tile a five-hundred-nanosecond bit and the receiver
reads only the center. And one more property falls out for free:
because &lt;code&gt;inc&lt;/code&gt; is smaller than &lt;code&gt;fClk&lt;/code&gt; ($32$ million against $100$
million), the value left after a wrap is smaller than &lt;code&gt;inc&lt;/code&gt;, so the
&lt;em&gt;next&lt;/em&gt; cycle’s sum cannot reach the modulus a second time. A tick is
therefore always a lone one-cycle pulse, never two back to back —
exactly the clean single-cycle enable the transmitter and receiver want
to gate on.&lt;/p&gt;
&lt;figure class=&quot;nco-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;nco&quot; viewBox=&quot;0 0 760 300&quot; role=&quot;img&quot; aria-labelledby=&quot;nco-t nco-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;nco-t&quot;&gt;The NCO phase accumulator as a sawtooth&lt;/title&gt;
&lt;desc id=&quot;nco-d&quot;&gt;A phase value climbs by a fixed increment each system clock, forming a rising ramp. Each time the ramp reaches or passes the modulus (f_clk, drawn as a dashed line) it wraps by subtracting the modulus and emits a one-cycle oversample tick, shown as a pulse below. The ramp overshoots the modulus slightly before wrapping; that overshoot is carried forward, which is why the ticks average one every 3.125 clocks with only single-clock jitter.&lt;/desc&gt;
&lt;style&gt;
.nco{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.nco .axis{stroke:var(--fg-dim);stroke-width:1.5;fill:none}
.nco .mod{stroke:var(--accent);stroke-width:2;stroke-dasharray:6 5;fill:none}
.nco .ramp{stroke:var(--fg-main);stroke-width:2.5;fill:none;stroke-linejoin:round}
.nco .dot{fill:var(--fg-main)}
.nco .pulse{stroke:var(--accent);stroke-width:2.5;fill:none}
.nco .pbase{stroke:var(--fg-dim);stroke-width:1.5;fill:none}
.nco text{font-family:var(--sans)}
.nco .lab{fill:var(--fg-main);font-size:13px}
.nco .dim{fill:var(--fg-dim);font-size:13px}
.nco .acc{fill:var(--accent);font-size:13px}
.nco .ah{fill:var(--fg-main)}
.nco .aha{fill:var(--accent)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;nco-a&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;nco-aa&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;aha&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;line class=&quot;axis&quot; x1=&quot;90&quot; y1=&quot;45&quot; x2=&quot;90&quot; y2=&quot;180&quot;/&gt;
&lt;line class=&quot;axis&quot; x1=&quot;90&quot; y1=&quot;180&quot; x2=&quot;655&quot; y2=&quot;180&quot;/&gt;
&lt;line class=&quot;mod&quot; x1=&quot;90&quot; y1=&quot;80&quot; x2=&quot;650&quot; y2=&quot;80&quot;/&gt;
&lt;text class=&quot;acc&quot; x=&quot;96&quot; y=&quot;74&quot;&gt;modulus = f_clk&lt;/text&gt;
&lt;polyline class=&quot;ramp&quot; points=&quot;100,148 152,116 204,84 256,52 256,152 308,120 360,88 412,56 412,156 464,124 516,92 568,60 568,160 620,128&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;100&quot; cy=&quot;148&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;152&quot; cy=&quot;116&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;204&quot; cy=&quot;84&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;256&quot; cy=&quot;52&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;308&quot; cy=&quot;120&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;360&quot; cy=&quot;88&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;412&quot; cy=&quot;56&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;464&quot; cy=&quot;124&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;516&quot; cy=&quot;92&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;568&quot; cy=&quot;60&quot; r=&quot;3&quot;/&gt;
&lt;circle class=&quot;dot&quot; cx=&quot;620&quot; cy=&quot;128&quot; r=&quot;3&quot;/&gt;
&lt;line class=&quot;axis&quot; x1=&quot;150&quot; y1=&quot;150&quot; x2=&quot;150&quot; y2=&quot;118&quot; marker-end=&quot;url(#nco-a)&quot;/&gt;
&lt;text class=&quot;dim&quot; x=&quot;158&quot; y=&quot;140&quot;&gt;+ inc each clock&lt;/text&gt;
&lt;text class=&quot;acc&quot; x=&quot;583&quot; y=&quot;150&quot;&gt;keep the overshoot&lt;/text&gt;
&lt;line class=&quot;pbase&quot; x1=&quot;90&quot; y1=&quot;225&quot; x2=&quot;640&quot; y2=&quot;225&quot;/&gt;
&lt;path class=&quot;pulse&quot; d=&quot;M250,225 L250,205 L262,205 L262,225&quot;/&gt;
&lt;path class=&quot;pulse&quot; d=&quot;M406,225 L406,205 L418,205 L418,225&quot;/&gt;
&lt;path class=&quot;pulse&quot; d=&quot;M562,225 L562,205 L574,205 L574,225&quot;/&gt;
&lt;text class=&quot;acc&quot; x=&quot;596&quot; y=&quot;214&quot;&gt;tick&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;334&quot; y=&quot;245&quot; text-anchor=&quot;middle&quot;&gt;3&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;490&quot; y=&quot;245&quot; text-anchor=&quot;middle&quot;&gt;3&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;30&quot; y=&quot;116&quot; transform=&quot;rotate(-90 30 116)&quot; text-anchor=&quot;middle&quot;&gt;phase (accumulator)&lt;/text&gt;
&lt;text class=&quot;dim&quot; x=&quot;360&quot; y=&quot;272&quot; text-anchor=&quot;middle&quot;&gt;system clock cycles (100 MHz)&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The baud generator as a numerically-controlled oscillator. The phase &lt;code&gt;acc&lt;/code&gt; gains &lt;code&gt;inc = 16·baud&lt;/code&gt; every system clock; when the candidate sum &lt;code&gt;sums&lt;/code&gt; reaches the modulus &lt;code&gt;f_clk&lt;/code&gt; the &lt;code&gt;mux&lt;/code&gt; subtracts the modulus (keeping the overshoot rather than zeroing) and a one-cycle &lt;code&gt;tick&lt;/code&gt; fires. Because the overshoot is carried, the gaps run three clocks and (one in eight) four, averaging exactly &lt;em&gt;50/16 = 3.125&lt;/em&gt;, so a 50-clock bit always contains sixteen ticks. It is an enable inside &lt;code&gt;Dom100&lt;/code&gt;, not a second clock.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;the-one-test&quot;&gt;The one test&lt;/h2&gt;
&lt;p&gt;The CRC unit came with a small battery of tests; the baud generator
gets one, because there is only one thing to check — does it tick at
the right rate?&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;baudTicks&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Int&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Bool&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;baudTicks n &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; sampleN n &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;oversampleTick &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;SNat&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;2_000_000&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Dom100&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;testCase &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;oversample tick rate is 16x baud (~32 MHz avg)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  let&lt;/span&gt;&lt;span&gt; n &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 10000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;length &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;L.&lt;/span&gt;&lt;span&gt;filter id &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;baudTicks n&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;   in&lt;/span&gt;&lt;span&gt; assertBool &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;tick count = &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;&amp;gt;&lt;/span&gt;&lt;span&gt; show c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;, expected ~3200&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;abs &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 3200&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The whole apparatus is &lt;code&gt;sampleN&lt;/code&gt;. &lt;code&gt;sampleN n sig&lt;/code&gt; runs the circuit for
&lt;code&gt;n&lt;/code&gt; cycles and hands back a list of its &lt;code&gt;n&lt;/code&gt; outputs — and that is the
line where all the primer’s talk about &lt;code&gt;Signal&lt;/code&gt; being “an endless
stream, one sample per cycle” pays off. A &lt;code&gt;Signal&lt;/code&gt; really is, morally,
an infinite lazy list; &lt;code&gt;sampleN n&lt;/code&gt; is &lt;code&gt;take n&lt;/code&gt; on it — that is, &lt;em&gt;take
the first &lt;code&gt;n&lt;/code&gt; elements from the list&lt;/em&gt;. So “simulate ten
thousand clock cycles of this oscillator” is not a call to a hardware
simulator with its process handshake and its waveform dump — it is a
list traversal, evaluated by plain Haskell.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Simulating ten thousand cycles is a &lt;code&gt;take 10000&lt;/code&gt; on a lazy list —
that, and nothing more elaborate, is why the whole suite finishes
before you blink.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The assertion just counts the &lt;code&gt;True&lt;/code&gt;s. Over ten thousand cycles the
oscillator should fire $10{,}000 \times \tfrac{32}{100} = 3200$ times,
and the test allows $\pm 2$ for the partial teeth at the ends of the
window, where the count can miss catching a tick that straddles the
boundary. It is a blunt check, but it pins the one number that matters:
the tick rate is what it claims to be.&lt;/p&gt;
&lt;p&gt;The subtler guarantee — that the ticks land &lt;em&gt;evenly enough&lt;/em&gt; for a
receiver to sample a transmitter’s bits dead-center — is not tested
here in isolation. It is tested two posts from now, by the full-UART
loopback, where a real fifty-clock bit must contain sixteen real ticks
or the receiver reads the wrong thing. This oscillator is one end of
that keystone test; we will meet the other end when the receiver is
built.&lt;/p&gt;
&lt;h2 id=&quot;what-we-read&quot;&gt;What we read&lt;/h2&gt;
&lt;p&gt;Fifteen lines of logic, one exported name, and no state machine at all:
a phase accumulator — a &lt;code&gt;register&lt;/code&gt; fed back through an adder, a
comparator, a subtractor, and a &lt;code&gt;mux&lt;/code&gt; — that reads its own clock
frequency out of the domain type, climbs by a compile-time increment,
and wraps by subtracting the modulus so it keeps its overshoot and
fires exactly sixteen ticks per bit with no long-term drift, no PLL,
and no second clock domain. What it hands the rest of the UART is an
&lt;em&gt;enable&lt;/em&gt;, not a &lt;em&gt;clock&lt;/em&gt;: a &lt;code&gt;Bool&lt;/code&gt; that says &lt;em&gt;now&lt;/em&gt; sixteen times a bit,
inside the one 100 MHz clock the whole design already runs
on.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt; promised that &lt;code&gt;Signal&lt;/code&gt; would stop being a footnote in the
block posts, and this is the block where it does. &lt;code&gt;register&lt;/code&gt;, &lt;code&gt;&amp;lt;$&amp;gt;&lt;/code&gt;
over a &lt;code&gt;Signal&lt;/code&gt;, &lt;code&gt;mux&lt;/code&gt;, and a feedback loop tied through a flip-flop are
the sequential vocabulary the rest of the series is built from — the
CRC’s combinational world, now with a clock edge in it.&lt;/p&gt;
&lt;p&gt;Next we start spending the heartbeat. The transmitter takes this tick
and a byte and drives a line high and low in an 8N1 frame — and it is
where &lt;code&gt;mealy&lt;/code&gt; finally climbs to the top of a module, exactly as the
primer said it would, lifting a pure &lt;code&gt;TxS -&amp;gt; … -&amp;gt; (TxS, …)&lt;/code&gt; step into a
clocked machine. The oscillator counts; the transmitter is the first
thing to march to it.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-singleton&quot;&gt;
&lt;p&gt;A &lt;strong&gt;singleton&lt;/strong&gt; is the bridge between a type-level number
and a value you can pass around. &lt;code&gt;SNat n&lt;/code&gt; is a type with exactly one
inhabitant, the value that stands for the type-level natural &lt;code&gt;n&lt;/code&gt;, so
writing &lt;code&gt;SNat @2_000_000&lt;/code&gt; hands a function a runtime token that still
carries &lt;code&gt;2000000&lt;/code&gt; in its type. &lt;code&gt;snatToNum&lt;/code&gt; walks it back down to an
ordinary &lt;code&gt;Integer&lt;/code&gt; (or any &lt;code&gt;Num&lt;/code&gt;), while the &lt;code&gt;KnownNat&lt;/code&gt; constraint is
the compiler’s proof that the number is statically known. It is the
round trip the primer’s “numbers can live in types” implies but does
not spell out: up into the type to be checked, back down to a wire to
be used. &lt;a href=&quot;#fr-singleton-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-nco&quot;&gt;
&lt;p&gt;A &lt;strong&gt;numerically-controlled oscillator&lt;/strong&gt; (NCO) — the engine of
&lt;strong&gt;direct digital synthesis&lt;/strong&gt; (DDS) — comes from radio and signal
processing, where it synthesizes an arbitrary frequency from a single
reference clock by accumulating a &lt;em&gt;phase word&lt;/em&gt; each cycle and using the
high bits as an angle into a sine table. Strip away the sine lookup and
keep only the accumulator and its overflow and you are left with a
fractional clock divider — precisely this module. In a textbook DDS the
modulus is a power of two, so the “wrap” is just the natural carry-out
of a fixed-width adder and the output frequency is the tidy fraction
&lt;code&gt;inc / 2^N&lt;/code&gt; of the clock; here the modulus is &lt;code&gt;fClk&lt;/code&gt;, an arbitrary
number rather than a power of two, so the wrap is an explicit
compare-and-subtract — but the principle, phase accumulates and the
overflow is your tick, is the same one that clocks software-defined
radios, benchtop function generators, and any FPGA that needs a
frequency its PLL cannot land on exactly. &lt;a href=&quot;#fr-nco-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-section&quot;&gt;
&lt;p&gt;&lt;code&gt;(&amp;gt;= fClk)&lt;/code&gt; is an operator &lt;strong&gt;section&lt;/strong&gt; — an infix operator in
parentheses with one operand already supplied, standing for a function
still waiting for the other. The side you fill is the side that stays
put: &lt;code&gt;(&amp;gt;= fClk)&lt;/code&gt; is the &lt;em&gt;right&lt;/em&gt; section &lt;code&gt;\x -&amp;gt; x &amp;gt;= fClk&lt;/code&gt;, “has &lt;code&gt;x&lt;/code&gt;
reached the modulus?”, whereas &lt;code&gt;(fClk &amp;gt;=)&lt;/code&gt; is the &lt;em&gt;left&lt;/em&gt; section
&lt;code&gt;\x -&amp;gt; fClk &amp;gt;= x&lt;/code&gt;, “is the modulus at least &lt;code&gt;x&lt;/code&gt;?” — the opposite
comparison, which would fire the tick on the wrong side of the
threshold. Which operand you park where is load-bearing in general; the
&lt;code&gt;(+ inc)&lt;/code&gt; a line earlier got away with either order &lt;em&gt;only&lt;/em&gt; because
addition commutes, so there &lt;code&gt;(+ inc)&lt;/code&gt; and &lt;code&gt;(inc +)&lt;/code&gt; genuinely denote the
same function. Order-sensitive operators grant no such reprieve — &lt;code&gt;&amp;gt;=&lt;/code&gt;,
&lt;code&gt;-&lt;/code&gt;, and &lt;code&gt;div&lt;/code&gt; all care about the side — which is, incidentally, why the
wrap spells its subtraction &lt;code&gt;subtract fClk&lt;/code&gt; rather than &lt;code&gt;(- fClk)&lt;/code&gt;: the
latter would parse as unary negation, not “subtract &lt;code&gt;fClk&lt;/code&gt;.” &lt;a href=&quot;#fr-section-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Tamal: The CRC block</title>
    <published>2026-07-20T09:00:00+00:00</published>
    <updated>2026-07-20T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-crc/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-crc/</id>
    <summary type="html">&lt;p&gt;As mentioned before &lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/tamal&quot;&gt;Tamal&lt;/a&gt; is an FPGA eSPI exerciser with its
gateware written in &lt;a rel=&quot;external&quot; href=&quot;https://www.haskell.org/&quot;&gt;Haskell&lt;/a&gt; and compiled to verilog with &lt;a rel=&quot;external&quot; href=&quot;https://clash-lang.org&quot;&gt;Clash&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In a &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;previous&lt;/a&gt; post we presented a very short introduction on
Haskell, just enough such that the code blocks below don’t look too
foreign. Today we start looking at one of the simplest blocks within
Tamal: its CRC8 engine.&lt;/p&gt;
&lt;p&gt;The code is so small that it fits easily in less than one screenfull
of content on a regular smartphone screen, but as we will see, it
hides a good amount of interesting operations due to Haskell’s
expressiveness.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;As mentioned before &lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/tamal&quot;&gt;Tamal&lt;/a&gt; is an FPGA eSPI exerciser with its
gateware written in &lt;a rel=&quot;external&quot; href=&quot;https://www.haskell.org/&quot;&gt;Haskell&lt;/a&gt; and compiled to verilog with &lt;a rel=&quot;external&quot; href=&quot;https://clash-lang.org&quot;&gt;Clash&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In a &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;previous&lt;/a&gt; post we presented a very short introduction on
Haskell, just enough such that the code blocks below don’t look too
foreign. Today we start looking at one of the simplest blocks within
Tamal: its CRC8 engine.&lt;/p&gt;
&lt;p&gt;The code is so small that it fits easily in less than one screenfull
of content on a regular smartphone screen, but as we will see, it
hides a good amount of interesting operations due to Haskell’s
expressiveness.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;the-entire-source&quot;&gt;The entire source&lt;/h2&gt;
&lt;p&gt;As promised, the entire source fits in a screenfull:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Crc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; crc8Update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Clash.Prelude&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;crc8Update&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;crc8Update crc byte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; foldl step crc &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;unpack byte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Vec&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;  step&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  step c inBit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; feedbackBit &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; high &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; shifted &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;xor&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x07&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; shifted&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;   where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    feedbackBit &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; msb c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;xor&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span&gt; inBit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    shifted &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;shiftL&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first thing we notice are the top three lines:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tamal.Crc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; crc8Update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A Haskell file opens by naming itself. &lt;code&gt;module Tamal.Crc&lt;/code&gt;
declares this module’s name, and the name is not free-form: it
mirrors the path on disk. &lt;code&gt;Tamal.Crc&lt;/code&gt; lives in
&lt;code&gt;src/Tamal/Crc.hs&lt;/code&gt;, a dot for each directory separator, the same
way a Rust &lt;code&gt;crate::module&lt;/code&gt; path or a Java package tracks its
folder. The compiler leans on that correspondence to find
modules, so the name and the location can never quietly drift
apart.&lt;/p&gt;
&lt;p&gt;The parentheses are the more interesting part. They are the
module’s &lt;strong&gt;export list&lt;/strong&gt; — the complete, deliberate enumeration of
what the outside world is allowed to see:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A module is a wall with a door in it, and the export list is
the door.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Only &lt;code&gt;crc8Update&lt;/code&gt; is on that list, so only &lt;code&gt;crc8Update&lt;/code&gt; leaves
the file. Everything else — and in a moment we will meet &lt;code&gt;step&lt;/code&gt;,
the helper that does the actual bit-twiddling — stays private,
sealed behind the wall. Had the author written
&lt;code&gt;module Tamal.Crc where&lt;/code&gt; with no list at all, Haskell would
export &lt;em&gt;everything&lt;/em&gt; by default, &lt;code&gt;step&lt;/code&gt; included, and the module’s
public surface would sprawl to match its implementation. The
explicit list is a choice to expose one name and hide the rest.&lt;/p&gt;
&lt;p&gt;That choice buys more than tidiness. &lt;code&gt;step&lt;/code&gt; is an internal detail
of how &lt;em&gt;this&lt;/em&gt; CRC happens to be computed; nothing outside should
depend on it, name it, or test it directly. Keeping it off the
export list reserves the author’s right to rewrite it —
table-driven, byte-parallel, whatever comes later — without
breaking a single caller. The public contract is exactly one
function wide. (It is also why the tests we read at the end
exercise the byte-level &lt;code&gt;crc8Update&lt;/code&gt; and never reach for &lt;code&gt;step&lt;/code&gt;:
&lt;code&gt;step&lt;/code&gt; is not theirs to reach.)&lt;/p&gt;
&lt;h2 id=&quot;the-prelude-swap&quot;&gt;The prelude swap&lt;/h2&gt;
&lt;p&gt;The line under the module header looks like ceremony and is
anything but:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Clash.Prelude&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every Haskell file begins in a world someone else furnished. That
furniture is the &lt;em&gt;prelude&lt;/em&gt;: the batch of names, types, and
functions that are simply &lt;em&gt;there&lt;/em&gt; before you import anything.
Ordinary Haskell hands you the standard &lt;code&gt;Prelude&lt;/code&gt; — &lt;code&gt;Int&lt;/code&gt;,
&lt;code&gt;Bool&lt;/code&gt;, lists, &lt;code&gt;foldl&lt;/code&gt;, &lt;code&gt;map&lt;/code&gt;, the familiar fittings.
&lt;code&gt;import Clash.Prelude&lt;/code&gt; throws that set out and moves a different
one in.&lt;/p&gt;
&lt;p&gt;What arrives instead is a hardware vocabulary: the &lt;code&gt;Bit&lt;/code&gt;,
&lt;code&gt;BitVector&lt;/code&gt;, &lt;code&gt;Vec&lt;/code&gt;, and &lt;code&gt;Signal&lt;/code&gt; types from the &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt;, the
&lt;code&gt;high&lt;/code&gt; and &lt;code&gt;low&lt;/code&gt; bit literals, &lt;code&gt;msb&lt;/code&gt;, &lt;code&gt;shiftL&lt;/code&gt;, &lt;code&gt;xor&lt;/code&gt;, &lt;code&gt;unpack&lt;/code&gt; —
and versions of the everyday functions, &lt;code&gt;foldl&lt;/code&gt; among them, that
operate on &lt;code&gt;Vec&lt;/code&gt;s and lower to gates. This one line is the
difference between a program that computes and a description that
becomes a circuit:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Swapping the prelude is what turns a Haskell file into a
hardware description.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Clash leans on the swap so hard that it &lt;em&gt;shadows&lt;/em&gt; the list
versions of common names. That has a visible consequence we will
run into in the test file: there, &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;++&lt;/code&gt; would mean the
&lt;code&gt;Vec&lt;/code&gt; versions, so gluing plain lists together has to reach for a
list comprehension and &lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt; instead. File that away; for now the
thing to see is that the lone &lt;code&gt;foldl&lt;/code&gt; two lines down is one of
these swapped names. It is the &lt;code&gt;Vec&lt;/code&gt; fold, and it will unroll into
wiring rather than spin a runtime loop. Nothing in the module
works without this import; it is the line that says &lt;em&gt;compile me to
hardware&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-type-and-why-it-has-to-be-this-one&quot;&gt;The type, and why it has to be this one&lt;/h2&gt;
&lt;p&gt;Now the signature, which under the primer’s reading is already
half the documentation:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;crc8Update&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two arrows, so — remembering that &lt;code&gt;-&amp;gt;&lt;/code&gt; associates to the right and
every function takes one argument — this is “give me a
&lt;code&gt;BitVector 8&lt;/code&gt;, then another &lt;code&gt;BitVector 8&lt;/code&gt;, and I hand back a
&lt;code&gt;BitVector 8&lt;/code&gt;.” Name the three roles and the intent appears: the
first argument is the &lt;strong&gt;running CRC&lt;/strong&gt;, the second is the
&lt;strong&gt;incoming byte&lt;/strong&gt;, and the result is the &lt;strong&gt;new running CRC&lt;/strong&gt;. It
is an accumulator update, one byte at a time.&lt;/p&gt;
&lt;p&gt;The type could have been written other ways, and the two decisions
behind it are both load-bearing.&lt;/p&gt;
&lt;p&gt;The first is &lt;code&gt;BitVector 8&lt;/code&gt; rather than &lt;code&gt;Unsigned 8&lt;/code&gt; or a &lt;code&gt;Word&lt;/code&gt;.
A &lt;code&gt;BitVector 8&lt;/code&gt; is eight wires with &lt;em&gt;no&lt;/em&gt; arithmetic meaning
attached — the type of a thing you &lt;code&gt;xor&lt;/code&gt;, shift, and index into,
not a thing you add. &lt;code&gt;Unsigned 8&lt;/code&gt; would have invited &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;*&lt;/code&gt;, and
carry, arithmetic this function never performs. Choosing
&lt;code&gt;BitVector&lt;/code&gt; states plainly that a CRC is bit-manipulation, not
counting, and the type system then holds the code to that promise:
reach for &lt;code&gt;+&lt;/code&gt; on a &lt;code&gt;BitVector&lt;/code&gt; by accident and the compiler
objects. It is the primer’s “wire-width checker,” applied one
level up — the &lt;em&gt;kind&lt;/em&gt; of value, not just its width, is part of the
contract.&lt;/p&gt;
&lt;p&gt;The second decision is the argument order: running CRC first, byte
second. That order is chosen so the function is &lt;strong&gt;fold-shaped&lt;/strong&gt;.
Recall currying — &lt;code&gt;crc8Update crc&lt;/code&gt; is a legal value on its own, a
function still waiting for a byte, meaning “advance &lt;em&gt;this&lt;/em&gt; running
CRC by one more byte.” That is precisely the shape a left fold
wants for its combining step, and it is exactly how the tests will
run the CRC across a whole message: &lt;code&gt;foldl crc8Update 0 bytes&lt;/code&gt;
reads left to right as “start at zero, fold in each byte.” Swap
the arguments and that reading falls apart. The signature is
written to make the fold obvious.&lt;/p&gt;
&lt;p&gt;One last thing the type says by saying nothing. Every &lt;code&gt;BitVector 8&lt;/code&gt;
in, produces a definite &lt;code&gt;BitVector 8&lt;/code&gt; out — no missing cases, no
error, no &lt;code&gt;Maybe&lt;/code&gt;. That totality &lt;em&gt;is&lt;/em&gt; a truth table, $f : (\text{crc},
\text{byte}) \to \text{crc}$, same inputs giving the same output every
time, which is the primer’s definition of combinational logic
verbatim. &lt;code&gt;crc8Update&lt;/code&gt; is a block of gates wearing a Haskell type. But
the body only makes sense once we have the one idea the whole file is
a transcription of.&lt;/p&gt;
&lt;h2 id=&quot;a-crc-is-a-remainder&quot;&gt;A CRC is a remainder&lt;/h2&gt;
&lt;p&gt;Strip the acronym away and a CRC is a &lt;strong&gt;remainder&lt;/strong&gt; — the leftover
from a division, carried out in a number system with exactly two
digits.&lt;/p&gt;
&lt;p&gt;Here is the whole trick. Take a stream of bits and read it as the
coefficients of a polynomial: the byte &lt;code&gt;00000111&lt;/code&gt; becomes
$x^2 + x + 1$, a one in each place where the polynomial has a
term. Bytes, messages, whole packets — all just polynomials whose
coefficients are either $0$ or $1$. Arithmetic on these
coefficients is done modulo two, which collapses into one
startling simplification: $1 + 1 = 0$, there are no carries, and
&lt;strong&gt;addition, subtraction, and XOR are all the same operation&lt;/strong&gt;.
That single fact is why CRC hardware is cheap — every
“subtraction” below is just an &lt;code&gt;xor&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The divisor in this division has a name, the &lt;strong&gt;generator
polynomial&lt;/strong&gt;. Tamal’s is the one the module comment spells out,&lt;/p&gt;
&lt;p&gt;$$ G(x) = x^8 + x^2 + x + 1 $$&lt;/p&gt;
&lt;p&gt;whose coefficients, written as bits, are &lt;code&gt;1 0000 0111&lt;/code&gt; — nine bits,
&lt;code&gt;0x107&lt;/code&gt;, degree eight. Look at the low eight of those bits: &lt;code&gt;0000 0111&lt;/code&gt;, &lt;code&gt;0x07&lt;/code&gt;, the very constant sitting in the source. The ninth
bit, the $x^8$ term, is the one that will “fall off the top” of an
eight-bit register in a moment; hold onto it.&lt;/p&gt;
&lt;p&gt;To take the CRC of a message $M$ is to do one thing:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Shift the message up by eight bits, divide by $G$, and keep the
remainder.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In symbols, $C = (M \cdot x^8) \bmod G$. The shift by $x^8$ —
eight zero bits of headroom appended below the message — is what
makes room for an eight-bit remainder to live, and it is also,
quietly, the thing that will make the residue trick at the end of
this post work. A well-chosen $G$ has the property that almost any
corruption of the message changes the remainder, so the remainder
acts as a compact fingerprint: change the packet, change the
fingerprint, and the receiver notices.&lt;/p&gt;
&lt;p&gt;The smallest possible example is worth doing by hand, because we
are about to meet it again as a unit test. Let $M$ be the one-byte
message &lt;code&gt;0x01&lt;/code&gt;, which as a polynomial is simply $1$. Shift up by
$x^8$ and reduce modulo $G$:&lt;/p&gt;
&lt;p&gt;$$ x^8 \equiv x^2 + x + 1 \pmod{G} $$&lt;/p&gt;
&lt;p&gt;because $x^8 = G + (x^2 + x + 1)$ and $G \equiv 0$ by definition of
“modulo $G$.” The remainder is $x^2 + x + 1$, which is &lt;code&gt;0000 0111&lt;/code&gt;,
which is &lt;code&gt;0x07&lt;/code&gt;. Remember that number. The code is about to
reproduce it out of shifts and XORs, and the two answers are going
to match.&lt;/p&gt;
&lt;h2 id=&quot;the-left-fold&quot;&gt;The left fold&lt;/h2&gt;
&lt;p&gt;With the division in mind, the body is short:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;crc8Update crc byte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; foldl step crc &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;unpack byte &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Vec&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two moves live on that line: turn the byte into bits, then fold
over them.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;unpack byte :: Vec 8 Bit&lt;/code&gt; takes the opaque eight-wire &lt;code&gt;BitVector&lt;/code&gt;
and re-presents it as a &lt;code&gt;Vec 8 Bit&lt;/code&gt;, a sequence of eight
individual &lt;code&gt;Bit&lt;/code&gt;s you can walk one at a time. The annotation &lt;code&gt;:: Vec 8 Bit&lt;/code&gt; is needed because &lt;code&gt;unpack&lt;/code&gt; is polymorphic — it will
produce whatever type the surrounding code asks for — so we pin
the target explicitly. The ordering is a convention worth naming:
index &lt;code&gt;0&lt;/code&gt; of the vector is the most significant bit, so walking
the vector head to tail walks the byte &lt;strong&gt;MSB first&lt;/strong&gt;, which is
what this CRC (and eSPI, and SMBus) require.&lt;/p&gt;
&lt;p&gt;Then the fold. From a purely functional standpoint a left fold&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-fold-1&quot;&gt;&lt;a href=&quot;#fn-fold&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;
is the most ordinary thing in the world, and it has nothing to do
with mutation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;foldl&lt;/code&gt; threads an accumulator through a sequence, left to
right, handing the running result and the next element to a
combining function, over and over.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Written out, &lt;code&gt;foldl step crc [b0, b1, .. b7]&lt;/code&gt; is nothing but a
nest of calls:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;step (step (step (.. step (step crc b0) b1 ..) b6) b7)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;crc&lt;/code&gt; seeds the accumulator. &lt;code&gt;step&lt;/code&gt; folds in &lt;code&gt;b0&lt;/code&gt; to produce a
&lt;em&gt;new&lt;/em&gt; accumulator; that value feeds into &lt;code&gt;step&lt;/code&gt; alongside &lt;code&gt;b1&lt;/code&gt; to
produce the next; and so on down the byte until &lt;code&gt;b7&lt;/code&gt; yields the
result. Nothing is ever overwritten. Each &lt;code&gt;step&lt;/code&gt; &lt;strong&gt;returns&lt;/strong&gt;&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-returns-1&quot;&gt;&lt;a href=&quot;#fn-returns&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;
a fresh CRC value and the previous one is simply never referenced
again. What in C is a register clobbered inside a loop is here a value
handed along a chain of pure functions — the same dance, performed
without a single mutation. This is the “mapping, not procedure” point
from the primer made concrete: the accumulator doesn’t &lt;em&gt;change&lt;/em&gt;, it is
&lt;em&gt;replaced&lt;/em&gt;, eight times, by eight applications of one pure function.&lt;/p&gt;
&lt;p&gt;And because the vector has a length the compiler knows — &lt;code&gt;8&lt;/code&gt;, right
there in &lt;code&gt;Vec 8 Bit&lt;/code&gt; — the hardware reading is the one the primer
promised:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A fold over a fixed-size &lt;code&gt;Vec&lt;/code&gt; is a pipeline unrolled in space,
not a loop unrolled in time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Clash does not synthesize a counter and a loop. It lays down eight
copies of &lt;code&gt;step&lt;/code&gt;’s logic, the output of each feeding the next, a
chain of combinational stages that transforms the whole byte
within a single clock. That chain is a shift register with XOR
taps — a &lt;strong&gt;linear-feedback shift register&lt;/strong&gt;, the textbook circuit
for a bit-serial CRC — except Clash &lt;em&gt;derived&lt;/em&gt; it from a &lt;code&gt;foldl&lt;/code&gt;
instead of asking anyone to draw it. The accumulator is the
register’s contents, &lt;code&gt;step&lt;/code&gt; is the tap logic, and the fold is the
wiring between the stages.&lt;/p&gt;
&lt;figure class=&quot;lfsr-fig&quot; style=&quot;margin:2rem 0&quot;&gt;
&lt;svg class=&quot;lfsr&quot; viewBox=&quot;0 0 760 200&quot; role=&quot;img&quot; aria-labelledby=&quot;lfsr-t lfsr-d&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;title id=&quot;lfsr-t&quot;&gt;CRC-8 Galois shift register for polynomial 0x07&lt;/title&gt;
&lt;desc id=&quot;lfsr-d&quot;&gt;Eight one-bit cells, b7 (the MSB) on the left through b0 (the LSB) on the right. Data shifts left. The MSB is XORed with the incoming message bit to form the feedback, which is folded back into the cells at bit positions 2, 1 and 0 — the set bits of the polynomial 0x07 = x squared plus x plus 1.&lt;/desc&gt;
&lt;style&gt;
.lfsr{max-width:760px;width:100%;height:auto;display:block;margin:0 auto}
.lfsr .ff{fill:var(--bg-dim);stroke:var(--fg-main);stroke-width:2}
.lfsr .wire{stroke:var(--fg-main);stroke-width:2;fill:none}
.lfsr .fb{stroke:var(--accent);stroke-width:2;fill:none}
.lfsr .gate{fill:var(--bg-main);stroke:var(--accent);stroke-width:2}
.lfsr .plus{stroke:var(--accent);stroke-width:2}
.lfsr .node{fill:var(--accent)}
.lfsr text{font-family:var(--sans)}
.lfsr .bit{fill:var(--fg-main);font-family:var(--mono);font-size:16px}
.lfsr .idx{fill:var(--fg-dim);font-size:13px}
.lfsr .lead{fill:var(--fg-main);font-size:13px}
.lfsr .tap{fill:var(--code-fg);font-family:var(--mono);font-size:13px}
.lfsr .ah{fill:var(--fg-main)}
.lfsr .ahf{fill:var(--accent)}
&lt;/style&gt;
&lt;defs&gt;
&lt;marker id=&quot;lfsr-a&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ah&quot;/&gt;&lt;/marker&gt;
&lt;marker id=&quot;lfsr-af&quot; markerWidth=&quot;8&quot; markerHeight=&quot;6&quot; refX=&quot;6&quot; refY=&quot;3&quot; orient=&quot;auto&quot;&gt;&lt;path d=&quot;M0,0 L6,3 L0,6 Z&quot; class=&quot;ahf&quot;/&gt;&lt;/marker&gt;
&lt;/defs&gt;
&lt;line class=&quot;wire&quot; x1=&quot;133&quot; y1=&quot;144&quot; x2=&quot;101&quot; y2=&quot;144&quot; marker-end=&quot;url(#lfsr-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;221&quot; y1=&quot;144&quot; x2=&quot;189&quot; y2=&quot;144&quot; marker-end=&quot;url(#lfsr-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;309&quot; y1=&quot;144&quot; x2=&quot;277&quot; y2=&quot;144&quot; marker-end=&quot;url(#lfsr-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;397&quot; y1=&quot;144&quot; x2=&quot;365&quot; y2=&quot;144&quot; marker-end=&quot;url(#lfsr-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;485&quot; y1=&quot;144&quot; x2=&quot;453&quot; y2=&quot;144&quot; marker-end=&quot;url(#lfsr-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;573&quot; y1=&quot;144&quot; x2=&quot;541&quot; y2=&quot;144&quot; marker-end=&quot;url(#lfsr-a)&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;661&quot; y1=&quot;144&quot; x2=&quot;629&quot; y2=&quot;144&quot; marker-end=&quot;url(#lfsr-a)&quot;/&gt;
&lt;rect class=&quot;ff&quot; x=&quot;45&quot; y=&quot;116&quot; width=&quot;54&quot; height=&quot;56&quot; rx=&quot;6&quot;/&gt;
&lt;rect class=&quot;ff&quot; x=&quot;133&quot; y=&quot;116&quot; width=&quot;54&quot; height=&quot;56&quot; rx=&quot;6&quot;/&gt;
&lt;rect class=&quot;ff&quot; x=&quot;221&quot; y=&quot;116&quot; width=&quot;54&quot; height=&quot;56&quot; rx=&quot;6&quot;/&gt;
&lt;rect class=&quot;ff&quot; x=&quot;309&quot; y=&quot;116&quot; width=&quot;54&quot; height=&quot;56&quot; rx=&quot;6&quot;/&gt;
&lt;rect class=&quot;ff&quot; x=&quot;397&quot; y=&quot;116&quot; width=&quot;54&quot; height=&quot;56&quot; rx=&quot;6&quot;/&gt;
&lt;rect class=&quot;ff&quot; x=&quot;485&quot; y=&quot;116&quot; width=&quot;54&quot; height=&quot;56&quot; rx=&quot;6&quot;/&gt;
&lt;rect class=&quot;ff&quot; x=&quot;573&quot; y=&quot;116&quot; width=&quot;54&quot; height=&quot;56&quot; rx=&quot;6&quot;/&gt;
&lt;rect class=&quot;ff&quot; x=&quot;661&quot; y=&quot;116&quot; width=&quot;54&quot; height=&quot;56&quot; rx=&quot;6&quot;/&gt;
&lt;text class=&quot;bit&quot; x=&quot;72&quot; y=&quot;150&quot; text-anchor=&quot;middle&quot;&gt;b7&lt;/text&gt;
&lt;text class=&quot;bit&quot; x=&quot;160&quot; y=&quot;150&quot; text-anchor=&quot;middle&quot;&gt;b6&lt;/text&gt;
&lt;text class=&quot;bit&quot; x=&quot;248&quot; y=&quot;150&quot; text-anchor=&quot;middle&quot;&gt;b5&lt;/text&gt;
&lt;text class=&quot;bit&quot; x=&quot;336&quot; y=&quot;150&quot; text-anchor=&quot;middle&quot;&gt;b4&lt;/text&gt;
&lt;text class=&quot;bit&quot; x=&quot;424&quot; y=&quot;150&quot; text-anchor=&quot;middle&quot;&gt;b3&lt;/text&gt;
&lt;text class=&quot;bit&quot; x=&quot;512&quot; y=&quot;150&quot; text-anchor=&quot;middle&quot;&gt;b2&lt;/text&gt;
&lt;text class=&quot;bit&quot; x=&quot;600&quot; y=&quot;150&quot; text-anchor=&quot;middle&quot;&gt;b1&lt;/text&gt;
&lt;text class=&quot;bit&quot; x=&quot;688&quot; y=&quot;150&quot; text-anchor=&quot;middle&quot;&gt;b0&lt;/text&gt;
&lt;text class=&quot;idx&quot; x=&quot;72&quot; y=&quot;190&quot; text-anchor=&quot;middle&quot;&gt;MSB&lt;/text&gt;
&lt;text class=&quot;idx&quot; x=&quot;688&quot; y=&quot;190&quot; text-anchor=&quot;middle&quot;&gt;LSB&lt;/text&gt;
&lt;line class=&quot;fb&quot; x1=&quot;72&quot; y1=&quot;116&quot; x2=&quot;72&quot; y2=&quot;67&quot;/&gt;
&lt;line class=&quot;wire&quot; x1=&quot;14&quot; y1=&quot;56&quot; x2=&quot;60&quot; y2=&quot;56&quot; marker-end=&quot;url(#lfsr-a)&quot;/&gt;
&lt;line class=&quot;fb&quot; x1=&quot;83&quot; y1=&quot;56&quot; x2=&quot;688&quot; y2=&quot;56&quot;/&gt;
&lt;line class=&quot;fb&quot; x1=&quot;556&quot; y1=&quot;56&quot; x2=&quot;556&quot; y2=&quot;133&quot;/&gt;
&lt;line class=&quot;fb&quot; x1=&quot;644&quot; y1=&quot;56&quot; x2=&quot;644&quot; y2=&quot;133&quot;/&gt;
&lt;line class=&quot;fb&quot; x1=&quot;688&quot; y1=&quot;56&quot; x2=&quot;688&quot; y2=&quot;116&quot; marker-end=&quot;url(#lfsr-af)&quot;/&gt;
&lt;circle class=&quot;node&quot; cx=&quot;556&quot; cy=&quot;56&quot; r=&quot;3.5&quot;/&gt;
&lt;circle class=&quot;node&quot; cx=&quot;644&quot; cy=&quot;56&quot; r=&quot;3.5&quot;/&gt;
&lt;circle class=&quot;gate&quot; cx=&quot;72&quot; cy=&quot;56&quot; r=&quot;11&quot;/&gt;
&lt;line class=&quot;plus&quot; x1=&quot;66&quot; y1=&quot;56&quot; x2=&quot;78&quot; y2=&quot;56&quot;/&gt;
&lt;line class=&quot;plus&quot; x1=&quot;72&quot; y1=&quot;50&quot; x2=&quot;72&quot; y2=&quot;62&quot;/&gt;
&lt;circle class=&quot;gate&quot; cx=&quot;556&quot; cy=&quot;144&quot; r=&quot;11&quot;/&gt;
&lt;line class=&quot;plus&quot; x1=&quot;550&quot; y1=&quot;144&quot; x2=&quot;562&quot; y2=&quot;144&quot;/&gt;
&lt;line class=&quot;plus&quot; x1=&quot;556&quot; y1=&quot;138&quot; x2=&quot;556&quot; y2=&quot;150&quot;/&gt;
&lt;circle class=&quot;gate&quot; cx=&quot;644&quot; cy=&quot;144&quot; r=&quot;11&quot;/&gt;
&lt;line class=&quot;plus&quot; x1=&quot;638&quot; y1=&quot;144&quot; x2=&quot;650&quot; y2=&quot;144&quot;/&gt;
&lt;line class=&quot;plus&quot; x1=&quot;644&quot; y1=&quot;138&quot; x2=&quot;644&quot; y2=&quot;150&quot;/&gt;
&lt;text class=&quot;lead&quot; x=&quot;14&quot; y=&quot;46&quot;&gt;message bit&lt;/text&gt;
&lt;text class=&quot;lead&quot; x=&quot;385&quot; y=&quot;44&quot; text-anchor=&quot;middle&quot;&gt;feedback = MSB ⊕ message bit&lt;/text&gt;
&lt;text class=&quot;tap&quot; x=&quot;562&quot; y=&quot;80&quot;&gt;x²&lt;/text&gt;
&lt;text class=&quot;tap&quot; x=&quot;650&quot; y=&quot;80&quot;&gt;x¹&lt;/text&gt;
&lt;text class=&quot;tap&quot; x=&quot;694&quot; y=&quot;80&quot;&gt;x⁰&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;text-align:center;color:var(--fg-dim);font-size:.9rem;margin-top:.85rem;font-family:var(--sans)&quot;&gt;The CRC-8 as a Galois shift register (polynomial &lt;code&gt;0x07&lt;/code&gt;). Each step shifts the byte left; the MSB is XORed with the next message bit to form the feedback, which is folded back in at the &lt;code&gt;x²&lt;/code&gt;, &lt;code&gt;x¹&lt;/code&gt; and &lt;code&gt;x⁰&lt;/code&gt; taps — the set bits of &lt;code&gt;0x07&lt;/code&gt;. &lt;code&gt;crc8Update&lt;/code&gt; &lt;em&gt;is&lt;/em&gt; this circuit with the flip-flops replaced by wires: Clash unrolls the &lt;code&gt;foldl&lt;/code&gt; into eight combinational copies, one per bit, so a whole byte is absorbed in a single clock.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;step-one-bit-at-a-time&quot;&gt;&lt;code&gt;step&lt;/code&gt;: one bit at a time&lt;/h2&gt;
&lt;p&gt;Which leaves the heart of the module, the private helper the
export list keeps to itself:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;step c inBit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; feedbackBit &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;==&lt;/span&gt;&lt;span&gt; high &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; shifted &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;xor&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x07&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span&gt; otherwise &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; shifted&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  feedbackBit &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; msb c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;xor&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span&gt; inBit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  shifted &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;shiftL&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The type tells the whole story of what &lt;code&gt;step&lt;/code&gt; is &lt;em&gt;for&lt;/em&gt;. It takes a
running CRC and a &lt;strong&gt;single&lt;/strong&gt; &lt;code&gt;Bit&lt;/code&gt; — not a byte, one bit — and
returns the next running CRC. That is the entire kernel of the
design; everything else in the file is &lt;code&gt;foldl&lt;/code&gt; invoking this
function eight times per byte. Read &lt;code&gt;crc8Update&lt;/code&gt; and &lt;code&gt;step&lt;/code&gt;
together and the division of labour is clean: &lt;code&gt;step&lt;/code&gt; knows how to
absorb one bit, and the fold knows how to do it eight times.&lt;/p&gt;
&lt;p&gt;Take the two &lt;code&gt;where&lt;/code&gt; bindings first, since the guards are written
in terms of them.&lt;/p&gt;
&lt;p&gt;The binding &lt;code&gt;shifted = c `shiftL` 1&lt;/code&gt; slides the CRC one place
toward the most significant bit: a zero enters at the bottom, and
the top bit drops off the end and is gone. In the language of the
previous section, shifting left by one is &lt;strong&gt;multiplying the
polynomial by $x$&lt;/strong&gt;. The bit that just dropped off is the
coefficient that &lt;em&gt;would have&lt;/em&gt; become the $x^8$ term — the exact
term $G$ exists to cancel.&lt;/p&gt;
&lt;p&gt;The other binding, &lt;code&gt;feedbackBit = msb c `xor` inBit&lt;/code&gt;, takes the
CRC’s current top bit (&lt;code&gt;msb c&lt;/code&gt; reads out the most significant
&lt;code&gt;Bit&lt;/code&gt;) and XORs in the incoming message bit. This is the point
where the message actually enters the division.&lt;/p&gt;
&lt;p&gt;Now the guards. The &lt;code&gt;|&lt;/code&gt; is a &lt;strong&gt;guard&lt;/strong&gt;: a list of boolean tests,
each paired with a result, where the first test that succeeds wins
and &lt;code&gt;otherwise&lt;/code&gt; is the catch-all that is always true. So the
function reads: if &lt;code&gt;feedbackBit == high&lt;/code&gt; — &lt;code&gt;high&lt;/code&gt; being the &lt;code&gt;Bit&lt;/code&gt;
whose value is one — the answer is &lt;code&gt;shifted `xor` 0x07&lt;/code&gt;;
otherwise the answer is just &lt;code&gt;shifted&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here is why that is division and not sleight of hand. Multiplying
by $x$ can push the polynomial up to degree eight, one bit wider
than the register holds. When the coefficient of that
overflow — &lt;code&gt;feedbackBit&lt;/code&gt; — is one, the value has grown “too big” and
must be brought back into range by subtracting $G$. Subtracting $G$
means XORing &lt;code&gt;0x107&lt;/code&gt;; but the $x^8$ bit already fell off the top in
the shift, so the only bits left to cancel are the low eight,
&lt;code&gt;0x07&lt;/code&gt;. When &lt;code&gt;feedbackBit&lt;/code&gt; is zero there was no overflow and there
is nothing to subtract. That is the complete rule the five lines
encode:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Multiply by $x$; if the bit that fell off says so, XOR the
polynomial back in to reduce modulo $G$.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We can watch it produce the very number the algebra promised. Fold
&lt;code&gt;step&lt;/code&gt; from &lt;code&gt;crc = 0&lt;/code&gt; across the byte &lt;code&gt;0x01&lt;/code&gt;, whose bits MSB-first
are seven zeros and then a one:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On each of the first seven bits, &lt;code&gt;msb c&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;inBit&lt;/code&gt; is
&lt;code&gt;0&lt;/code&gt;, so &lt;code&gt;feedbackBit&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt;; &lt;code&gt;shifted&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt; shifted, still
&lt;code&gt;0&lt;/code&gt;. The accumulator never leaves zero.&lt;/li&gt;
&lt;li&gt;On the final bit, &lt;code&gt;inBit&lt;/code&gt; is &lt;code&gt;1&lt;/code&gt; while &lt;code&gt;msb c&lt;/code&gt; is still &lt;code&gt;0&lt;/code&gt;, so
&lt;code&gt;feedbackBit&lt;/code&gt; is &lt;code&gt;0 xor 1 = 1&lt;/code&gt;. &lt;code&gt;shifted&lt;/code&gt; is
&lt;code&gt;0 `shiftL` 1 = 0&lt;/code&gt;, the guard fires, and the result is
&lt;code&gt;0 xor 0x07 = 0x07&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The fold ends at &lt;code&gt;0x07&lt;/code&gt; — exactly the $x^8 \bmod G = x^2 + x + 1$
we worked out by hand. The shifts-and-XORs and the polynomial
division are not two implementations that happen to agree; they
are one statement written twice. Fold that per-bit rule across all
eight bits and the accumulator becomes
$(\text{byte} \cdot x^8) \bmod G$, the byte’s contribution to the
running remainder; chain &lt;code&gt;crc8Update&lt;/code&gt; across a whole message,
seeding from &lt;code&gt;0&lt;/code&gt; and feeding each result back as the next &lt;code&gt;crc&lt;/code&gt;,
and you have divided the entire message by $G$, one byte at a
time.&lt;/p&gt;
&lt;h2 id=&quot;the-tests&quot;&gt;The tests&lt;/h2&gt;
&lt;p&gt;The module is twenty-eight lines. Its test file is shorter still,
and the two of them together make a quiet point worth pausing on:
a claim this small can be pinned down &lt;em&gt;completely&lt;/em&gt;, from more than
one direction. The tests come in two flavours that answer two
different questions.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;crc8&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;crc8 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; L.&lt;/span&gt;&lt;span&gt;foldl&amp;#39; crc8Update &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;tests &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  testGroup &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;Crc&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    [&lt;/span&gt;&lt;span&gt; testCase &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;crc8Update 0 0x01 == 0x07&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        crc8Update &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x01&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @?=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x07&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span&gt; testCase &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;CRC-8/SMBUS check &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;\&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;123456789&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;\&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; == 0xF4&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        crc8 &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;fromIntegral &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fromEnum c&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;123456789&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; @?=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0xF4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;/span&gt;&lt;span&gt; testProperty &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;residue law&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; $&lt;/span&gt;&lt;span&gt; property &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        msg &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span&gt; forAll &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Gen.&lt;/span&gt;&lt;span&gt;list &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Range.&lt;/span&gt;&lt;span&gt;linear &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; genByte&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        crc8 &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;msg &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;crc8 msg&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ===&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First the helper. &lt;code&gt;crc8Update&lt;/code&gt; works one byte at a time, but a
message is a &lt;em&gt;list&lt;/em&gt; of bytes, so the tests define &lt;code&gt;crc8 = L.foldl&#39; crc8Update 0&lt;/code&gt; — fold the per-byte update across the whole list,
seeding from the initial value &lt;code&gt;0&lt;/code&gt;. This is the second fold in the
story, and it sits one level up from the first: the source folds
&lt;em&gt;bits&lt;/em&gt; into a byte’s CRC, the test folds &lt;em&gt;bytes&lt;/em&gt; into a message’s
CRC. Same &lt;code&gt;foldl&lt;/code&gt; idea, twice, at two scales.&lt;/p&gt;
&lt;p&gt;A small Clash wrinkle hides in that &lt;code&gt;L.&lt;/code&gt;. Because &lt;code&gt;import Clash.Prelude&lt;/code&gt; shadowed the list functions with &lt;code&gt;Vec&lt;/code&gt; ones back at
the top of the module, the plain-list fold has to be summoned
from &lt;code&gt;Data.List&lt;/code&gt; under the qualifier &lt;code&gt;L&lt;/code&gt;, hence &lt;code&gt;L.foldl&#39;&lt;/code&gt;. The
note at the bottom of the real test file is flagging the same shadow
for &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;++&lt;/code&gt;, which is why the message is stitched together
with a list comprehension and &lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt; rather than the names you might
expect. That one import casts its shadow all the way into the
tests.&lt;/p&gt;
&lt;h3 id=&quot;the-oracle-tests&quot;&gt;The oracle tests&lt;/h3&gt;
&lt;p&gt;The first two entries are &lt;strong&gt;HUnit&lt;/strong&gt; cases — plain, hand-checked
assertions of the shape “this exact input yields this exact
output,” where &lt;code&gt;@?=&lt;/code&gt; reads “the actual value should equal the
expected one.”&lt;/p&gt;
&lt;p&gt;&lt;code&gt;crc8Update 0 0x01 @?= 0x07&lt;/code&gt; is the smallest witness in the whole
project. We have now derived it &lt;em&gt;twice&lt;/em&gt; — once as $x^8 \bmod G$ with
pencil and polynomials, once by tracing the fold through eight
bits — and here it is bolted to the workbench so it can never
silently change. If some future refactor ever breaks the
multiply-by-$x$-then-reduce rule, this one-liner is the first thing
to go red.&lt;/p&gt;
&lt;p&gt;The second case checks a number that is not the author’s at all —
it is the &lt;strong&gt;world’s&lt;/strong&gt;. &lt;code&gt;CRC-8/SMBUS&lt;/code&gt; is a catalogued CRC, and every
catalogued CRC ships with a standard &lt;em&gt;check value&lt;/em&gt;: the CRC of the
nine ASCII bytes &lt;code&gt;&quot;123456789&quot;&lt;/code&gt;. For this polynomial and these
parameters that value is &lt;code&gt;0xF4&lt;/code&gt;, the same &lt;code&gt;0xF4&lt;/code&gt; printed in
reference tables and reproduced by independent implementations in
every language. Asserting it turns the test suite into a cross-check
against the entire ecosystem: if Tamal agrees with the catalog on
&lt;code&gt;&quot;123456789&quot;&lt;/code&gt;, then it is computing the CRC it &lt;em&gt;claims&lt;/em&gt; to be — the
one an eSPI or SMBus partner on the other end of the wire will also
be computing — and not some subtly different cousin. These two are
&lt;strong&gt;oracle tests&lt;/strong&gt;: known questions with known answers, one derived
in-house, one borrowed from the standard.&lt;/p&gt;
&lt;h3 id=&quot;the-property-test&quot;&gt;The property test&lt;/h3&gt;
&lt;p&gt;The third entry is a different animal. &lt;a rel=&quot;external&quot; href=&quot;https://hedgehog.qa&quot;&gt;Hedgehog&lt;/a&gt; is a
property-based testing library: instead of one example, you state a
&lt;em&gt;law&lt;/em&gt; that must hold for &lt;em&gt;every&lt;/em&gt; input, and the library manufactures
hundreds of random inputs trying to break it. The law here is the
one the whole block exists to satisfy:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;crc8 &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;msg &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;crc8 msg&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ===&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read it aloud: take any message &lt;code&gt;msg&lt;/code&gt;, compute its CRC, append that
CRC byte to the end of the message, and run the CRC over the whole
extended thing — and you get zero. The line above it, &lt;code&gt;forAll (Gen.list (Range.linear 0 32) genByte)&lt;/code&gt;, is Hedgehog drawing &lt;code&gt;msg&lt;/code&gt;
as a random list of up to thirty-two random bytes, with &lt;code&gt;genByte&lt;/code&gt;
(from the shared &lt;code&gt;Test.Gen&lt;/code&gt;) producing fully-defined bytes. Every
run of the suite fires this across a fresh shower of messages. And
on the day it &lt;em&gt;does&lt;/em&gt; fail, Hedgehog does the thing that earns its
keep: it &lt;strong&gt;shrinks&lt;/strong&gt; the counterexample, paring a giant random
failure down to the smallest message that still breaks the law, and
hands you that minimal case instead of a haystack.&lt;/p&gt;
&lt;p&gt;Why is this the &lt;em&gt;right&lt;/em&gt; property — the single law worth elevating
above all the specific numbers? For two reasons, one practical and
one that finally cashes in the polynomial setup.&lt;/p&gt;
&lt;p&gt;The practical reason is that it is exactly how Tamal &lt;em&gt;uses&lt;/em&gt; this
CRC on the bus. The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;introduction post&lt;/a&gt;’s
&lt;code&gt;peripheral_io_read.s&lt;/code&gt; ends its response phase by reading the
trailing CRC byte and checking that the running residue has driven
to zero — &lt;code&gt;rdsr t2, CRC&lt;/code&gt; followed by &lt;code&gt;bnez t2, bad_crc&lt;/code&gt;. A good
packet followed by its CRC leaves a residue of zero; anything else
is a corrupted packet. The property test is that runtime check,
lifted out of the eSPI program and asserted directly against the
pure function. It tests the law the hardware actually leans on.&lt;/p&gt;
&lt;p&gt;The mathematical reason is the payoff for all the polynomials.
Appending the CRC byte $C$ to the message $M$ builds the number
$M \cdot x^8 + C$ — the message shifted up eight bits to make room,
with $C$ dropped into the low byte. But $C$ was &lt;em&gt;defined&lt;/em&gt; as the
remainder of $M \cdot x^8$ divided by $G$, and adding a remainder
back onto its own dividend lands you on a clean multiple of the
divisor. (Adding and subtracting are the same XOR here, which is
why &lt;em&gt;appending&lt;/em&gt; $C$ and &lt;em&gt;subtracting off the remainder&lt;/em&gt; come to the
same thing.) So $M \cdot x^8 + C$ is divisible by $G$ with nothing
left over — a valid codeword — and running the CRC over it is
dividing a multiple of $G$ by $G$:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The residue is zero because a message-plus-CRC is, by
construction, a multiple of the generator polynomial.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There is one subtlety the shift handles for free. Running the CRC
over the codeword multiplies it by a further $x^8$ — the fold’s own
headroom — before reducing. But that changes nothing in the
direction we care about: the codeword is already a multiple of $G$,
and $x^8$ times a multiple of $G$ is still a multiple of $G$, so the
remainder is still zero. (The extra factor is harmless the other
way too: because $G$ ends in a one — its constant term is $1$ —
multiplying by $x$ is reversible modulo $G$, so the headroom can
never manufacture a false zero out of a &lt;em&gt;bad&lt;/em&gt; packet.) The property
holds for every message because the algebra leaves it no other
option, and Hedgehog’s hundreds of random trials stand guard over
the day a careless edit breaks that algebra.&lt;/p&gt;
&lt;h2 id=&quot;what-we-read&quot;&gt;What we read&lt;/h2&gt;
&lt;p&gt;Twenty-eight lines, one exported name, and underneath it a complete
CRC: a module wall with a single door, a prelude swap that turns the
file into a circuit, a curried type written fold-shaped on purpose,
a left fold that Clash unrolls into a linear-feedback shift
register, and a five-line &lt;code&gt;step&lt;/code&gt; that is polynomial division
disguised as shift-and-XOR. Beside it, a test file that stakes the
whole thing down from three sides at once — a witness we derived by
hand, the world’s catalogued check value, and the residue law the
eSPI engine trusts at runtime.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://balbi.sh/posts/tamal-haskell-primer/&quot;&gt;primer&lt;/a&gt; promised that the syntax would stop standing between
you and the hardware. This is the first block where that promise
gets cashed: there is nothing left in these lines that is &lt;em&gt;merely&lt;/em&gt;
Haskell — every one of them is a statement about gates, or about the
algebra those gates carry out. Next in the series we take on a block
with a clock inside it — the UART — where &lt;code&gt;Signal&lt;/code&gt; stops being a
footnote and the &lt;code&gt;mealy&lt;/code&gt; from the primer finally earns its perch at
the top of a module.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-fold&quot;&gt;
&lt;p&gt;A &lt;strong&gt;fold&lt;/strong&gt; — also called &lt;code&gt;reduce&lt;/code&gt;, &lt;code&gt;accumulate&lt;/code&gt;, &lt;code&gt;inject&lt;/code&gt;, or
&lt;code&gt;aggregate&lt;/code&gt;, depending on the language — is the higher-order function
that collapses a whole sequence into a single value by threading an
accumulator through it: start from a seed, then repeatedly apply a
two-argument combining function to “the result so far” and “the next
element.” It comes handed: a &lt;em&gt;left&lt;/em&gt; fold (&lt;code&gt;foldl&lt;/code&gt;) brackets from the
left and walks front to back, a &lt;em&gt;right&lt;/em&gt; fold (&lt;code&gt;foldr&lt;/code&gt;) from the other
end. Fold is bread-and-butter in the languages descended from the
lambda calculus — Lisp and Scheme, the ML family and OCaml, Haskell —
where it sits beside &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, &lt;code&gt;scan&lt;/code&gt;, and &lt;code&gt;zip&lt;/code&gt; in a shared
vocabulary of combinators; the &lt;em&gt;array&lt;/em&gt; languages (APL, J, BQN, Uiua)
know it just as intimately, usually as a single-glyph primitive.
Imperative languages have long had it too, under a scatter of
names — Python’s &lt;code&gt;functools.reduce&lt;/code&gt;, C++’s &lt;code&gt;std::accumulate&lt;/code&gt;, Ruby’s
&lt;code&gt;inject&lt;/code&gt;, JavaScript’s &lt;code&gt;reduce&lt;/code&gt;, C#’s LINQ &lt;code&gt;Aggregate&lt;/code&gt; — but have
tended to reach for an explicit loop-with-an-accumulator instead;
Rust is arguably the first mainstream systems language to make &lt;code&gt;fold&lt;/code&gt;
(and &lt;code&gt;reduce&lt;/code&gt;) a reached-for, idiomatic tool rather than a
functional-programming curiosity, largely by building it into the
iterator adaptors everyone already uses. &lt;a href=&quot;#fr-fold-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-returns&quot;&gt;
&lt;p&gt;Technically Haskell functions never &lt;strong&gt;return&lt;/strong&gt;, rather
they &lt;strong&gt;evaluate to&lt;/strong&gt; a value. &lt;a href=&quot;#fr-returns-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Enough Haskell to Read Tamal</title>
    <published>2026-07-19T09:00:00+00:00</published>
    <updated>2026-07-19T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-haskell-primer/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-haskell-primer/</id>
    <summary type="html">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/tamal&quot;&gt;Tamal&lt;/a&gt; is an FPGA eSPI exerciser, and its gateware — the UART, the
cycle engine, the ALU, the CRC unit, the loader, the trace ring — is
written in Haskell and compiled straight to hardware by &lt;a rel=&quot;external&quot; href=&quot;https://clash-lang.org&quot;&gt;Clash&lt;/a&gt;. This
is the first post in a series that opens up that gateware one block at
a time and reads each one end to end.&lt;/p&gt;
&lt;p&gt;There’s a catch, and if you’re here at all you can probably feel it
coming. Approach this code from C, Rust, or Verilog — which is where
most people who care about an eSPI exerciser are standing — and the
source can read like line noise. &lt;code&gt;::&lt;/code&gt;, &lt;code&gt;-&amp;gt;&lt;/code&gt;, &lt;code&gt;\case&lt;/code&gt;,
&lt;code&gt;deriving stock&lt;/code&gt;, &lt;code&gt;BitVector 32&lt;/code&gt;, a lone &lt;code&gt;mealy&lt;/code&gt; perched atop a module
like an incantation. None of it is &lt;em&gt;hard&lt;/em&gt;. All of it is unfamiliar,
and unfamiliar is enough to stop you at the door.&lt;/p&gt;
&lt;p&gt;So before the series spends a whole post on the &lt;code&gt;Uart&lt;/code&gt; or the
&lt;code&gt;Engine&lt;/code&gt;, this one pays down the syntax tax up front. Think of it as a
Rosetta Stone: just enough Haskell that a Tamal signature stops being
an obstacle and starts being documentation.&lt;/p&gt;
&lt;p&gt;Two honest caveats about scope. This is &lt;strong&gt;not&lt;/strong&gt; a Haskell course —
there are excellent ones, and I’m not writing another — and it
deliberately steers &lt;em&gt;around&lt;/em&gt; the deep theory: no monad tutorial, no
functor laws, no monoids, none of the machinery a “learn Haskell” book
leads with. The goal is narrow and practical: by the end you should be
able to look at a Tamal type signature, a &lt;code&gt;data&lt;/code&gt; block, a &lt;code&gt;case&lt;/code&gt;, and
the &lt;code&gt;mealy&lt;/code&gt; at the top of an engine and know exactly what you’re seeing
— not prove theorems about it, just &lt;em&gt;read&lt;/em&gt; it. That’s the whole job.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/tamal&quot;&gt;Tamal&lt;/a&gt; is an FPGA eSPI exerciser, and its gateware — the UART, the
cycle engine, the ALU, the CRC unit, the loader, the trace ring — is
written in Haskell and compiled straight to hardware by &lt;a rel=&quot;external&quot; href=&quot;https://clash-lang.org&quot;&gt;Clash&lt;/a&gt;. This
is the first post in a series that opens up that gateware one block at
a time and reads each one end to end.&lt;/p&gt;
&lt;p&gt;There’s a catch, and if you’re here at all you can probably feel it
coming. Approach this code from C, Rust, or Verilog — which is where
most people who care about an eSPI exerciser are standing — and the
source can read like line noise. &lt;code&gt;::&lt;/code&gt;, &lt;code&gt;-&amp;gt;&lt;/code&gt;, &lt;code&gt;\case&lt;/code&gt;,
&lt;code&gt;deriving stock&lt;/code&gt;, &lt;code&gt;BitVector 32&lt;/code&gt;, a lone &lt;code&gt;mealy&lt;/code&gt; perched atop a module
like an incantation. None of it is &lt;em&gt;hard&lt;/em&gt;. All of it is unfamiliar,
and unfamiliar is enough to stop you at the door.&lt;/p&gt;
&lt;p&gt;So before the series spends a whole post on the &lt;code&gt;Uart&lt;/code&gt; or the
&lt;code&gt;Engine&lt;/code&gt;, this one pays down the syntax tax up front. Think of it as a
Rosetta Stone: just enough Haskell that a Tamal signature stops being
an obstacle and starts being documentation.&lt;/p&gt;
&lt;p&gt;Two honest caveats about scope. This is &lt;strong&gt;not&lt;/strong&gt; a Haskell course —
there are excellent ones, and I’m not writing another — and it
deliberately steers &lt;em&gt;around&lt;/em&gt; the deep theory: no monad tutorial, no
functor laws, no monoids, none of the machinery a “learn Haskell” book
leads with. The goal is narrow and practical: by the end you should be
able to look at a Tamal type signature, a &lt;code&gt;data&lt;/code&gt; block, a &lt;code&gt;case&lt;/code&gt;, and
the &lt;code&gt;mealy&lt;/code&gt; at the top of an engine and know exactly what you’re seeing
— not prove theorems about it, just &lt;em&gt;read&lt;/em&gt; it. That’s the whole job.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;f-a-b-is-just-f-a-b&quot;&gt;&lt;code&gt;f :: A -&amp;gt; B&lt;/code&gt; is just &lt;code&gt;f : A → B&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Start with the single most important translation, because once it
clicks the rest follows. In mathematics you write a function as
$f : A \to B$, read aloud as “$f$ takes an $A$ and gives you a $B$.”
That arrow is the entire idea.&lt;/p&gt;
&lt;p&gt;Haskell writes the exact same statement as &lt;code&gt;f :: A -&amp;gt; B&lt;/code&gt;. The &lt;code&gt;-&amp;gt;&lt;/code&gt; is
the arrow. The only oddity is the doubled colon: &lt;code&gt;::&lt;/code&gt; reads “has
type.” A single &lt;code&gt;:&lt;/code&gt; would have been the obvious choice, but it was
already spoken for — in Haskell &lt;code&gt;:&lt;/code&gt; is the list-cons operator — so the
type annotation had to take the double. Read &lt;code&gt;f :: A -&amp;gt; B&lt;/code&gt; as “&lt;code&gt;f&lt;/code&gt; has
type $A \to B$,” and you are reading the mathematics.&lt;/p&gt;
&lt;p&gt;Here is the smallest honest example:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Int&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Int&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;double n &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; n &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first line is the type; the second the definition. And I do mean
&lt;em&gt;mapping&lt;/em&gt;, not &lt;em&gt;procedure&lt;/em&gt;. &lt;code&gt;double&lt;/code&gt; is not a recipe of steps that
mutate something on the way to a result — it is the mathematical object
that associates each &lt;code&gt;Int&lt;/code&gt; with another, no more alive than the
function $x \mapsto 2x$ on a chalkboard. Feed it &lt;code&gt;21&lt;/code&gt; and you get &lt;code&gt;42&lt;/code&gt;,
today, tomorrow, on every core, with nothing else happening anywhere.
That last clause has a name: &lt;strong&gt;purity&lt;/strong&gt;. Same input, same output, no
side effects, no hidden state read or written.&lt;/p&gt;
&lt;p&gt;If you build hardware you already own the mental model, and it is worth
stating loudly because it is load-bearing for everything Clash does:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A block of combinational logic is a pure function of its inputs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A truth table is literally $f : \text{inputs} \to \text{outputs}$:
same inputs, same outputs, every time, no memory of what came before —
that &lt;em&gt;is&lt;/em&gt; purity, in gates instead of lambda calculus. It is the whole
reason a functional language can describe a circuit at all: your
design’s combinational core and a pure Haskell function are the same
kind of object, and Clash is the compiler that notices.&lt;/p&gt;
&lt;h2 id=&quot;a-quick-tour-of-categories&quot;&gt;A quick tour of categories&lt;/h2&gt;
&lt;p&gt;Once functions are arrows, the natural question is: what do you
actually &lt;em&gt;do&lt;/em&gt; with them? Strip it down and there are exactly two
moves.&lt;/p&gt;
&lt;p&gt;The first is &lt;strong&gt;composition&lt;/strong&gt;. Given $f : A \to B$ and $g : B \to C$ —
the target of the first matching the source of the second — you run
them nose to tail and get a single arrow $A \to C$. Mathematicians
write it $g \circ f$ (“$g$ after $f$”); Haskell writes it &lt;code&gt;g . f&lt;/code&gt;, a
single dot:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; (.) :: (b -&amp;gt; c) -&amp;gt; (a -&amp;gt; b) -&amp;gt; (a -&amp;gt; c)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;pipeline &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; decode &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span&gt; validate &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span&gt; receive&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read that right to left: &lt;code&gt;receive&lt;/code&gt;, then &lt;code&gt;validate&lt;/code&gt;, then &lt;code&gt;decode&lt;/code&gt;.
The comment is just the arrow-matching rule spelled out — give &lt;code&gt;.&lt;/code&gt; an
arrow &lt;code&gt;b -&amp;gt; c&lt;/code&gt; and an arrow &lt;code&gt;a -&amp;gt; b&lt;/code&gt;, and it hands back the composite
&lt;code&gt;a -&amp;gt; c&lt;/code&gt;. If you have ever built a datapath as a chain of stages, each
feeding the next, you have built exactly this.&lt;/p&gt;
&lt;p&gt;The second move is humbler still: the &lt;strong&gt;identity&lt;/strong&gt;, a do-nothing arrow
$A \to A$ that hands its input straight back. It sounds too trivial to
mention, but it is the neutral element that makes composition
well-behaved — the wire from a stage to itself.&lt;/p&gt;
&lt;p&gt;Those two ingredients, plus two laws, are the entire definition of a
&lt;strong&gt;category&lt;/strong&gt;: some &lt;em&gt;objects&lt;/em&gt;, &lt;em&gt;arrows&lt;/em&gt; between them, a way to
&lt;em&gt;compose&lt;/em&gt; arrows whose ends line up, and an &lt;em&gt;identity&lt;/em&gt; arrow on every
object, subject to&lt;/p&gt;
&lt;p&gt;$$ f \circ \mathrm{id} = f = \mathrm{id} \circ f $$&lt;/p&gt;
&lt;p&gt;$$ (h \circ g) \circ f = h \circ (g \circ f) $$&lt;/p&gt;
&lt;p&gt;The first law says the identity truly does nothing; the second says
composition doesn’t care how you parenthesise a chain — only order
matters, not grouping. That is the whole axiom set. There is no third
page.&lt;/p&gt;
&lt;p&gt;Now the part that trips people up. Sets with functions between them do
form a category — objects are sets, arrows are functions, composition
the ordinary $g \circ f$ — and it has a name, &lt;strong&gt;Set&lt;/strong&gt;. Haskell types
with functions form another. So it can &lt;em&gt;sound&lt;/em&gt; like categories are
just “sets, restated.” They are not.&lt;/p&gt;
&lt;p&gt;Here is a category with no sets and no functions in sight. Take any
&lt;strong&gt;preorder&lt;/strong&gt; — a set with a $\le$ relation that is reflexive and
transitive, like divisibility on the integers. Make the elements the
&lt;em&gt;objects&lt;/em&gt;, and draw one arrow $a \to b$ precisely when $a \le b$.
Composition? If $a \le b$ and $b \le c$ then $a \le c$: that
transitivity &lt;em&gt;is&lt;/em&gt; the composite. The identity? $a \le a$: reflexivity.
The laws hold for free, and you have a category in which an “arrow” is
not a mapping of anything — it is a &lt;em&gt;fact&lt;/em&gt;, that one thing sits below
another.&lt;/p&gt;
&lt;p&gt;That is the honest version of the “superset” story. Category theory
does not &lt;em&gt;contain&lt;/em&gt; set theory — &lt;strong&gt;Set is one example among many.&lt;/strong&gt; It
steps back from &lt;em&gt;what the objects are made of&lt;/em&gt; — sets, integers,
types, states — and studies only the pattern they share: things,
arrows, and a law-abiding notion of composition. Forget the elements;
keep the plumbing.&lt;/p&gt;
&lt;p&gt;You need none of this to type a &lt;code&gt;.&lt;/code&gt; into a Haskell file. But it buys
the right mental model for the rest of this post:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Composition is the primary verb.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A type signature full of &lt;code&gt;-&amp;gt;&lt;/code&gt; is not a scary pile of punctuation. It
is a chain of arrows, and your job when reading Tamal is usually just
to see where each arrow starts, where it ends, and how they snap
together.&lt;/p&gt;
&lt;h2 id=&quot;arrows-in-a-row-currying&quot;&gt;Arrows in a row: currying&lt;/h2&gt;
&lt;p&gt;If a function is one arrow from one input to one output, here is a
puzzle. Why does addition have &lt;em&gt;two&lt;/em&gt; arrows?&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Int&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Int&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Int&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;add x y &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; x &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;+&lt;/span&gt;&lt;span&gt; y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;addFive&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Int&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Int&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;addFive &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span&gt; add &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The resolution is a single rule: &lt;code&gt;-&amp;gt;&lt;/code&gt; associates to the &lt;strong&gt;right&lt;/strong&gt;. So
&lt;code&gt;Int -&amp;gt; Int -&amp;gt; Int&lt;/code&gt; secretly means &lt;code&gt;Int -&amp;gt; (Int -&amp;gt; Int)&lt;/code&gt; — “give me an
&lt;code&gt;Int&lt;/code&gt;, and I hand you back &lt;em&gt;a function&lt;/em&gt; of type &lt;code&gt;Int -&amp;gt; Int&lt;/code&gt;.” &lt;code&gt;add&lt;/code&gt;
does not take two arguments at all: it takes one &lt;code&gt;Int&lt;/code&gt; and returns a
function still waiting for the second. Every Haskell function takes
exactly one argument; the multi-arrow signatures everywhere are sugar
for this chain. The trick has a name — &lt;strong&gt;currying&lt;/strong&gt;, after Haskell
Curry, who also lent the language its first name.&lt;/p&gt;
&lt;p&gt;The payoff is that you can stop halfway. &lt;code&gt;add 5&lt;/code&gt; is a complete value —
the function that adds five to whatever comes next. Supplying some
arguments now and the rest later is &lt;strong&gt;partial application&lt;/strong&gt;, and
&lt;code&gt;addFive&lt;/code&gt; is exactly that.&lt;/p&gt;
&lt;p&gt;The hardware reading is direct, and worth holding onto:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Partial application is a circuit family parameterised by
configuration.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Fix the configuration bits now; feed the data later. &lt;code&gt;add 5&lt;/code&gt; is the
adder with one operand strapped to a constant — a specialized block
carved out of a general one. You will see this shape all over Tamal:
the ALU’s signature
&lt;code&gt;alu :: AluOp -&amp;gt; BitVector 32 -&amp;gt; BitVector 32 -&amp;gt; BitVector 32&lt;/code&gt; reads
left to right as “pick an operation, then hand me two 32-bit operands,
and I’ll give you a 32-bit result.” Thanks to currying, &lt;code&gt;alu Add&lt;/code&gt; is
already meaningful on its own — the two-operand adder, carved out of
the general ALU by pinning the opcode. Configuration first, data
second, all the way down.&lt;/p&gt;
&lt;h2 id=&quot;types-as-data-algebraic-data-types&quot;&gt;Types as data: algebraic data types&lt;/h2&gt;
&lt;p&gt;Now the syntax you will meet more than any other: &lt;code&gt;data&lt;/code&gt;, the keyword
that builds new types. Haskell’s are called &lt;strong&gt;algebraic data types&lt;/strong&gt;,
and the name is a promise about how they are built — from two
operations you already know, even if never by these names.&lt;/p&gt;
&lt;p&gt;Start with the simpler one, the &lt;strong&gt;sum type&lt;/strong&gt;. Each &lt;code&gt;|&lt;/code&gt; is an
alternative — read it as “or”:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Color&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Red&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Green&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Blue&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;code&gt;Color&lt;/code&gt; is &lt;code&gt;Red&lt;/code&gt; &lt;strong&gt;or&lt;/strong&gt; &lt;code&gt;Green&lt;/code&gt; &lt;strong&gt;or&lt;/strong&gt; &lt;code&gt;Blue&lt;/code&gt;, and nothing else.
This is a C or Rust &lt;code&gt;enum&lt;/code&gt;, and if that were all you ever used it for
it would already earn its keep. Tamal’s engine lifecycle is exactly
this shape — a &lt;code&gt;Phase&lt;/code&gt; that is &lt;code&gt;Idle&lt;/code&gt; or &lt;code&gt;Preamble&lt;/code&gt; or &lt;code&gt;Fetch&lt;/code&gt; or one
of a handful of other named states, the FSM’s state register written
as a type.&lt;/p&gt;
&lt;p&gt;The other operation is the &lt;strong&gt;product type&lt;/strong&gt;, better known as a struct
or record — a bundle that holds one field &lt;strong&gt;and&lt;/strong&gt; another &lt;strong&gt;and&lt;/strong&gt;
another:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Point&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Point&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; x&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Int&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; y&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Int&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;code&gt;Point&lt;/code&gt; carries an &lt;code&gt;x&lt;/code&gt; and a &lt;code&gt;y&lt;/code&gt;, both at once. The record syntax
throws in a gift: each field name is also an accessor function. Write
that &lt;code&gt;data&lt;/code&gt; and you get &lt;code&gt;x :: Point -&amp;gt; Int&lt;/code&gt; for free, no boilerplate.
Tamal’s engine state is one big record of this kind, a
&lt;code&gt;State { phase, pc, regs, ... }&lt;/code&gt; threaded through every step — program
counter, register file, current phase, all in one value.&lt;/p&gt;
&lt;p&gt;Constructors are allowed to carry data, which is where sum and product
combine into something sharper than a C &lt;code&gt;enum&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Shape&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Circle&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Double&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Rectangle&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Double&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Double&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;code&gt;Shape&lt;/code&gt; is &lt;em&gt;either&lt;/em&gt; a &lt;code&gt;Circle&lt;/code&gt; carrying one &lt;code&gt;Double&lt;/code&gt; (its radius)
&lt;em&gt;or&lt;/em&gt; a &lt;code&gt;Rectangle&lt;/code&gt; carrying two (width and height). The tag and its
payload travel together — and, as the next section shows, you cannot
reach the payload without first checking the tag. A Rust programmer
will recognize this immediately; it is a data-bearing &lt;code&gt;enum&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So why “algebraic”? Because you are literally doing arithmetic on
types. A sum &lt;strong&gt;adds&lt;/strong&gt; possibilities; a product &lt;strong&gt;multiplies&lt;/strong&gt; them.
Count the inhabitants and the algebra is exact: $|A + B| = |A| + |B|$
for a sum, and $|A \times B| = |A| \cdot |B|$ for a product. &lt;code&gt;Bool&lt;/code&gt;
has 2 values; the pair &lt;code&gt;(Bool, Bool)&lt;/code&gt; has $2 \cdot 2 = 4$;
&lt;code&gt;Either Bool Bool&lt;/code&gt; has $2 + 2 = 4$. That counting is no party trick —
it is exactly why the compiler can tell whether you have handled every
case, which matters a great deal for state machines.&lt;/p&gt;
&lt;p&gt;One member of the family deserves special mention, because it is how
Haskell abolishes the null pointer:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Maybe&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; a&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Nothing&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Just&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; a&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Maybe a&lt;/code&gt; has two constructors: &lt;code&gt;Nothing&lt;/code&gt;, carrying no data, or
&lt;code&gt;Just&lt;/code&gt;, carrying an &lt;code&gt;a&lt;/code&gt;. It is the type of “a value that might be
absent” — but the absence lives &lt;em&gt;in the type&lt;/em&gt;, out in the open. There
is no null &lt;code&gt;a&lt;/code&gt; hiding inside an ordinary &lt;code&gt;a&lt;/code&gt; waiting to segfault you;
if a value might be missing, its type says &lt;code&gt;Maybe&lt;/code&gt;, and you can’t use
it as a bare &lt;code&gt;a&lt;/code&gt; — you have to pattern-match, and if you skip the
&lt;code&gt;Nothing&lt;/code&gt; case the build warns you, by name, before you ever run it.
In Tamal a step returning a &lt;code&gt;Maybe Ring&lt;/code&gt; states a fact about hardware:
&lt;em&gt;at most one trace-RAM write happens this cycle&lt;/em&gt; — zero or one, never a
smuggled “sometimes.” A write-enable line, promoted to a type.&lt;/p&gt;
&lt;h2 id=&quot;taking-types-apart-pattern-matching&quot;&gt;Taking types apart: pattern matching&lt;/h2&gt;
&lt;p&gt;If &lt;code&gt;data&lt;/code&gt; is how you build a value, &lt;strong&gt;pattern matching&lt;/strong&gt; is how you
take one apart — and it is the mirror image, constructor for
constructor. The tool is the &lt;code&gt;case&lt;/code&gt; expression:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;area&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Shape&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Double&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;area s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; case&lt;/span&gt;&lt;span&gt; s &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Circle&lt;/span&gt;&lt;span&gt; r      &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 3.14159&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; *&lt;/span&gt;&lt;span&gt; r &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;*&lt;/span&gt;&lt;span&gt; r&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Rectangle&lt;/span&gt;&lt;span&gt; w h &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; w &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;*&lt;/span&gt;&lt;span&gt; h&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read &lt;code&gt;case s of&lt;/code&gt; as “look at which &lt;code&gt;Shape&lt;/code&gt; &lt;code&gt;s&lt;/code&gt; actually is.” If it was
built with &lt;code&gt;Circle&lt;/code&gt;, the first branch fires and binds &lt;code&gt;r&lt;/code&gt; to the
radius packed inside; if &lt;code&gt;Rectangle&lt;/code&gt;, the second binds &lt;code&gt;w&lt;/code&gt; and &lt;code&gt;h&lt;/code&gt;.
This is the “you can’t get the payload without checking the tag” rule
made concrete: the only route to &lt;code&gt;r&lt;/code&gt; is to match &lt;code&gt;Circle r&lt;/code&gt;, and inside
that branch the compiler &lt;em&gt;knows&lt;/em&gt; a radius is there.&lt;/p&gt;
&lt;p&gt;When a function is nothing but one &lt;code&gt;case&lt;/code&gt; on its final argument — which
is most of the time — there is a shorthand that drops the ceremony,
&lt;code&gt;\case&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;area&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Shape&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Double&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;area&amp;#39; &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; \&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;case&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Circle&lt;/span&gt;&lt;span&gt; r      &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 3.14159&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; *&lt;/span&gt;&lt;span&gt; r &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;*&lt;/span&gt;&lt;span&gt; r&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;  Rectangle&lt;/span&gt;&lt;span&gt; w h &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; w &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;*&lt;/span&gt;&lt;span&gt; h&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same behaviour, one fewer name to invent; &lt;code&gt;\case&lt;/code&gt; is “a function that
immediately pattern-matches on the argument I didn’t bother to name.”
You will see it constantly in Tamal — most of the gateware’s logic &lt;em&gt;is&lt;/em&gt;
a match on some state or opcode.&lt;/p&gt;
&lt;p&gt;Here is the part that should make a hardware engineer sit up straight.
The compiler does &lt;strong&gt;exhaustiveness checking&lt;/strong&gt;: if your &lt;code&gt;case&lt;/code&gt; over a
sum type leaves out a constructor, you get a warning pointing at the
exact one you missed. Now recall that a &lt;code&gt;Phase&lt;/code&gt; is a sum type and the
engine’s next-state logic is a &lt;code&gt;case&lt;/code&gt; over it. That means the question
every FSM review is supposed to ask —&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Did you handle every state?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;— is answered by the compiler, on every build, for free. Add a new
&lt;code&gt;Phase&lt;/code&gt; and forget to wire it into the next-state logic, and the build
tells you before you run a test, let alone open a waveform. It is the
most boring flavour of formal-ish guarantee, and one of the most
valuable: a whole category of “unhandled state” bugs surfaces at build
time instead of in a waveform.&lt;/p&gt;
&lt;h2 id=&quot;types-with-behaviour-typeclasses-and-deriving&quot;&gt;Types with behaviour: typeclasses and &lt;code&gt;deriving&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The last piece of everyday syntax is how Haskell attaches &lt;em&gt;behaviour&lt;/em&gt;
to types. A &lt;strong&gt;typeclass&lt;/strong&gt; is an interface — a named set of operations
a type can choose to support:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; a&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;  (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; a&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; a&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This declares &lt;code&gt;Eq&lt;/code&gt;, the class of types whose values can be compared:
an &lt;code&gt;Eq&lt;/code&gt; must supply a &lt;code&gt;==&lt;/code&gt; taking two of its values and returning a
&lt;code&gt;Bool&lt;/code&gt;. A concrete type opts in with an &lt;strong&gt;instance&lt;/strong&gt; — the actual &lt;code&gt;==&lt;/code&gt;
for &lt;code&gt;Int&lt;/code&gt;, for &lt;code&gt;Color&lt;/code&gt;, for whatever. If you write Rust this is a
&lt;strong&gt;trait&lt;/strong&gt;; in modern C++, a &lt;strong&gt;concept&lt;/strong&gt;. What it is &lt;em&gt;not&lt;/em&gt;, despite the
keyword, is an object-oriented class: no fields, no constructors, no
inheritance tree, no &lt;code&gt;this&lt;/code&gt;. A typeclass is a contract a type
satisfies, not a box a value lives inside.&lt;/p&gt;
&lt;p&gt;Typeclasses exist to enable &lt;strong&gt;bounded polymorphism&lt;/strong&gt; — one function
that works for &lt;em&gt;any&lt;/em&gt; type, provided it supports the operations you
need. The signature carries the requirement:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;elem&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; a&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; a&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;a&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bool&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read the &lt;code&gt;=&amp;gt;&lt;/code&gt; as a &lt;strong&gt;constraint arrow&lt;/strong&gt;: “for &lt;strong&gt;any&lt;/strong&gt; type &lt;code&gt;a&lt;/code&gt; that is
an &lt;code&gt;Eq&lt;/code&gt;, &lt;code&gt;elem&lt;/code&gt; takes an &lt;code&gt;a&lt;/code&gt; and a list of &lt;code&gt;a&lt;/code&gt; and says whether the
value is in the list.” The part before &lt;code&gt;=&amp;gt;&lt;/code&gt; is a precondition on the
caller — &lt;em&gt;given that &lt;code&gt;a&lt;/code&gt; can be compared&lt;/em&gt; — and the part after is the
ordinary type. &lt;code&gt;elem&lt;/code&gt; does not care whether &lt;code&gt;a&lt;/code&gt; is &lt;code&gt;Int&lt;/code&gt; or &lt;code&gt;Color&lt;/code&gt;; it
only asks that &lt;code&gt;==&lt;/code&gt; exist.&lt;/p&gt;
&lt;p&gt;Which brings us to the incantation stapled to nearly every &lt;code&gt;data&lt;/code&gt;
declaration in Tamal:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Phase&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Idle&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Running&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Halted&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; stock&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Generic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Show&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Eq&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Enum&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bounded&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  deriving&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; anyclass&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;NFDataX&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;deriving&lt;/code&gt; is the compiler offering to write the boring instances for
you, so you never hand-roll a &lt;code&gt;==&lt;/code&gt; that just compares constructors. The
list looks like noise the first time; here is what each name buys, so
it stops looking like noise:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Show&lt;/code&gt;&lt;/strong&gt; — the type is printable, so a failing property test can
show you the exact value it choked on.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Eq&lt;/code&gt;&lt;/strong&gt; — values can be compared with &lt;code&gt;==&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Enum&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;Bounded&lt;/code&gt;&lt;/strong&gt; — “list me every constructor, in
order,” which is exactly what an exhaustive test over all &lt;code&gt;Phase&lt;/code&gt;s
needs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Generic&lt;/code&gt;&lt;/strong&gt; — a structural, machine-readable description of the
type that other libraries build on top of; plumbing, rarely read by
humans.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;NFDataX&lt;/code&gt;&lt;/strong&gt; — the one that is really about hardware. It is Clash’s
way of saying &lt;strong&gt;this type is allowed to sit in a register&lt;/strong&gt; — that
its values, including “undefined at power-up,” are well-defined
enough to be stored in a flip-flop.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;stock&lt;/code&gt; versus &lt;code&gt;anyclass&lt;/code&gt; split is only &lt;em&gt;which machinery&lt;/em&gt; writes
the instance — a built-in strategy for the classic classes, a
generics-based one for &lt;code&gt;NFDataX&lt;/code&gt;. It is a detail you will copy more
often than you think about. The takeaway: a wall of &lt;code&gt;deriving&lt;/code&gt; atop a
Tamal type is not doing anything deep — it is the compiler filling in
equality, printing, enumeration, and “fits in a register,” so the
author didn’t have to.&lt;/p&gt;
&lt;h2 id=&quot;types-that-count-widths-in-the-type&quot;&gt;Types that count: widths in the type&lt;/h2&gt;
&lt;p&gt;Here is the feature that startles newcomers most, and delights them
shortly after: in Clash, &lt;strong&gt;numbers can live in types.&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; BitVector&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 32&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;   --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; exactly 32 bits&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Vec&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Bit&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;      --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; exactly 4 elements&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Index&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 5&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;        --&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; a value 0..4, and never 5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;32&lt;/code&gt;, the &lt;code&gt;4&lt;/code&gt;, and the &lt;code&gt;5&lt;/code&gt; are not runtime values or comments.
They are &lt;em&gt;part of the type&lt;/em&gt;, checked at compile time like everything
else. &lt;code&gt;BitVector 32&lt;/code&gt; is the type of exactly-32-bit vectors — not “up to
32,” not “32 by convention,” exactly 32. &lt;code&gt;Vec 4 Bit&lt;/code&gt; is a vector of
exactly four bits. &lt;code&gt;Index 5&lt;/code&gt; is a number in the range &lt;code&gt;0..4&lt;/code&gt; and, by
construction, &lt;em&gt;never&lt;/em&gt; 5 — the natural type for an index into something
with five slots.&lt;/p&gt;
&lt;p&gt;Because the width is in the type, the compiler enforces it. You cannot
index past the end of a &lt;code&gt;Vec 4&lt;/code&gt;, because the index type will not let
the out-of-range value exist in the first place. You cannot wire a
&lt;code&gt;BitVector 4&lt;/code&gt; into a port expecting a &lt;code&gt;BitVector 8&lt;/code&gt;; they are different
types and the mismatch is a compile error. The clean way to say what
that buys you:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The compiler is a wire-width checker.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Anyone who has written RTL knows the dread here. In Verilog a width
mismatch is not an error — it is a silent zero-extension or, worse, a
silent truncation you discover three days later as a dropped high bit.
In Clash the same mistake is a type error, reported by name, before you
run a single test. Tamal leans on this everywhere: an &lt;code&gt;Unsigned AW&lt;/code&gt;,
where &lt;code&gt;AW&lt;/code&gt; is the address width, carries the program-counter size in
the type; an &lt;code&gt;Index 5&lt;/code&gt; pins a five-state micro-FSM step to exactly its
five legal values. A whole class of off-by-one-bit bugs never makes it
past the compiler — a strictly better place to catch them than a logic
analyzer.&lt;/p&gt;
&lt;h2 id=&quot;from-pure-functions-to-hardware-signal-and-mealy&quot;&gt;From pure functions to hardware: &lt;code&gt;Signal&lt;/code&gt; and &lt;code&gt;mealy&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Everything up to now has been combinational: pure functions, inputs to
outputs, no memory. But hardware has clocks, and sequential logic needs
a notion of &lt;strong&gt;time&lt;/strong&gt;. Clash spells time with a type called &lt;code&gt;Signal&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;counter&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Signal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; dom&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;Unsigned&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read &lt;code&gt;Signal dom a&lt;/code&gt; as “a value of type &lt;code&gt;a&lt;/code&gt; that may change on every
clock tick, in clock domain &lt;code&gt;dom&lt;/code&gt;.” It is an endless stream — one
sample per cycle, across all of time — which is precisely what a wire
&lt;em&gt;is&lt;/em&gt; once you stop looking at a single instant and watch it over the
whole run. The &lt;code&gt;dom&lt;/code&gt; parameter names the clock domain, so the type
system can stop two clocks from crossing by accident; for reading Tamal
you can mostly gloss it as “the clock.”&lt;/p&gt;
&lt;p&gt;The bridge from the pure world to the clocked one is a single function,
&lt;code&gt;mealy&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; mealy :: (s -&amp;gt; i -&amp;gt; (s, o)) -&amp;gt; s -&amp;gt; (Signal dom i -&amp;gt; Signal dom o)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ::&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; State&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Input&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;State&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; Output&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Look past the parameters and the shape is simple. You write an
ordinary, pure step function — current state and input in, next state
and output out — plus an initial state. &lt;code&gt;mealy&lt;/code&gt; takes those two things
and hands back a function on &lt;code&gt;Signal&lt;/code&gt;s: a real register holding the
state, clocked, with your step function as its next-state and output
logic. That is a textbook &lt;strong&gt;Mealy machine&lt;/strong&gt;, which is where the name
comes from, and it is the heart of the design — Tamal’s engine is,
quite literally, &lt;code&gt;mealy stepM initState&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is the punchline behind the &lt;a href=&quot;https://balbi.sh/posts/tamal-introducing/&quot;&gt;introduction post&lt;/a&gt;’s claim
that its tests run in under a second. &lt;code&gt;stepM&lt;/code&gt; is just a function —
&lt;code&gt;State&lt;/code&gt; and &lt;code&gt;Input&lt;/code&gt; in, new &lt;code&gt;State&lt;/code&gt; and &lt;code&gt;Output&lt;/code&gt; out, pure, no clock,
no simulator — so you can hammer it with thousands of property tests in
milliseconds against a reference model, and only &lt;em&gt;after&lt;/em&gt; you trust it
does &lt;code&gt;mealy&lt;/code&gt; make it hardware. The pure core is the part you test;
&lt;code&gt;mealy&lt;/code&gt; is the thin bridge to silicon. We’ll pull that thread in the
block posts; for now it is enough to spot the &lt;code&gt;mealy&lt;/code&gt; at the top of a
module and read it as “clocked state machine, its brain the pure
function beside it.”&lt;/p&gt;
&lt;h2 id=&quot;where-to-go-next&quot;&gt;Where to go next&lt;/h2&gt;
&lt;p&gt;That is the whole toolkit. Look back at what you can now read without
flinching: a Tamal type signature is $f : A \to B$ with the widths
written into the types; a &lt;code&gt;data&lt;/code&gt; block is sums and products, with
&lt;code&gt;Maybe&lt;/code&gt; for the things that might be absent; a &lt;code&gt;case&lt;/code&gt; or &lt;code&gt;\case&lt;/code&gt; takes
those values apart, exhaustively, with the compiler checking your work;
the stack of &lt;code&gt;deriving&lt;/code&gt; clauses is the compiler filling in equality,
printing, enumeration, and “fits in a register”; a &lt;code&gt;BitVector n&lt;/code&gt; or an
&lt;code&gt;Index n&lt;/code&gt; is a wire whose width the type system guards; and the &lt;code&gt;mealy&lt;/code&gt;
at the top of a module is a clocked state machine wrapped around an
ordinary, testable function.&lt;/p&gt;
&lt;p&gt;None of that is the &lt;em&gt;interesting&lt;/em&gt; part of Tamal. It is the syntax you
had to stop tripping over so the interesting part could show through.
Next in the series we do exactly that: open a single block and read it
end to end, top to bottom, no hand-waving. I’ll start with a gentle
one — the CRC unit, or the UART, something small enough to hold whole
in your head — now that the Haskell is no longer standing between you
and the hardware. Bring the mental model of arrows snapping together;
that is most of what you will need.&lt;/p&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Tamal: an eSPI exerciser</title>
    <published>2026-07-13T09:00:00+00:00</published>
    <updated>2026-07-13T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/tamal-introducing/" type="text/html"/>
    <id>https://balbi.sh/posts/tamal-introducing/</id>
    <summary type="html">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/mole&quot;&gt;mole&lt;/a&gt; is an FPGA bit-cycle engine for exercising I²C and I³C — you
write a little program, it drives the bus edge by edge, legal or
illegal, and hands you back a structured trace of what happened.
&lt;em&gt;Tamal&lt;/em&gt; is that same idea aimed at a different, meaner bus: Intel’s
&lt;a rel=&quot;external&quot; href=&quot;https://www.intel.com/content/www/us/en/support/articles/000020952/software/chipset-software.html&quot;&gt;Enhanced Serial Peripheral Interface&lt;/a&gt; (eSPI, base spec
rev 1.0).&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/mole&quot;&gt;mole&lt;/a&gt; is an FPGA bit-cycle engine for exercising I²C and I³C — you
write a little program, it drives the bus edge by edge, legal or
illegal, and hands you back a structured trace of what happened.
&lt;em&gt;Tamal&lt;/em&gt; is that same idea aimed at a different, meaner bus: Intel’s
&lt;a rel=&quot;external&quot; href=&quot;https://www.intel.com/content/www/us/en/support/articles/000020952/software/chipset-software.html&quot;&gt;Enhanced Serial Peripheral Interface&lt;/a&gt; (eSPI, base spec
rev 1.0).&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;
&lt;p&gt;The one-line pitch, which is the vision more than the current state:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A programmable eSPI controller/target that turns compliance testing
into a reproducible, fully-observable, byte-for-byte deterministic
exercise.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Read that as the destination, not the odometer. Tamal is
&lt;strong&gt;experimental&lt;/strong&gt; — I’m keeping that adjective on it until it has
withstood considerable real-world load, at which point I’ll drop it.
Today it plays eSPI &lt;strong&gt;controller&lt;/strong&gt; only (target role is designed but
not built), at a &lt;strong&gt;single 20 MHz clock&lt;/strong&gt; (all eSPI speeds are on the
roadmap), and everything below the honesty line near the end of this
post runs in &lt;em&gt;simulation&lt;/em&gt;, not on silicon.&lt;/p&gt;
&lt;p&gt;This post is what Tamal is, why it’s shaped the way it is, why the
gateware is written in Haskell of all things, what I’ve learned
building it — and, honestly, how early it still is. The repo is
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/tamal&quot;&gt;github.com/felipebalbi/tamal&lt;/a&gt;, public and split-licensed. Read
on before you clone it expecting a finished product; it isn’t one yet.&lt;/p&gt;
&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;/h2&gt;
&lt;p&gt;eSPI is the bus that replaced LPC on modern platforms: a short,
fast-ish serial link between a chipset/SoC and an embedded controller,
flash device, or BMC. It’s more capable than it looks. A single eSPI
link multiplexes four logical channels — &lt;strong&gt;Peripheral&lt;/strong&gt;, &lt;strong&gt;Virtual
Wire&lt;/strong&gt;, &lt;strong&gt;OOB&lt;/strong&gt; (tunneled SMBus), and &lt;strong&gt;Runtime Flash Access&lt;/strong&gt; — over
one set of pins, with command/response framing, turnaround phases,
CRC, alerts, and a status register that gates who’s allowed to talk.&lt;/p&gt;
&lt;p&gt;That density is exactly what makes it hard to test. “Does my eSPI
target behave correctly?” is not one question. It’s: does it honour
turnaround timing, does it respect CRC when enabled, does it track the
status register, does it tolerate a WAIT STATE, does it drive alerts
correctly, does it decode each channel’s packets, and — the part
almost nothing can produce on demand — &lt;em&gt;does it do the right thing
when the other side does the wrong thing?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Producing a legal transaction is the easy 20%. Producing a specific
&lt;em&gt;illegal&lt;/em&gt; one — a deliberately short turnaround, a corrupted CRC, a
truncated packet, a cycle on a channel that isn’t enabled yet — at a
precise moment, reproducibly, over and over, is the 80% that a real
device or a canned test fixture can’t give you. That’s the gap Tamal
is built to fill: an exerciser that will drive &lt;em&gt;any&lt;/em&gt; cycle, correct or
malformed, with deterministic timing, and capture everything the bus
did in response.&lt;/p&gt;
&lt;h2 id=&quot;the-name&quot;&gt;The name&lt;/h2&gt;
&lt;p&gt;eSPI wraps four logical channels into one serial packet stream. A
&lt;em&gt;tamal&lt;/em&gt; is a wrapped, layered dish — masa and filling folded inside a
husk. The mapping wrote itself: eSPI wraps Peripheral, Virtual Wire,
OOB, and Runtime Flash Access into one stream of bytes on the wire;
Tamal is the thing that unwraps every layer and checks it against the
spec. Naming projects is one of the few unambiguous joys of this line
of work, and the &lt;em&gt;mole&lt;/em&gt; → &lt;em&gt;tamal&lt;/em&gt; lineage (both wrapped, both
Mesoamerican, both about what’s hidden inside) was too good to pass
up.&lt;/p&gt;
&lt;h2 id=&quot;the-key-insight&quot;&gt;The key insight&lt;/h2&gt;
&lt;p&gt;The temptation with an FPGA bus tool is to think of it as a throughput
problem — get the bytes on and off the wire fast enough. eSPI defeats
that framing immediately, and in a useful way:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is &lt;strong&gt;not&lt;/strong&gt; a throughput problem. It is an &lt;strong&gt;external timing
alignment&lt;/strong&gt; problem.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;eSPI tops out at 66 MHz. On an Artix-7 that’s &lt;em&gt;slow&lt;/em&gt;; the fabric runs
at 100 MHz and never breaks a sweat pushing bits. The danger isn’t
speed. It’s that in &lt;strong&gt;target&lt;/strong&gt; role — which Tamal is designed for but
does not yet play — you are not the clock master: the eSPI clock is
driven by the device under test, and you must respond &lt;em&gt;relative to
it&lt;/em&gt;. The parts that will actually hurt you are turnaround/tri-state
timing, setup and hold against an external clock, and getting IO
direction control exactly right on a shared, bidirectional data bus.
None of that is about going fast. All of it is about being aligned.&lt;/p&gt;
&lt;p&gt;Internalizing that reframed the whole design. The engine doesn’t need
to be clever or quick. It needs to be &lt;em&gt;precise&lt;/em&gt; about when it drives,
when it samples, and when it lets go of the pins.&lt;/p&gt;
&lt;h2 id=&quot;the-shape-three-planes&quot;&gt;The shape: three planes&lt;/h2&gt;
&lt;p&gt;Tamal has a clean split into three planes, and almost every design
decision falls out of which plane it belongs to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Control plane&lt;/strong&gt; (host → FPGA): load a compiled test program,
select role (controller today; target is designed but not yet built),
IO mode (single/dual/quad), CRC on or off, and triggers. Error
injection isn’t a knob here — it lives &lt;em&gt;in the program&lt;/em&gt;, where the
test author writes the malformed cycle directly (more on that below).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bus plane&lt;/strong&gt; (FPGA ↔ DUT): the eSPI link itself — &lt;code&gt;CS#&lt;/code&gt;, &lt;code&gt;CLK&lt;/code&gt;,
&lt;code&gt;IO[3:0]&lt;/code&gt;, &lt;code&gt;ALERT#&lt;/code&gt;, &lt;code&gt;RESET#&lt;/code&gt; — driven or sampled against the device
under test.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trace / result plane&lt;/strong&gt; (FPGA → host): observed transactions,
channel decode, captured cycles, and verdicts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There’s one load-bearing rule that spans the planes: &lt;strong&gt;never block the
bus on trace backpressure.&lt;/strong&gt; If the host can’t keep up draining the
trace, the bus plane does not stall waiting for it — events get
dropped with an overflow marker instead. A test rig that perturbs the
timing of the thing it’s measuring is worse than useless, so the trace
plane is always allowed to lose data before the bus plane is allowed
to hesitate.&lt;/p&gt;
&lt;p&gt;On the FPGA, that shape is deliberately boring:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; host ──UART──► loader ──► instr BRAM ──► engine (mealy step) ──► eSPI pads&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      ◄─UART──── drain  ◄──── ring BRAM ◄──────┘   (IO[3:0], CS#, SCK,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                                                     RESET#, ALERT#)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The host ships a compiled program over UART; the loader writes it into
an instruction block RAM; the engine executes it as a Mealy machine,
driving and sampling the eSPI pads; every transaction lands in a trace
ring (another block RAM); on &lt;code&gt;HALT&lt;/code&gt; the drainer sweeps the ring back
out over the same UART.&lt;/p&gt;
&lt;p&gt;The whole thing is &lt;strong&gt;one clock domain&lt;/strong&gt; — &lt;code&gt;Dom100&lt;/code&gt;, 100 MHz — with no
clock-domain crossings and no FIFOs. The trace ring BRAM &lt;em&gt;is&lt;/em&gt; the
buffer, and UART is roughly 500× slower than the fabric, so there’s
nothing to gain from a FIFO in front of it. (On the Cyclone V board a
50 MHz oscillator is multiplied up to 100 MHz by an Altera PLL, but
it’s still a single design domain.) Fewer moving parts, less to get
wrong, and — this matters later — a design that simulates end to end
without any special-casing.&lt;/p&gt;
&lt;h2 id=&quot;the-repo&quot;&gt;The repo&lt;/h2&gt;
&lt;p&gt;Two toolchains, cleanly separated:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;crates/                 Rust host tooling (Cargo workspace)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tamal-abi/            bytecode/ISA encoding + COBS/CRC-8 wire format + typed trace decode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tamal-asm/            assembler: RISC-V-flavored source -&amp;gt; tamal bytecode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tamal-asm-cli/        `tamal-asm` binary (clap front-end)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tamal-loader/         host-side loader: load -&amp;gt; trigger -&amp;gt; drain over a transport&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tamal-loader-cli/     `tamal-loader` binary&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;hdl/                    Clash gateware + Vivado/Quartus build (self-contained)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;docs/                   design specs + implementation plans&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;tamal-abi&lt;/code&gt; is the crate that matters most: it’s the ABI, the single
source of truth for the bytecode encoding, the COBS/CRC-8 wire format,
and the typed trace decode. Everything else — assembler, loader,
gateware — agrees with &lt;code&gt;tamal-abi&lt;/code&gt; byte for byte or it’s a bug. It is
deliberately &lt;strong&gt;transport-agnostic&lt;/strong&gt;: v1 speaks UART because that’s
what the Arty’s FT2232 gives you (USB-UART plus JTAG, not a USB3
SuperSpeed FIFO), but the wire format doesn’t know or care, so a
future EZ-USB FX3 (GPIF II slave-FIFO) shield can be bolted on as
another backend without touching the ABI.&lt;/p&gt;
&lt;p&gt;The licensing follows the &lt;code&gt;crates/&lt;/code&gt; ↔ &lt;code&gt;hdl/&lt;/code&gt; seam. The Rust host
tooling is &lt;strong&gt;MIT&lt;/strong&gt;. The Clash gateware is &lt;strong&gt;CERN-OHL-W-2.0&lt;/strong&gt; (CERN
Open Hardware Licence v2, Weakly Reciprocal), because an open-hardware
licence fits a hardware description better than a software licence
does. Every &lt;code&gt;.hs&lt;/code&gt; file under &lt;code&gt;hdl/&lt;/code&gt; carries a REUSE-style SPDX header
to make that boundary machine-checkable.&lt;/p&gt;
&lt;h2 id=&quot;a-risc-v-flavored-isa-and-a-very-dumb-engine&quot;&gt;A RISC-V-flavored ISA, and a very dumb engine&lt;/h2&gt;
&lt;p&gt;Tamal’s on-FPGA engine is programmable, and the instruction set is
&lt;strong&gt;inspired by — but not compatible with — the RISC-V 32-bit (RV32I)
ISA.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;From RV32I it borrows the ergonomics: 32-bit fixed-width instructions;
a 32-name register space &lt;code&gt;x0&lt;/code&gt;..&lt;code&gt;x31&lt;/code&gt; with &lt;code&gt;x0&lt;/code&gt; hardwired to zero (v1
implements 16 physical registers, with &lt;code&gt;x16&lt;/code&gt;..&lt;code&gt;x31&lt;/code&gt; aliasing their
low-4 twin); the R/I/S/B/U/J instruction formats; and the ABI register
names (&lt;code&gt;zero&lt;/code&gt;, &lt;code&gt;ra&lt;/code&gt;, &lt;code&gt;sp&lt;/code&gt;, &lt;code&gt;t0&lt;/code&gt;, &lt;code&gt;s0&lt;/code&gt;, &lt;code&gt;a0&lt;/code&gt;, …). The assembler follows
the &lt;a rel=&quot;external&quot; href=&quot;https://github.com/riscv-non-isa/riscv-asm-manual&quot;&gt;riscv-asm-manual&lt;/a&gt; conventions for directives, labels,
and pseudo-instructions — but &lt;em&gt;follows&lt;/em&gt; is doing real work in that
sentence. What’s implemented today is the subset the examples exercise
(&lt;code&gt;.text&lt;/code&gt;, &lt;code&gt;.globl&lt;/code&gt;, &lt;code&gt;.equ&lt;/code&gt;, the &lt;code&gt;li&lt;/code&gt; pseudo-instruction, symbolic
labels, the branch mnemonics). The rest of the RISC-V surface I’d like
to have — &lt;code&gt;.align&lt;/code&gt;, &lt;code&gt;.macro&lt;/code&gt;, numeric local labels (&lt;code&gt;1f&lt;/code&gt;/&lt;code&gt;1b&lt;/code&gt;),
&lt;code&gt;mv&lt;/code&gt;/&lt;code&gt;j&lt;/code&gt;/&lt;code&gt;call&lt;/code&gt;/&lt;code&gt;ret&lt;/code&gt;/&lt;code&gt;beqz&lt;/code&gt; and friends — is intended but not written
yet. If you’ve used RISC-V assembly the current subset will read like
home; just don’t assume the whole manual works.&lt;/p&gt;
&lt;p&gt;Where it diverges is the opcode space: Tamal repurposes and extends it
with bus-domain instructions for eSPI work — driving and sampling
cycles, per-channel operations, deterministic timing, author-written
error injection, and capture/verdict. The upshot is a hard rule: Tamal
bytecode is &lt;strong&gt;not&lt;/strong&gt; interchangeable with a stock RISC-V toolchain, and
the project never claims otherwise. It’s RISC-V-&lt;em&gt;flavored&lt;/em&gt;, not
RISC-V.&lt;/p&gt;
&lt;p&gt;The design decision I’m happiest with is how little the engine knows.
&lt;strong&gt;The engine is a nearly-dumb SPI shifter with almost no eSPI
knowledge.&lt;/strong&gt; There is no “PUT_IORD” opcode and no notion of a channel.
The one concession is a CRC-8 unit that runs over &lt;em&gt;incoming&lt;/em&gt; bytes, so
a program can read the residue and decide whether a packet from the
link partner was valid — but even it has no idea what a “packet” &lt;em&gt;is&lt;/em&gt;,
and every &lt;em&gt;outbound&lt;/em&gt; CRC byte is still computed by the host. The host
builds every byte that goes on the wire — command, header, payload, TX
CRC — and the engine just shifts them out and shifts responses back.
All the eSPI semantics live in the program, in software, where they’re
easy to read, easy to change, and easy to get deliberately wrong.&lt;/p&gt;
&lt;h2 id=&quot;the-shape-of-a-program&quot;&gt;The shape of a program&lt;/h2&gt;
&lt;p&gt;The smallest interesting program reads one byte from an eSPI
Peripheral-channel I/O port. Here’s the real &lt;code&gt;peripheral_io_read.s&lt;/code&gt;
from the repo, reading I/O port &lt;code&gt;0x64&lt;/code&gt; — the classic 8042
keyboard-controller status port:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;asm&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;equ&lt;/span&gt;&lt;span&gt;  PUT_IORD1,      &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0x44&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;    # PUT_IORD_SHORT, length = 1 byte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;equ&lt;/span&gt;&lt;span&gt;  RSP_WAIT_STATE, &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0x0F&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;    # eSPI WAIT_STATE response code&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;equ&lt;/span&gt;&lt;span&gt;  VERDICT_OK,     &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;    # host verdict codes (written by halt)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;equ&lt;/span&gt;&lt;span&gt;  VERDICT_CRC,    &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0x11&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    .text&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    .globl _start&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;_start&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    set_config CONTROLLER, X1, SCK20, ALERT_PIN  &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; # controller, x1 IO, 20 MHz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    cs_assert                      &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; # begin frame: CS# low&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;    # --- command phase: host-built eSPI packet ---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    put_byte PUT_IORD1             &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; # CMD:  PUT_IORD_SHORT (1 byte)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    put_byte &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;                   # addr [15:8]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    put_byte &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0x64&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;                   # addr [7:0]  -&amp;gt; I/O port 0x64&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    put_byte &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0x16&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;                   # TX CRC-8 over the 3 bytes above (poly 0x07)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    tar &lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;                           # legal turnaround; tar 3 / tar 1 = deliberate violation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;    # --- response phase: WAIT_STATE poll + RX CRC residue verdict ---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;poll&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    crc_reset                      &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; # drop any prior WAIT_STATE byte from the residue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    get_byte t0                    &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; # response code (auto-updates RX CRC-8)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    li   t1, RSP_WAIT_STATE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    beq  t0, t1, poll              &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; # WAIT_STATE -&amp;gt; keep polling&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    get_byte t0                    &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; # read data byte (port 0x64 value)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    get_byte t0                    &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; # status [7:0]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    get_byte t0                    &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; # status [15:8]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    get_byte t0                    &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; # trailing CRC byte -&amp;gt; drives residue to 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    rdsr t2, CRC                   &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; # RX CRC-8 residue (0 == good packet)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    cs_deassert                    &lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; # end frame: CS# high&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    bnez t2, bad_crc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    halt VERDICT_OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;bad_crc&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    halt VERDICT_CRC&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read it top to bottom and the whole model is visible. &lt;code&gt;set_config&lt;/code&gt;
picks controller role, single (x1) IO, a 20 MHz clock (&lt;code&gt;SCK20&lt;/code&gt; — the
only rate wired up today, though the ISA reserves the field for the
faster eSPI speeds still to come), and pin-based alerts. &lt;code&gt;cs_assert&lt;/code&gt;
pulls &lt;code&gt;CS#&lt;/code&gt; low. Then a run of &lt;code&gt;put_byte&lt;/code&gt;s spells out the eSPI command
packet a byte at a time — opcode, address, and a TX CRC-8 the &lt;em&gt;program&lt;/em&gt;
computed, not the engine. &lt;code&gt;tar 2&lt;/code&gt; performs a legal turnaround;
swapping in &lt;code&gt;tar 3&lt;/code&gt; or &lt;code&gt;tar 1&lt;/code&gt; is how you inject a deliberate
turnaround violation — that’s the whole error-injection story, a test
author spelling out the malformed cycle by hand, not a probabilistic
“corrupt 10% of packets” knob. The response phase polls for
WAIT_STATE, reads the data and status bytes, and checks that the RX
CRC residue drove to zero — the eSPI-idiomatic way to validate a
packet. Finally &lt;code&gt;halt&lt;/code&gt; writes a verdict code the host reads back from
the trace.&lt;/p&gt;
&lt;p&gt;The load-bearing observation: &lt;strong&gt;only the command-phase &lt;code&gt;put_byte&lt;/code&gt;s are
channel-specific.&lt;/strong&gt; The CS framing, the WAIT_STATE poll, and the CRC
residue check are byte-for-byte identical across the Peripheral I/O
read, the &lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/tamal/blob/main/examples/virtual_wire_pltrst.s&quot;&gt;Virtual Wire&lt;/a&gt; example that deasserts &lt;code&gt;PLTRST#&lt;/code&gt;,
and every other per-channel program in &lt;code&gt;examples/&lt;/code&gt;. The channel is
just a different run of bytes in the middle. That’s the payoff of a
dumb engine: the interesting part of each test is a handful of lines,
and everything around it is boilerplate you can read once and stop
thinking about.&lt;/p&gt;
&lt;h2 id=&quot;why-clash-why-haskell&quot;&gt;Why Clash, why Haskell&lt;/h2&gt;
&lt;p&gt;The gateware is written in &lt;a rel=&quot;external&quot; href=&quot;https://clash-lang.org&quot;&gt;Clash&lt;/a&gt; — a Haskell-to-RTL compiler — and
that’s the choice people ask about first. It looks exotic next to
Verilog or even the SpinalHDL that drives mole. The reasons are
entirely practical, and they all come back to one thing: &lt;strong&gt;iteration
speed&lt;/strong&gt; — how fast the edit-test loop closes. (Not to be confused with
eSPI’s turnaround; this is developer seconds, not bus cycles.)&lt;/p&gt;
&lt;p&gt;The Tamal test suite runs in &lt;strong&gt;under a second.&lt;/strong&gt; Not the synthesis —
the &lt;em&gt;tests&lt;/em&gt;. Every pure leaf of the design (&lt;code&gt;stepM&lt;/code&gt;, &lt;code&gt;ringWrite&lt;/code&gt;,
&lt;code&gt;rigState&lt;/code&gt;, &lt;code&gt;ledPattern&lt;/code&gt;) is a plain Haskell function, and I test them
the way I’d test any Haskell function: &lt;a rel=&quot;external&quot; href=&quot;https://hedgehog.qa&quot;&gt;hedgehog&lt;/a&gt; property tests and
HUnit unit tests, each pure leaf and the engine keystone checked
against a reference model. No simulator to spin up, no waveform to
eyeball, no four-minute Vivado round-trip to find out I got a mux
backwards. &lt;code&gt;cabal test&lt;/code&gt;, a second later I know.&lt;/p&gt;
&lt;p&gt;It gets better at the top. Because the whole integration is wired over
plain Clash &lt;code&gt;Signal&lt;/code&gt;s — no &lt;code&gt;BiSignal&lt;/code&gt; in the core, the tri-state only
appears in the thin board shell — the &lt;em&gt;entire system&lt;/em&gt; simulates in
Haskell. There’s a whole-system cosim (&lt;code&gt;Test.Top&lt;/code&gt;) that serializes a
real &lt;code&gt;LOAD_PROGRAM&lt;/code&gt; + &lt;code&gt;TRIGGER&lt;/code&gt; onto a modeled UART line, runs the
actual load → run → drain path, decodes the UART output, and asserts
the drained trace (revision header, records, &lt;code&gt;HALT&lt;/code&gt; terminator) &lt;em&gt;and&lt;/em&gt;
the eSPI pin activity — UART → loader → engine → eSPI → ring → drain,
end to end, in one test, in the same language as the design, in
milliseconds. I can refactor the engine and know within a second
whether the full pipeline still behaves. That feedback loop is the
single biggest force multiplier on the project.&lt;/p&gt;
&lt;p&gt;The type system pulls its weight too. eSPI is a bidirectional,
tri-state, direction-controlled bus, which is precisely where hardware
bugs love to hide. Having the compiler track widths, states, and pad
directions catches a whole class of “which way is this pin pointing
this cycle” mistakes before simulation, let alone before silicon.&lt;/p&gt;
&lt;p&gt;And the discipline of a single clock domain with no CDC and no FIFOs
means the design is small enough to hold in your head — which is what
makes the sub-second property testing tractable in the first place.
Fast tests and a simple design reinforce each other.&lt;/p&gt;
&lt;p&gt;The trade is real and worth stating: Clash is a smaller ecosystem, the
error messages can be a wall of type, and the toolchain (GHC 9.10.3 +
&lt;code&gt;cabal&lt;/code&gt; + Vivado/Quartus) is more to stand up than &lt;code&gt;verilator&lt;/code&gt;. For
this project, on this bus, that sub-second feedback loop won the
argument easily.&lt;/p&gt;
&lt;h2 id=&quot;lessons-learned&quot;&gt;Lessons learned&lt;/h2&gt;
&lt;p&gt;A few things I didn’t know going in, in case they save you some time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The bus is not the hard part; the pins are.&lt;/strong&gt; I spent the early
design energy worrying about eSPI’s channel matrix and almost none
on tri-state timing. That was backwards. Reframing the whole thing
as an &lt;em&gt;external timing alignment&lt;/em&gt; problem — especially for the
not-yet-built target role — is what made the architecture click.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A dumb engine is a feature.&lt;/strong&gt; Every time I was tempted to teach the
engine more eSPI (“just let it build the TX CRC too”, “just add a
channel register”), pushing it back up into the host program made the
design smaller &lt;em&gt;and&lt;/em&gt; made illegal-cycle injection trivial. Malformed
packets are free when the host builds every byte. (The one place I
gave in — an RX-side CRC-8 so a program can check an inbound residue —
earns its keep precisely because it stops at &lt;em&gt;checking&lt;/em&gt; and never
learns what a packet is.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clash’s tri-state lowering has sharp edges.&lt;/strong&gt; Four scalar &lt;code&gt;inout&lt;/code&gt;
lanes (&lt;code&gt;io0&lt;/code&gt;..&lt;code&gt;io3&lt;/code&gt;) exist instead of a tidy &lt;code&gt;Vec&lt;/code&gt; because Clash
fuses a per-lane &lt;code&gt;BiSignalIn&lt;/code&gt;/&lt;code&gt;BiSignalOut&lt;/code&gt; pair into one &lt;code&gt;inout&lt;/code&gt;
port — but a &lt;code&gt;Vec&lt;/code&gt; of BiSignals does &lt;em&gt;not&lt;/em&gt;; it silently lowers to a
plain input. That one cost me an afternoon.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The no-reset power-up design is deliberate.&lt;/strong&gt; The top ties reset
permanently de-asserted and relies on power-up &lt;code&gt;init&lt;/code&gt;, so Clash emits
no reset port at all — matching the sibling Clash examples. Fighting
that to add a “proper” reset is a trap.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Some ghc-options are load-bearing.&lt;/strong&gt; The &lt;code&gt;common-options&lt;/code&gt; in
&lt;code&gt;tamal.cabal&lt;/code&gt; aren’t optional style — Clash needs them. Trimming them
to tidy up breaks codegen in non-obvious ways.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-s-running-today-and-what-isn-t&quot;&gt;What’s running today — and what isn’t&lt;/h2&gt;
&lt;p&gt;Now the honest part, because this is the whole reason for the
&lt;strong&gt;experimental&lt;/strong&gt; label I put on it up top.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Gateware: v1 complete, in &lt;em&gt;simulation&lt;/em&gt;.&lt;/strong&gt; The full pipeline exists
and is tested in Clash — the RISC-V-flavored cycle engine, the
instruction and trace-ring block RAMs, the COBS/CRC-8 wire format, the
UART load/drain loader, the tri-state eSPI pad boundary, and the
&lt;code&gt;topEntity&lt;/code&gt; that wires it all to the Arty A7 pins. The whole-system
cosim streams a program in and checks the drained trace end to end.
&lt;code&gt;cabal run clash -- Tamal.Board.ArtyA7 --verilog&lt;/code&gt; emits a
synthesizable top, and &lt;code&gt;cd hdl &amp;amp;&amp;amp; make&lt;/code&gt; builds a bitstream. But &lt;strong&gt;v1
is controller role, single (x1) IO, one 20 MHz clock rate, UART
transport, and it has not yet been brought up on real hardware.&lt;/strong&gt;
Simulation-complete is a real milestone; it is not the same as “works
on the bench.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Host tooling: v1 implemented and tested.&lt;/strong&gt; The Rust ABI, assembler,
and loader are built and mirror the gateware’s wire and bytecode
contract byte for byte. The live serial path is exercised on hardware
rather than in CI, and the pass/fail &lt;em&gt;conformance verdict engine&lt;/em&gt; — the
thing that would let Tamal actually render a compliance judgment —
does not exist yet. Today a program ends in a &lt;code&gt;HALT&lt;/code&gt;/&lt;code&gt;TRAP&lt;/code&gt; with a
host-defined verdict code; a real conformance catalog is a later
phase.&lt;/p&gt;
&lt;p&gt;So, plainly: &lt;strong&gt;Tamal is early.&lt;/strong&gt; What it most needs next is validation
against &lt;em&gt;real&lt;/em&gt; eSPI scenarios — on real silicon, against real targets,
with a logic analyzer confirming the timing the simulator promises.
Until on-hardware bring-up lands, every claim in this post about what
the bus does is a claim the &lt;em&gt;model&lt;/em&gt; makes, and models are exactly the
thing an exerciser exists to distrust.&lt;/p&gt;
&lt;p&gt;The roadmap is four phases:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Link + transaction bring-up.&lt;/strong&gt; SPI-style framing,
command/response/turnaround, Get Status / Get Configuration, CRC;
controller role over single IO. &lt;em&gt;(This is where v1 sits — done in
sim, next on hardware.)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The four channels.&lt;/strong&gt; Peripheral, Virtual Wire, OOB (tunneled
SMBus), Runtime Flash Access; the ISA, assembler, and loader path;
result streaming.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Target role, alerts, dual/quad IO, faster clocks.&lt;/strong&gt; The
external-timing-alignment problem in full: responding to a clock you
don’t own, and driving the higher eSPI speeds beyond today’s 20 MHz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Determinism and verdicts.&lt;/strong&gt; The error-injection model stays what
it is today — the test author writes the malformed cycle by hand, so
a program is byte-for-byte reproducible by construction — joined by
the verdict engine and a conformance catalog that turn a captured
trace into an actual pass/fail judgment.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;where-to-look&quot;&gt;Where to look&lt;/h2&gt;
&lt;p&gt;The source is &lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/tamal&quot;&gt;on GitHub&lt;/a&gt;. The gateware’s story is in
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/tamal/blob/main/hdl/README.md&quot;&gt;&lt;code&gt;hdl/README.md&lt;/code&gt;&lt;/a&gt; and &lt;code&gt;hdl/PLAN.md&lt;/code&gt;; the design specs live
under &lt;code&gt;docs/&lt;/code&gt;; &lt;code&gt;AGENTS.md&lt;/code&gt; is the fastest orientation to how the
pieces fit. The &lt;code&gt;examples/&lt;/code&gt; directory is the best way to get the feel
of the ISA — start with &lt;code&gt;smoke_halt.s&lt;/code&gt;, then &lt;code&gt;peripheral_io_read.s&lt;/code&gt;,
then the per-channel programs.&lt;/p&gt;
&lt;p&gt;It’s early, it’s promising, and it’s exactly the stage where design
critiques are most useful. If you know eSPI — especially its nastier
corners — I’d love to hear where this is wrong before it meets
silicon.&lt;/p&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>From &#39;it works&#39; to &#39;I trust it&#39;: two torture loops on a NOR driver</title>
    <published>2026-06-28T09:00:00+00:00</published>
    <updated>2026-06-28T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/flexspi-mcxa-hardening/" type="text/html"/>
    <id>https://balbi.sh/posts/flexspi-mcxa-hardening/</id>
    <summary type="html">&lt;p&gt;The FlexSPI NOR driver for the NXP MCXA5xx already worked. The
blocking self-test was green, the examples ran, I could erase a
sector, program a page, read it back, and watch the same bytes come
out of the memory-mapped window. By the usual bar for “the driver
works,” it worked.&lt;/p&gt;
&lt;p&gt;That bar is too low. A driver that passes its own happy-path
self-test has demonstrated exactly one thing: that it can succeed when
nothing goes wrong. It has said nothing about what happens when an
async operation gets cancelled half a microsecond into a command
shift, or when a caller does a partial-page write at an address the
controller quietly dislikes. The distance between “it works” and “I
trust it” is made entirely of those questions, and you don’t answer
them by running the happy path again.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;The FlexSPI NOR driver for the NXP MCXA5xx already worked. The
blocking self-test was green, the examples ran, I could erase a
sector, program a page, read it back, and watch the same bytes come
out of the memory-mapped window. By the usual bar for “the driver
works,” it worked.&lt;/p&gt;
&lt;p&gt;That bar is too low. A driver that passes its own happy-path
self-test has demonstrated exactly one thing: that it can succeed when
nothing goes wrong. It has said nothing about what happens when an
async operation gets cancelled half a microsecond into a command
shift, or when a caller does a partial-page write at an address the
controller quietly dislikes. The distance between “it works” and “I
trust it” is made entirely of those questions, and you don’t answer
them by running the happy path again.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;
&lt;p&gt;You answer them by building something whose only job is to be nasty to
the driver and to notice when the driver flinches. This post is about
two such things — a pair of self-checking soak rigs for the
&lt;code&gt;embassy-mcxa&lt;/code&gt; FlexSPI driver — and the two bugs they caught on the
way. The work landed in &lt;a rel=&quot;external&quot; href=&quot;https://github.com/embassy-rs/embassy/pull/6389&quot;&gt;embassy-rs/embassy#6389&lt;/a&gt;, validated on an
FRDM-MCXA577 with its on-board Winbond W25Q64 (8 MiB), and it builds on
the register-level fixes (&lt;a rel=&quot;external&quot; href=&quot;https://github.com/embassy-rs/embassy/pull/6386&quot;&gt;AHBCR&lt;/a&gt;, &lt;code&gt;DLLCR&lt;/code&gt;, &lt;code&gt;FLSHCR2&lt;/code&gt;) from
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/embassy-rs/embassy/pull/6386&quot;&gt;#6386&lt;/a&gt; — more on how &lt;em&gt;those&lt;/em&gt; got found in a moment, because it
was the same rig.&lt;/p&gt;
&lt;h2 id=&quot;what-a-torture-loop-has-to-be&quot;&gt;What a torture loop has to be&lt;/h2&gt;
&lt;p&gt;A soak test that only loops the happy path faster is not a soak test;
it’s a slow way to confirm what you already believed. To actually buy
trust, the loop has to have a few properties, and getting those
properties right was most of the work.&lt;/p&gt;
&lt;p&gt;It has to be &lt;strong&gt;adversarial&lt;/strong&gt;: every round erases a fresh window,
programs it with randomly-sized, randomly-placed sub-page writes and
full pages, and then reads it back through &lt;em&gt;two independent paths&lt;/em&gt; —
the IP command path and the memory-mapped AHB window — asserting they
agree with each other and with the pattern it wrote. A bug that lives
in only one path can’t hide when the other path is watching.&lt;/p&gt;
&lt;p&gt;It has to be &lt;strong&gt;reproducible&lt;/strong&gt;: every round is driven by a logged
xorshift seed, so a failure three hours in isn’t a ghost — it’s a seed
I can replay from a clean boot.&lt;/p&gt;
&lt;p&gt;It has to &lt;strong&gt;protect itself from itself&lt;/strong&gt;: a torture loop that programs
random addresses is one off-by-one away from scribbling on the sector
it’s verifying, or wearing a single sector to death. So the window
rotates by a prime stride to spread wear, a per-sector erase budget
caps the damage, a wall-clock limit ends the run, and a &lt;strong&gt;never-erased
canary sector&lt;/strong&gt; sits off to the side. If a wild write ever lands in
the canary, the canary stops matching, and I learn that the driver
wrote somewhere it was never told to.&lt;/p&gt;
&lt;p&gt;Stripped of the bookkeeping, each round looks like this:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;loop&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;    let&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; seed&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; rng&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;next&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;          //&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; logged, so any failure replays&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;    let&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; window&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; next_window&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;seed&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; prime-stride rotation, wear-spread&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;    erase&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;window&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;    verify_erased&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;window&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    for&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; write&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; random_writes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;seed&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; window&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;        program&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span&gt;addr&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; write&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span&gt;bytes&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; The load-bearing assertion: two independent read paths must&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; agree with each other and with what we wrote.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;    assert_eq!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;read_ip&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;window&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;   expected&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;seed&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;    assert_eq!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;read_mmap&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;window&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; expected&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;seed&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;    assert_canary_intact&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;        //&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; nobody wrote where they shouldn&amp;#39;t&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    if&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; elapsed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; LIMIT&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; break&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first time I pointed this at the driver, it didn’t get to a clean
round. The IP path and the memory-mapped path disagreed, which is how
the &lt;code&gt;AHBCR.AFLASHBASE&lt;/code&gt; addressing bug surfaced — I fixed that
separately in &lt;a rel=&quot;external&quot; href=&quot;https://github.com/embassy-rs/embassy/pull/6386&quot;&gt;#6386&lt;/a&gt; before any of the rest of this made
sense. With that fixed, the rig got much further, and then found
something stranger.&lt;/p&gt;
&lt;h2 id=&quot;what-it-found-first-the-byte-before-the-write-keeps-going-to-zero&quot;&gt;What it found first: the byte before the write keeps going to zero&lt;/h2&gt;
&lt;p&gt;The failure was small and specific, which is the worst kind. Most
rounds were clean. But on rounds where the random writes happened to
place one sub-page write starting at an address with &lt;code&gt;addr % 8&lt;/code&gt; of 5 or
7, and &lt;em&gt;another&lt;/em&gt; write followed in the same page, the byte immediately
&lt;em&gt;before&lt;/em&gt; the second write’s start address came back as &lt;code&gt;0x00&lt;/code&gt;. Not the
bytes I wrote — those were fine. The byte &lt;em&gt;before&lt;/em&gt; them, which I hadn’t
touched in that operation at all.&lt;/p&gt;
&lt;p&gt;My first assumption was that I had a bug in the FIFO-fill loop: an
off-by-one in how the driver packs the caller’s buffer into the
controller’s TX FIFO. I went and read that loop very carefully, then
did the thing I should have done first and diffed it, byte for byte,
against NXP’s &lt;code&gt;FLEXSPI_WriteBlocking&lt;/code&gt; in their SDK. It was identical.
The driver was filling the FIFO exactly the way the vendor does.&lt;/p&gt;
&lt;p&gt;That reframed the whole problem. If the loop is byte-identical to the
reference and the reference is trusted, the corruption isn’t in the
loop — it’s in the controller’s handling of an unaligned write boundary
between two consecutive writes in a page. A single write of any length,
at any alignment, is fine. It’s specifically the seam between two
sub-page writes when the second one starts unaligned that the IP
mangles. This is a controller alignment quirk, and no amount of
re-reading my code was going to fix a property of the silicon.&lt;/p&gt;
&lt;p&gt;So the fix isn’t to make the unaligned write work — it’s to make the
unaligned write &lt;em&gt;impossible to ask for&lt;/em&gt;, loudly, instead of letting it
silently corrupt an adjacent byte. &lt;code&gt;page_program&lt;/code&gt; now enforces a
write-size contract: the start address and the length must both be
8-byte aligned, and the write must stay within a single page. Anything
else returns an error before a single byte reaches the flash.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; The 8 is a property of the FlexSPI IP/DMA FIFO window,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; not of the flash chip. Name it so nobody &amp;quot;optimizes&amp;quot; it away.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; WRITE_GRANULARITY&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; usize&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 8&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; check_program&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;addr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; u32&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; len&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; usize&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; flash_size&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; u32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Result&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; IoError&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    if&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; addr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; as&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; usize&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; %&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; WRITE_GRANULARITY&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; len&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; %&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; WRITE_GRANULARITY&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        return&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Err&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;IoError&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Misaligned&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    match&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; addr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;checked_add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; as&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; u32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;        Some&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;end&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; end&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;lt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; flash_size&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;        _&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; return&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Err&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;IoError&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;OutOfBounds&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; ...plus the single-page check...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    Ok&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two things made this an easy call rather than a regression. First, it’s
the exact granularity the DMA write path already required (&lt;code&gt;len % 8 == 0&lt;/code&gt;), so the async/DMA side of the driver had always lived under this
contract — I was only making the blocking side honest about it. Second,
it maps cleanly onto &lt;code&gt;embedded-storage&lt;/code&gt;’s &lt;code&gt;WRITE_SIZE&lt;/code&gt; model, so a
future trait impl falls out naturally rather than fighting the
contract. The same pass also bounds-checks &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;erase_sector&lt;/code&gt;, and
&lt;code&gt;program&lt;/code&gt; against the configured flash size, returning
&lt;code&gt;IoError::OutOfBounds&lt;/code&gt; instead of wrapping around. Both new variants
went onto an already &lt;code&gt;#[non_exhaustive]&lt;/code&gt; &lt;code&gt;IoError&lt;/code&gt;, so none of this
breaks callers — the only behavioural change is that a sub-page write
now has to align to 8 bytes, where before it would silently eat the
byte next door.&lt;/p&gt;
&lt;p&gt;Partial-page writes still work. They just work at 8-byte granularity,
the way the hardware can actually deliver them. I updated the bundled
examples to match (a &lt;code&gt;100&lt;/code&gt;-byte partial write became &lt;code&gt;96&lt;/code&gt;; the stress
example’s program sizes got rounded to multiples of 8), which was a
nice forcing function for confirming the contract was livable and not
just defensible.&lt;/p&gt;
&lt;h2 id=&quot;pushing-the-same-idea-at-the-async-path&quot;&gt;Pushing the same idea at the async path&lt;/h2&gt;
&lt;p&gt;The blocking driver was now hardened and soaked. But the interesting
half of an Embassy driver is the async half, and async brings a failure
mode that blocking code simply doesn’t have: &lt;strong&gt;cancellation&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In async Rust, the idiomatic way to bound an operation is to wrap it in
&lt;code&gt;with_timeout&lt;/code&gt;, or race it in a &lt;code&gt;select!&lt;/code&gt;, or cancel the task it runs
in. All three do the same thing under the hood: they &lt;em&gt;drop the future
mid-flight&lt;/em&gt;. The operation was in progress, and now it isn’t, and it
never got to run whatever cleanup it might have wanted to run. Any
async driver that can’t survive that is a driver with a landmine in it,
waiting for the first caller who puts a timeout around a flash write.&lt;/p&gt;
&lt;p&gt;So the second rig, &lt;code&gt;flexspi-cancel-soak&lt;/code&gt;, does that on purpose,
thousands of times. It runs the same erase/program/verify integrity
checks over the async + DMA path, and then it does something deliberately
hostile: it takes a real operation, wraps it in &lt;code&gt;with_timeout&lt;/code&gt; with a
delay swept across a wide range — from about a microsecond (mid
command-shift, the operation has barely started) out to a couple of
milliseconds (mid write-in-progress, the flash is busy) — so the future
gets dropped at every interesting phase of the transaction. Then it
issues a &lt;em&gt;fresh&lt;/em&gt; operation and asserts that one returns correct data.&lt;/p&gt;
&lt;p&gt;The elegant part of testing cancel-safety is that you barely need an
assertion. A driver that isn’t cancel-safe doesn’t return wrong data
after a botched cancellation — it &lt;em&gt;hangs the next operation forever&lt;/em&gt;. So
the test simply &lt;em&gt;reaching the next round&lt;/em&gt;, across thousands of
in-flight cancellations, is itself the pass signal. If the rig is still
counting rounds, the driver is recovering. If it stops, it wedged.&lt;/p&gt;
&lt;p&gt;It stopped.&lt;/p&gt;
&lt;h2 id=&quot;what-it-found-second-a-dropped-future-wedges-the-controller&quot;&gt;What it found second: a dropped future wedges the controller&lt;/h2&gt;
&lt;p&gt;The symptom was a hang, deep in &lt;code&gt;prepare_ip_transfer&lt;/code&gt;, spinning in
&lt;code&gt;wait_idle()&lt;/code&gt; waiting for a sequence engine that was never going to
become idle. The diagnosis, once I dumped the controller state at the
hang, was clear: the cancelled operation had left the FlexSPI sequence
engine non-idle — chip-select still asserted, the TX FIFO in underrun —
and the driver had no notion that it should clean any of that up. The
&lt;em&gt;next&lt;/em&gt; operation walked in, asked the engine to be idle, and waited for
a condition that the &lt;em&gt;previous, cancelled&lt;/em&gt; operation had made
permanently false. One cancelled future, and the controller was bricked
until reset.&lt;/p&gt;
&lt;p&gt;The fix is to stop trusting that the engine is idle just because the
last operation “finished,” and instead make &lt;code&gt;prepare_ip_transfer&lt;/code&gt;
self-healing. On entry, if the engine isn’t idle — the fingerprint of a
previously cancelled op — force it idle with a software reset before
doing anything else:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; prepare_ip_transfer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;mut&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; self&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Result&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; IoError&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; A cancelled op leaves the sequence engine non-idle: CS still&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; asserted, TX FIFO underrun. Detect that fingerprint and recover.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;    //&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; One SEQIDLE sample is authoritative here: SEQIDLE only deasserts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; on a fresh IPCMD trigger, and none is pending at this point.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    if&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; !&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;seq_idle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;        //&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; Force idle, de-assert CS, reset the instruction pointer,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;        //&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; flush the FIFOs. The LUT and controller config survive a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;        //&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; software reset -- the init path already relies on that.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;        self&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;software_reset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;    self&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;wait_idle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; ...load the IP command, trigger IPCMD...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    Ok&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There’s a deliberate design decision hiding in what this fix &lt;em&gt;doesn’t&lt;/em&gt;
do. It does not add a timeout. The driver imposes no timeout of its own
and keeps &lt;code&gt;embassy-time&lt;/code&gt; out of its hot path entirely. Timeout &lt;em&gt;policy&lt;/em&gt;
— how long is too long, and whether to give up — belongs to the
application, which already has &lt;code&gt;with_timeout&lt;/code&gt; for exactly that. The
driver’s job isn’t to decide the policy; it’s to make the policy
&lt;em&gt;safe&lt;/em&gt;. The self-healing recovery is precisely what lets a caller wrap
any operation in &lt;code&gt;with_timeout&lt;/code&gt; without fear: when their timeout
elapses and drops the future, the driver un-wedges itself on the next
call instead of staying broken. The cancellation that used to be a
landmine is now just a thing that happens.&lt;/p&gt;
&lt;h2 id=&quot;the-payoff&quot;&gt;The payoff&lt;/h2&gt;
&lt;p&gt;With both fixes in, the two rigs ran clean:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;flexspi-soak&lt;/code&gt;&lt;/strong&gt; (blocking IP + memory-mapped): 384 rounds, zero
mismatches between the IP and AHB paths, and every out-of-contract
call — misaligned, page-crossing, out-of-range — returning the error
it should.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;flexspi-cancel-soak&lt;/code&gt;&lt;/strong&gt; (async + DMA): 388 rounds, &lt;strong&gt;5,044
in-flight cancellations&lt;/strong&gt;, 3,880 clean recoveries, zero wedges, zero
data corruption.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those 5,044 cancellations are the number I care about most, because
every one of them is a &lt;code&gt;with_timeout&lt;/code&gt; firing at a moment that, a week
earlier, would have bricked the controller until the next reset.&lt;/p&gt;
&lt;h2 id=&quot;what-i-d-carry-to-the-next-driver&quot;&gt;What I’d carry to the next driver&lt;/h2&gt;
&lt;p&gt;A few things generalize past this one chip:&lt;/p&gt;
&lt;p&gt;A happy-path self-test proves a driver &lt;em&gt;can&lt;/em&gt; work. It says nothing
about whether you can trust it. Those are different claims, and only the
second one matters once real callers show up.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cancel-safety is a first-class property of an async driver&lt;/strong&gt;, not an
edge case. If dropping an in-flight future can leave your peripheral in
a state the next operation can’t recover from, you don’t have a working
async driver yet — you have one that works until someone uses a timeout.
And in Embassy, &lt;em&gt;someone always uses a timeout.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Make illegal states loud.&lt;/strong&gt; The unaligned write didn’t fail — it
succeeded and corrupted a neighbour. A contract that turns “silently
wrong” into “loudly refused” is worth more than it costs, every time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Seed your randomness.&lt;/strong&gt; A fuzzing soak loop that can’t replay its own
failures is a slot machine. The xorshift seed turned “it broke
somewhere in three hours” into “it broke on this seed, here’s the
repro.”&lt;/p&gt;
&lt;h2 id=&quot;a-note-on-trusting-it&quot;&gt;A note on trusting it&lt;/h2&gt;
&lt;p&gt;I trust this driver today — on this board, with this flash part,
against everything the two rigs throw at it. That’s a real and specific
claim, and it’s also a smaller one than “it’s correct.” Both bugs in
this post were invisible until the moment a rig was nasty enough to
provoke them, and the honest conclusion to draw from that is &lt;em&gt;not&lt;/em&gt; that
I’ve now found the last one. It’s that the next rig, or the next caller
with a workload I didn’t imagine, may well surface something these two
didn’t. Soak tests buy confidence proportional to how mean they are, and
no finite amount of meanness reaches a proof. If you run this driver and
it does something neither rig caught, I’d genuinely like to know — that’s
the rig I haven’t built yet.&lt;/p&gt;
&lt;h2 id=&quot;what-this-doesn-t-cover&quot;&gt;What this doesn’t cover&lt;/h2&gt;
&lt;p&gt;The usual caveats, and they’re real ones. This is one board, one flash
part, one controller IP. The unaligned-write corruption is a quirk of
&lt;em&gt;this&lt;/em&gt; FlexSPI block, and I’d be unsurprised if it looked nothing like
that on other silicon — the &lt;em&gt;shape&lt;/em&gt; of the lesson (diff against the
vendor reference; if your loop is identical, suspect the controller)
transfers better than the specifics. The driver also doesn’t implement
the &lt;code&gt;embedded-storage&lt;/code&gt; traits yet, though the write-size contract was
designed so it maps straight onto &lt;code&gt;WRITE_SIZE = 8&lt;/code&gt; / &lt;code&gt;ERASE_SIZE = 4096&lt;/code&gt; when it does.&lt;/p&gt;
&lt;p&gt;The full set of fixes, the two soak rigs, and the hardware run logs are
in &lt;a rel=&quot;external&quot; href=&quot;https://github.com/embassy-rs/embassy/pull/6389&quot;&gt;embassy-rs/embassy#6389&lt;/a&gt;. If you’re bringing up FlexSPI on
MCXA, I hope the rigs save you the half-week. If you’re doing it on
something else and one of these patterns rhymes with what you’re seeing,
I’d like to hear about it.&lt;/p&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Pico de Gallo: Release Announcement</title>
    <published>2026-06-22T09:00:00+00:00</published>
    <updated>2026-06-22T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/pico-de-gallo-reliability-release/" type="text/html"/>
    <id>https://balbi.sh/posts/pico-de-gallo-reliability-release/</id>
    <summary type="html">&lt;p&gt;&lt;a href=&quot;/pico-de-gallo/&quot;&gt;Pico de Gallo&lt;/a&gt; turns an RP2350 into a USB-attached bridge that
lets a host program drive real I²C, SPI, GPIO, PWM, ADC, UART, and
1-Wire from &lt;code&gt;std&lt;/code&gt; Rust, C, or Python — so you can write and test
device drivers on your laptop instead of cross-compiling for an MCU
every time. Today’s release moves the whole ecosystem forward at once.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;&lt;a href=&quot;/pico-de-gallo/&quot;&gt;Pico de Gallo&lt;/a&gt; turns an RP2350 into a USB-attached bridge that
lets a host program drive real I²C, SPI, GPIO, PWM, ADC, UART, and
1-Wire from &lt;code&gt;std&lt;/code&gt; Rust, C, or Python — so you can write and test
device drivers on your laptop instead of cross-compiling for an MCU
every time. Today’s release moves the whole ecosystem forward at once.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;
&lt;p&gt;This is a &lt;strong&gt;lockstep release&lt;/strong&gt;. The wire protocol in
&lt;code&gt;pico-de-gallo-internal&lt;/code&gt; went from schema 0.5 to 0.6, and under the
pre-1.0 schema-versioning rule that is a breaking change. &lt;strong&gt;Firmware
and host must be upgraded together&lt;/strong&gt; — a 0.10 firmware will reject an
older host’s RPCs, and a new host will refuse to talk to old firmware
rather than silently mis-decode bytes on the wire. The
&lt;a href=&quot;https://balbi.sh/posts/pico-de-gallo-reliability-release/#upgrade-and-compatibility&quot;&gt;upgrade notes&lt;/a&gt; at the bottom have the full
version table.&lt;/p&gt;
&lt;p&gt;Most of what landed here comes out of a reliability review that turned
up a handful of real bugs: a GPIO wait could wedge the entire firmware
dispatcher, a hung handler had no recovery path, GPIO subscriptions
leaked when a host process crashed, and a bumped schema &lt;em&gt;major&lt;/em&gt; could
slip past validation and corrupt decoding silently. Each crate’s share
of the fix is below.&lt;/p&gt;
&lt;h2 id=&quot;pico-de-gallo-internal-0-6-0-the-wire-protocol&quot;&gt;&lt;code&gt;pico-de-gallo-internal&lt;/code&gt; 0.6.0 — the wire protocol&lt;/h2&gt;
&lt;p&gt;This is the crate every other one depends on, and the breaking schema
bump that drives the lockstep.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GpioWaitRequest&lt;/code&gt; gained a &lt;code&gt;timeout_ms: u32&lt;/code&gt; field, used by all five
&lt;code&gt;gpio/wait-*&lt;/code&gt; endpoints (&lt;code&gt;wait-high&lt;/code&gt;, &lt;code&gt;wait-low&lt;/code&gt;, &lt;code&gt;wait-rising&lt;/code&gt;,
&lt;code&gt;wait-falling&lt;/code&gt;, &lt;code&gt;wait-any&lt;/code&gt;). A value of &lt;code&gt;0&lt;/code&gt; preserves the old
wait-forever behavior; a non-zero value bounds the firmware-side wait
and returns the new &lt;code&gt;GpioError::Timeout&lt;/code&gt; on expiry. This is the wire
half of the fix for the dispatcher wedge — a wait on a pin that never
transitions used to block &lt;em&gt;every other endpoint&lt;/em&gt; until you
power-cycled the board.&lt;/li&gt;
&lt;li&gt;New &lt;code&gt;system/reset-subscriptions&lt;/code&gt; endpoint (request &lt;code&gt;()&lt;/code&gt;, response
&lt;code&gt;u8&lt;/code&gt; count). GPIO subscriptions are server-side state that outlives
the USB transport, so a host that crashed without sending
&lt;code&gt;gpio/unsubscribe&lt;/code&gt; would strand those pins until a power cycle. This
endpoint is the recovery path.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both changes are append-only on the wire, but the schema-version bump
itself is what requires the coordinated upgrade.&lt;/p&gt;
&lt;h2 id=&quot;pico-de-gallo-lib-0-6-0-the-rust-host-library&quot;&gt;&lt;code&gt;pico-de-gallo-lib&lt;/code&gt; 0.6.0 — the Rust host library&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;New &lt;code&gt;gpio_wait_for_{high,low,rising_edge,falling_edge,any_edge}_with_timeout&lt;/code&gt;
methods take a &lt;code&gt;std::time::Duration&lt;/code&gt; and return
&lt;code&gt;Err(PicoDeGalloError::Endpoint(GpioError::Timeout))&lt;/code&gt; on expiry. The
existing two-argument methods keep waiting forever by sending
&lt;code&gt;timeout_ms: 0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;New &lt;code&gt;system_reset_subscriptions()&lt;/code&gt; method returns the number of
subscriptions it reset. The recommended connect sequence is now
&lt;code&gt;new()&lt;/code&gt; → &lt;code&gt;validate().await?&lt;/code&gt; → &lt;code&gt;system_reset_subscriptions().await?&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;validate()&lt;/code&gt; now checks &lt;code&gt;schema_major&lt;/code&gt; in addition to
&lt;code&gt;schema_minor&lt;/code&gt;. Previously a firmware reporting a bumped major with a
matching minor would pass validation and the host would then
mis-decode wire bytes — silent garbage out. &lt;code&gt;ValidateError::SchemaMismatch&lt;/code&gt;
now carries &lt;code&gt;expected_major&lt;/code&gt; / &lt;code&gt;actual_major&lt;/code&gt;, and its &lt;code&gt;Display&lt;/code&gt;
shows the full &lt;code&gt;MAJOR.MINOR.x&lt;/code&gt; skew.&lt;/li&gt;
&lt;li&gt;Fixed: &lt;code&gt;validate()&lt;/code&gt; no longer mis-classifies transport,
postcard-decode, and frame-size errors as
&lt;code&gt;ValidateError::LegacyFirmware&lt;/code&gt;. Only the postcard-rpc “no handler
for that key” signals (&lt;code&gt;UnknownKey&lt;/code&gt;, &lt;code&gt;KeyTooSmall&lt;/code&gt;) map to
&lt;code&gt;LegacyFirmware&lt;/code&gt;; everything else routes to &lt;code&gt;Comms&lt;/code&gt;, so you stop
being told to upgrade firmware that is already current.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MAX_BATCH_OPS&lt;/code&gt; and &lt;code&gt;MAX_TRANSFER_SIZE&lt;/code&gt; are now re-exported, so you
don’t have to depend on the wire crate just to validate batch sizes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;pico-de-gallo-hal-0-6-0-the-embedded-hal-layer&quot;&gt;&lt;code&gt;pico-de-gallo-hal&lt;/code&gt; 0.6.0 — the &lt;code&gt;embedded-hal&lt;/code&gt; layer&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;New &lt;code&gt;Hal::new_validated()&lt;/code&gt; and &lt;code&gt;Hal::new_validated_with_serial_number()&lt;/code&gt;
constructors call &lt;code&gt;validate()&lt;/code&gt; before returning, failing loudly on a
disconnected device or a schema mismatch. The lazy &lt;code&gt;Hal::new()&lt;/code&gt; still
defers failures to the first RPC if that’s what you want. A standalone
&lt;code&gt;Hal::validate()&lt;/code&gt; accessor lets you check after the fact.&lt;/li&gt;
&lt;li&gt;New &lt;code&gt;Hal::system_reset_subscriptions() -&amp;gt; Result&amp;lt;u8, SystemHalError&amp;gt;&lt;/code&gt;
exposes the subscription teardown that previously required dropping
down to &lt;code&gt;pico-de-gallo-lib&lt;/code&gt;. Recommended right after
&lt;code&gt;new_validated()&lt;/code&gt; in any app that uses GPIO subscriptions.&lt;/li&gt;
&lt;li&gt;New &lt;code&gt;Gpio::wait_for_*_with_timeout&lt;/code&gt; inherent async methods accept a
&lt;code&gt;Duration&lt;/code&gt; and return &lt;code&gt;GpioError::Timeout&lt;/code&gt; on expiry. They’re
inherent methods rather than trait methods because
&lt;code&gt;embedded-hal-async&lt;/code&gt;’s &lt;code&gt;Wait&lt;/code&gt; trait has no notion of a timeout; the
trait methods keep their wait-forever semantics.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AdcChannel&lt;/code&gt;, &lt;code&gt;AdcConfigurationInfo&lt;/code&gt;, &lt;code&gt;GpioDirection&lt;/code&gt;, &lt;code&gt;GpioEdge&lt;/code&gt;,
and &lt;code&gt;GpioPull&lt;/code&gt; are now re-exported — driver authors no longer need
&lt;code&gt;pico-de-gallo-lib&lt;/code&gt; in their &lt;code&gt;Cargo.toml&lt;/code&gt; just for these types.&lt;/li&gt;
&lt;li&gt;New &lt;code&gt;HalInitError&lt;/code&gt; and &lt;code&gt;SystemHalError&lt;/code&gt; types, and a fix for a stale
doc-comment that referenced a &lt;code&gt;Hal::uart_set_config&lt;/code&gt; method that
never existed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;pico-de-gallo-ffi-0-7-0-the-c-bindings&quot;&gt;&lt;code&gt;pico-de-gallo-ffi&lt;/code&gt; 0.7.0 — the C bindings&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;New &lt;code&gt;gallo_init_strict()&lt;/code&gt; and &lt;code&gt;gallo_init_strict_with_serial_number()&lt;/code&gt;
call &lt;code&gt;validate()&lt;/code&gt; internally and return &lt;code&gt;NULL&lt;/code&gt; on device-not-found,
schema mismatch, or legacy firmware. Prefer these over the lazy
&lt;code&gt;gallo_init&lt;/code&gt; in production C — failures surface at construct time
instead of on the first RPC.&lt;/li&gt;
&lt;li&gt;New &lt;code&gt;gallo_gpio_wait_for_{high,low,rising_edge,falling_edge,any_edge}_with_timeout_ms&lt;/code&gt;
functions. &lt;code&gt;timeout_ms == 0&lt;/code&gt; keeps the wait-forever behavior;
non-zero bounds it and returns &lt;code&gt;Status::GpioTimeout&lt;/code&gt; (&lt;code&gt;-70&lt;/code&gt;). These
need firmware schema 0.6+; older firmware returns
&lt;code&gt;Status::SchemaMismatch&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;New &lt;code&gt;gallo_system_reset_subscriptions()&lt;/code&gt; with
&lt;code&gt;SystemResetSubscriptionsFailed&lt;/code&gt; (&lt;code&gt;-69&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The high-throughput primitives &lt;code&gt;gallo_spi_transfer&lt;/code&gt;, &lt;code&gt;gallo_spi_batch&lt;/code&gt;,
and &lt;code&gt;gallo_i2c_batch&lt;/code&gt; are now reachable from C, via the tagged structs
&lt;code&gt;GalloSpiBatchOp&lt;/code&gt; / &lt;code&gt;GalloI2cBatchOp&lt;/code&gt;. On a per-operation failure an
optional &lt;code&gt;out_failed_op&lt;/code&gt; receives the zero-based index of the failing
op. New status codes: &lt;code&gt;I2cBatchFailed&lt;/code&gt; (&lt;code&gt;-66&lt;/code&gt;), &lt;code&gt;SpiBatchFailed&lt;/code&gt;
(&lt;code&gt;-67&lt;/code&gt;), &lt;code&gt;SpiTransferFailed&lt;/code&gt; (&lt;code&gt;-68&lt;/code&gt;). The wire protocol is unchanged
here — this is pure FFI surface over existing endpoints.&lt;/li&gt;
&lt;li&gt;All &lt;code&gt;gallo_*&lt;/code&gt; functions now take &lt;code&gt;const PicoDeGallo *&lt;/code&gt; for the device
handle. The ABI is unchanged, but C consumers that previously cast
away &lt;code&gt;const&lt;/code&gt; on every call can drop those casts, and headers built
with &lt;code&gt;-Wcast-qual&lt;/code&gt; stop warning. The handle remains &lt;code&gt;Send + Sync&lt;/code&gt; and
interior-mutable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;pyco-de-gallo-0-4-2-the-python-bindings&quot;&gt;&lt;code&gt;pyco-de-gallo&lt;/code&gt; 0.4.2 — the Python bindings&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;New &lt;code&gt;pyco_de_gallo.open_strict()&lt;/code&gt; and
&lt;code&gt;open_strict_with_serial_number(serial_number)&lt;/code&gt; call &lt;code&gt;validate()&lt;/code&gt;
before returning the handle and raise &lt;code&gt;RuntimeError&lt;/code&gt; on
device-not-found, schema mismatch, or legacy firmware. Prefer these
over the lazy &lt;code&gt;open()&lt;/code&gt; in production Python.&lt;/li&gt;
&lt;li&gt;New &lt;code&gt;gpio_wait_for_*_with_timeout(timeout_ms: int)&lt;/code&gt; methods — &lt;code&gt;0&lt;/code&gt;
waits forever, non-zero raises &lt;code&gt;RuntimeError&lt;/code&gt; on &lt;code&gt;GpioError::Timeout&lt;/code&gt;.
Requires firmware schema 0.6+.&lt;/li&gt;
&lt;li&gt;New &lt;code&gt;system_reset_subscriptions()&lt;/code&gt; returns an &lt;code&gt;int&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;gallo-cli-0-7-0-the-command-line-tool&quot;&gt;&lt;code&gt;gallo&lt;/code&gt; (CLI) 0.7.0 — the command-line tool&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gallo&lt;/code&gt; now calls &lt;code&gt;validate()&lt;/code&gt; at the top of every subcommand except
&lt;code&gt;list&lt;/code&gt; and &lt;code&gt;version&lt;/code&gt;. A schema-version mismatch is reported up front
with an actionable message that points at &lt;code&gt;gallo version&lt;/code&gt; and tells
you to re-flash the firmware or install a matching &lt;code&gt;gallo&lt;/code&gt;, instead
of surfacing as a confusing &lt;code&gt;CommsFailed&lt;/code&gt; on the first RPC. &lt;code&gt;list&lt;/code&gt; is
exempt because it doesn’t touch a connected device; &lt;code&gt;version&lt;/code&gt; is
exempt because it &lt;em&gt;is&lt;/em&gt; the diagnostic that reports schema skew.&lt;/li&gt;
&lt;li&gt;Everything else is unchanged. The existing &lt;code&gt;gpio&lt;/code&gt; subcommands
(&lt;code&gt;get&lt;/code&gt;, &lt;code&gt;put&lt;/code&gt;, &lt;code&gt;set-config&lt;/code&gt;, &lt;code&gt;monitor&lt;/code&gt;) keep working. The CLI doesn’t
expose &lt;code&gt;gpio wait-for-*&lt;/code&gt; subcommands, so bounded waits stay available
through the Rust, C, and Python libraries.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;pico-de-gallo-firmware-0-10-0-the-device&quot;&gt;&lt;code&gt;pico-de-gallo-firmware&lt;/code&gt; 0.10.0 — the device&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gpio_wait_for_*&lt;/code&gt; handlers now honor the per-request &lt;code&gt;timeout_ms&lt;/code&gt;.
Non-zero values wrap embassy’s &lt;code&gt;wait_for_*_edge()&lt;/code&gt; future in
&lt;code&gt;embassy_time::with_timeout(...)&lt;/code&gt; and return &lt;code&gt;GpioError::Timeout&lt;/code&gt; on
expiry; &lt;code&gt;0&lt;/code&gt; keeps the pre-0.6 wait-forever behavior.&lt;/li&gt;
&lt;li&gt;An embassy-rp watchdog is now enabled at a 2-second timeout, fed
every 800 ms by a dedicated &lt;code&gt;watchdog_feeder_task&lt;/code&gt;. It’s a separate
task on purpose — a wedged handler can’t be trusted to feed a
handler-based scheme — so the device recovers from any future handler
hang. &lt;code&gt;pause_on_debug(true)&lt;/code&gt; keeps debugger sessions from resetting
the chip.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;i2c_scan_handler&lt;/code&gt; now wraps each per-address probe in a 50 ms
timeout, so one slow-to-NAK address no longer burns the whole scan
budget.&lt;/li&gt;
&lt;li&gt;New &lt;code&gt;system/reset-subscriptions&lt;/code&gt; handler iterates the GPIO monitor
slots, signals each live one to stop, awaits the pin back from its
monitor task, and returns it to the context. It’s idempotent and
cheap when nothing is subscribed — the device-side half of the
subscription-leak recovery.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Together these close the dispatcher-wedge regression (a &lt;code&gt;gpio_wait&lt;/code&gt; on
a never-transitioning pin blocking every other endpoint), the
no-recovery-from-a-hung-handler gap, and the worst-case impact of a
flaky I²C bus on &lt;code&gt;i2c_scan&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;upgrade-and-compatibility&quot;&gt;Upgrade and compatibility&lt;/h2&gt;
&lt;p&gt;Because the wire schema changed, &lt;strong&gt;flash the new firmware and update
your host crate in the same step&lt;/strong&gt;. Mixed versions won’t talk to each
other — by design, the new validation refuses rather than mis-decodes.&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Crate&lt;/th&gt;&lt;th&gt;Old&lt;/th&gt;&lt;th&gt;New&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pico-de-gallo-internal&lt;/code&gt;&lt;/td&gt;&lt;td&gt;0.5.0&lt;/td&gt;&lt;td&gt;0.6.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pico-de-gallo-lib&lt;/code&gt;&lt;/td&gt;&lt;td&gt;0.5.0&lt;/td&gt;&lt;td&gt;0.6.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pico-de-gallo-hal&lt;/code&gt;&lt;/td&gt;&lt;td&gt;0.5.0&lt;/td&gt;&lt;td&gt;0.6.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pico-de-gallo-ffi&lt;/code&gt;&lt;/td&gt;&lt;td&gt;0.6.0&lt;/td&gt;&lt;td&gt;0.7.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;gallo&lt;/code&gt; (CLI)&lt;/td&gt;&lt;td&gt;0.6.0&lt;/td&gt;&lt;td&gt;0.7.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pyco-de-gallo&lt;/code&gt;&lt;/td&gt;&lt;td&gt;0.2.0&lt;/td&gt;&lt;td&gt;0.4.2&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pico-de-gallo-firmware&lt;/code&gt;&lt;/td&gt;&lt;td&gt;0.9.0&lt;/td&gt;&lt;td&gt;0.10.0&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;After flashing, point the host at the device and confirm the schema
lines up:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; gallo version&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For new code, reach for the validating entry points so a version skew
or a missing board fails at construct time rather than on the first
call:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rust library: &lt;code&gt;PicoDeGallo::new()&lt;/code&gt; → &lt;code&gt;validate().await?&lt;/code&gt; →
&lt;code&gt;system_reset_subscriptions().await?&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;HAL: &lt;code&gt;Hal::new_validated()&lt;/code&gt;, then &lt;code&gt;system_reset_subscriptions()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;C: &lt;code&gt;gallo_init_strict()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Python: &lt;code&gt;pyco_de_gallo.open_strict()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you hit a schema-mismatch error after upgrading only one side, that
mismatch is the new validation doing its job. Re-flash or re-install so
both ends report the same schema, and you’re good.&lt;/p&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>I3C target mode on MCXA: three things the datasheet didn&#39;t warn me about</title>
    <published>2026-06-20T09:00:00+00:00</published>
    <updated>2026-06-20T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/i3c-target-mcxa-bring-up-gotchas/" type="text/html"/>
    <id>https://balbi.sh/posts/i3c-target-mcxa-bring-up-gotchas/</id>
    <summary type="html">&lt;p&gt;I spent the last several weeks bringing up an I3C &lt;em&gt;target&lt;/em&gt; driver for
the NXP MCXA family (MCXA2xx and MCXA5xx share the same I3C IP) inside
the embassy HAL. “Target” is what I3C calls the role I²C calls
“&lt;em&gt;slave&lt;/em&gt;”: the device that sits on the bus, responds to a controller,
and can also raise an in-band interrupt (IBI) and service a directed
read without anyone toggling a sideband pin.  The use case was the
boring one: a Rust target talking to a Rust controller, raising IBIs
and returning responses for a few million iterations without
panicking.&lt;/p&gt;
&lt;p&gt;The driver landed in &lt;a rel=&quot;external&quot; href=&quot;https://github.com/embassy-rs/embassy/pull/6160&quot;&gt;embassy-rs/embassy#6160&lt;/a&gt; and the soak rig
has now ticked past 50 million IBI-then-directed-read iterations at
16-byte payloads, bus at 1.5 MHz SDR, against an NXP SDK controller
on the other end. It got there by way of three corner cases that
each took longer to diagnose than to fix, and that each looked
nothing like what I’d assumed they were when I started chasing them.
This post is those three.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;I spent the last several weeks bringing up an I3C &lt;em&gt;target&lt;/em&gt; driver for
the NXP MCXA family (MCXA2xx and MCXA5xx share the same I3C IP) inside
the embassy HAL. “Target” is what I3C calls the role I²C calls
“&lt;em&gt;slave&lt;/em&gt;”: the device that sits on the bus, responds to a controller,
and can also raise an in-band interrupt (IBI) and service a directed
read without anyone toggling a sideband pin.  The use case was the
boring one: a Rust target talking to a Rust controller, raising IBIs
and returning responses for a few million iterations without
panicking.&lt;/p&gt;
&lt;p&gt;The driver landed in &lt;a rel=&quot;external&quot; href=&quot;https://github.com/embassy-rs/embassy/pull/6160&quot;&gt;embassy-rs/embassy#6160&lt;/a&gt; and the soak rig
has now ticked past 50 million IBI-then-directed-read iterations at
16-byte payloads, bus at 1.5 MHz SDR, against an NXP SDK controller
on the other end. It got there by way of three corner cases that
each took longer to diagnose than to fix, and that each looked
nothing like what I’d assumed they were when I started chasing them.
This post is those three.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;
&lt;p&gt;One caveat up front: this is one vendor, one HAL crate, one
bring-up. I have no idea whether these gotchas generalize to other
I3C silicon. The shape of &lt;em&gt;how the bugs hid&lt;/em&gt; probably transfers;
the specific register names and timing numbers don’t.&lt;/p&gt;
&lt;h2 id=&quot;the-setup&quot;&gt;The setup&lt;/h2&gt;
&lt;p&gt;The hardware is two MCXA boards wired together over their I3C0
peripherals: one running the Rust controller, one running the Rust
target, both on top of the &lt;code&gt;embassy-mcxa&lt;/code&gt; HAL. The bus runs SDR at
1.5 MHz push-pull / 750 kHz open-drain. The exchange under test is the
obvious one: controller writes a few bytes, target raises an IBI,
controller responds with a directed read, target returns a pre-loaded
payload, repeat. The target’s RX path is DMA-fed into a bbqueue ring
so the IRQ can drain &lt;code&gt;SRDATAB&lt;/code&gt; at line rate while the consuming task
takes its time — that ring is what makes the soak loop possible at
all, but it isn’t where any of these three gotchas live.&lt;/p&gt;
&lt;p&gt;For the first few days everything worked. Then the rig started
panicking after twenty thousand to a few hundred thousand
iterations with &lt;code&gt;InvalidStart&lt;/code&gt; or &lt;code&gt;SdrParity&lt;/code&gt; on the target side,
and I’d go back to staring at register dumps.&lt;/p&gt;
&lt;h2 id=&quot;gotcha-1-the-target-clock-was-4x-too-slow-and-the-symptom-was-on-the-controller&quot;&gt;Gotcha 1: the target clock was 4× too slow, and the symptom was on the controller&lt;/h2&gt;
&lt;p&gt;The first failure mode looked like SDA contention around the IBI
handshake — once every few hundred thousand iterations, the
controller would see a frame with bad parity, or the target would
surface &lt;code&gt;SERRWARN.INVSTART&lt;/code&gt; on what should have been a clean
repeated-Start. Not every IBI; not even most IBIs. Just enough to
be unmissable in soak and unreproducible at the bench.&lt;/p&gt;
&lt;p&gt;I lost a day re-reading the IBI sequencing code for a race that
wasn’t there before I gave up and added a &lt;code&gt;dump_registers()&lt;/code&gt;
method on both sides — one defmt line each, every &lt;code&gt;M*&lt;/code&gt; / &lt;code&gt;S*&lt;/code&gt;
register as a raw &lt;code&gt;u32&lt;/code&gt;. Diffed against the NXP SDK’s target on
the same wires, every register matched byte-for-byte except
&lt;code&gt;SCONFIG&lt;/code&gt;. I had &lt;code&gt;BAMATCH=1&lt;/code&gt; on the mcxa5xx example and
&lt;code&gt;BAMATCH=2&lt;/code&gt; on mcxa2xx; the SDK had &lt;code&gt;BAMATCH=11&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;BAMATCH is the count, in I3C functional-clock cycles, the target
waits after seeing bus-idle before declaring the bus “available.”
The HAL computes it as &lt;code&gt;fclk_MHz - 1&lt;/code&gt; — correct, but only if
&lt;code&gt;fclk&lt;/code&gt; is in the range the I3C spec assumes. The MCXA examples
were feeding I3C0 at roughly 3 MHz (mcxa5xx) and 2.8 MHz
(mcxa2xx), giving bus-available times of 0.36 µs and 0.67 µs; the
spec puts the minimum at 1 µs and the SDK runs at 12 MHz
(0.92 µs). With BAMATCH expiring sub-microsecond, the target was
declaring the bus available before the controller had finished
releasing SDA, and very occasionally both ends would drive the
line at the same time. That was the parity error and the
&lt;code&gt;INVSTART&lt;/code&gt;: SDA contention manifesting one full transaction
downstream of the actual race.&lt;/p&gt;
&lt;p&gt;The fix is one line in the example clock-tree setup: route
I3C0_FCLK from FRO_LF (12 MHz exact) with a /1 divider, and the
HAL’s math produces &lt;code&gt;BAMATCH=11&lt;/code&gt;. &lt;code&gt;SCONFIG&lt;/code&gt; becomes &lt;code&gt;0x140b0019&lt;/code&gt;
exactly, matching the SDK. The soak loop went from failing inside
half a million iterations to running indefinitely clean.&lt;/p&gt;
&lt;p&gt;The lesson: when a vendor’s reference and yours run on the same
wires and only one is reliable, dump every register on both sides
post-init and diff them — the difference is almost never where
you’re looking. And if a peripheral’s clock contributes to a
&lt;em&gt;bus-timing&lt;/em&gt; parameter rather than just a baud rate, check that
field’s spec minimum before you trust the formula the HAL uses to
compute it.&lt;/p&gt;
&lt;h2 id=&quot;gotcha-2-raising-the-ibi-before-the-fifo-was-loaded&quot;&gt;Gotcha 2: raising the IBI before the FIFO was loaded&lt;/h2&gt;
&lt;p&gt;With the clocks corrected, the soak rig hit a different failure:
&lt;code&gt;SERRWARN.INVSTART&lt;/code&gt; on the target, during the IBI-payload phase
itself this time. The trace was unambiguous — the controller ACK’d
the IBI, issued a repeated-Start with the target’s dynamic address
and the read bit, started clocking SCL — and the target’s TX FIFO
was empty when the first byte was demanded. The cause was an
ordering bug in &lt;code&gt;dma_respond_to_read_with_ibi&lt;/code&gt;: arm TX DMA, set
&lt;code&gt;SCTRL.EVENT = Ibi&lt;/code&gt;, then wait for DMA to drain into the FIFO.
The IBI arbitration plus the controller’s auto-IBI ACK plus the
repeated-Start plus the read bit takes about 640 ns at 1.5 MHz —
on a quiet executor DMA setup wins that race; under realistic
Embassy load it doesn’t.&lt;/p&gt;
&lt;p&gt;The intuitive fix is to invert the order — drain DMA synchronously,
push the end-marker via &lt;code&gt;SWDATABE&lt;/code&gt;, &lt;em&gt;then&lt;/em&gt; raise &lt;code&gt;EVENT = Ibi&lt;/code&gt;.
This works perfectly up to the TX FIFO depth, which on this IP is
8 bytes. The moment you try a 9-byte payload it deadlocks: DMA
blocks waiting for FIFO space; the FIFO won’t drain because the
controller hasn’t been told there’s anything to read; the
controller hasn’t been told because the IBI hasn’t gone out. The
shape the fix has to take is the one the NXP SDK uses, and isn’t
the one I’d have guessed: arm DMA, raise the IBI &lt;em&gt;immediately&lt;/em&gt;,
then await DMA completion while the controller drains the FIFO at
the same rate DMA fills it.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; Arm DMA first so the first transfer is in flight.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;dma_arm_tx&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;buf&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; end_marker&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; Raise IBI immediately. Controller starts clocking; FIFO drains&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; concurrently with DMA filling it.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span&gt;regs&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;sctrl&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;modify&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; w&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;set_event&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Ibi&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; Now wait for DMA. The FIFO is active throughout.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;dma_wait_complete&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The lesson I keep relearning: when “obviously correct order” and
“actually correct order” disagree, the hardware is usually right.
If a peripheral’s FIFO is meant to be filled concurrently with
being drained, you can’t fill it sequentially before signalling
the drain, however much safer that feels.&lt;/p&gt;
&lt;h2 id=&quot;gotcha-3-the-post-ibi-stop-turned-my-repeated-start-into-a-fresh-frame&quot;&gt;Gotcha 3: the post-IBI Stop turned my repeated-Start into a fresh frame&lt;/h2&gt;
&lt;p&gt;The third one took longer to believe than it did to find. With
clocks correct and the IBI ordering fixed, Rust↔Rust soak still
failed — much later — and the failure mode was that the target’s
directed-read response was misaligned by a byte, or surfaced as
&lt;code&gt;InvalidStart&lt;/code&gt; immediately after a successful IBI.&lt;/p&gt;
&lt;p&gt;The IBI itself was clean. The handshake worked. The TX FIFO was
loaded correctly. The problem was that the controller side, in
&lt;code&gt;async_wait_for_ibi&lt;/code&gt;, was emitting an &lt;code&gt;async_stop&lt;/code&gt; once the IBI
completed. That turned the next &lt;code&gt;async_read&lt;/code&gt; the caller issued
into a fresh Start at the dynamic address, rather than the
repeated-Start the target’s pre-loaded response was sitting there
expecting. The target was set up for Sr→addr→R; the controller
handed it Stop→Start→addr→R; the framing went sideways.&lt;/p&gt;
&lt;p&gt;The fix is to delete the &lt;code&gt;async_stop&lt;/code&gt;. After a successful IBI the
bus is already in &lt;code&gt;NORMACT&lt;/code&gt; and the controller can issue a
repeated-Start directly into the directed-read. The function’s doc
comment now spells out at some length why putting the Stop back
would be wrong, because the temptation to “clean up after yourself”
by trailing every transaction with a Stop is real.&lt;/p&gt;
&lt;p&gt;The lesson is shorter than the bug: I3C, like I²C, distinguishes
Start, repeated-Start, and Stop, and the &lt;em&gt;target’s&lt;/em&gt; response state
machine cares which one comes next. Pre-load a response for a
repeated-Start, hand the target a fresh Start instead, and you
don’t get a parity error — you get a perfectly framed read that
returns the wrong bytes. That’s the worst kind of bug to have in
soak: the symptom looks like data corruption, not a protocol
violation.&lt;/p&gt;
&lt;h2 id=&quot;what-i-d-do-first-next-time&quot;&gt;What I’d do first next time&lt;/h2&gt;
&lt;p&gt;If I were starting an I3C target bring-up on this family of silicon
tomorrow, in this order:&lt;/p&gt;
&lt;p&gt;First, &lt;strong&gt;set the I3C functional clock to 12 MHz exactly&lt;/strong&gt; and
verify &lt;code&gt;SCONFIG.BAMATCH&lt;/code&gt; post-init against the spec’s 1 µs
bus-available minimum. The HAL’s formula is correct; whether the
input clock makes it produce a legal value is on you. FRO_LF /1
gives you 12 MHz exact and matches the SDK.&lt;/p&gt;
&lt;p&gt;Second, &lt;strong&gt;add a &lt;code&gt;dump_registers()&lt;/code&gt; method to both controller and
target from day one&lt;/strong&gt; and call it after init in the examples. One
defmt line each, every &lt;code&gt;M*&lt;/code&gt; / &lt;code&gt;S*&lt;/code&gt; register as a raw &lt;code&gt;u32&lt;/code&gt;. The cost
is trivial; the diagnostic value the first time something goes weird
against a known-good reference is disproportionate. Of course,
remember to remove that method prior to producing a PR. That method is
easy to write and only useful when things go awry. In general, we
don’t need it.&lt;/p&gt;
&lt;p&gt;Third, &lt;strong&gt;mirror the vendor SDK’s ordering on anything that combines
DMA, FIFO, and bus signalling&lt;/strong&gt; even if it looks wrong: arm DMA,
signal the bus, &lt;em&gt;then&lt;/em&gt; wait for completion. Don’t drain the FIFO
before raising the IBI. Don’t emit a Stop after an IBI if the next
thing you expect is a directed read.&lt;/p&gt;
&lt;p&gt;Those three would have saved me the bulk of the bring-up time.&lt;/p&gt;
&lt;h2 id=&quot;what-this-doesn-t-cover&quot;&gt;What this doesn’t cover&lt;/h2&gt;
&lt;p&gt;One developer, one family of silicon, two boards on a bench. I have no
idea how I3C target mode looks on other vendors’ IP, and I’d be
surprised if specific register names, FIFO depths, or clock-tree
dependencies transferred. The PR also contains a handful of smaller
fixes I didn’t write up — a stale &lt;code&gt;MERRWARN&lt;/code&gt; warning leaking across
frames, a racy &lt;code&gt;state == Slvreq&lt;/code&gt; guard in the IBI wake path, a
controller-side ODHPP-vs-baud-rate calculation race — each of which
fixed a real failure but wasn’t structurally interesting enough to
earn a section. Read the PR if you want the full list.&lt;/p&gt;
&lt;p&gt;If you’re bringing up I3C target mode on MCXA, I hope this saves you
the half-week. If you’re doing it on something else and one of these
patterns rhymes with what you’re seeing, I’d genuinely like to hear
about it.&lt;/p&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Examples that run</title>
    <published>2026-06-15T15:58:00+00:00</published>
    <updated>2026-06-15T15:58:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/examples-that-run/" type="text/html"/>
    <id>https://balbi.sh/posts/examples-that-run/</id>
    <summary type="html">&lt;p&gt;Most driver crates I reach for ship an &lt;code&gt;examples/&lt;/code&gt; directory that I
have to &lt;em&gt;port&lt;/em&gt; before I can read it. Wrong PAC for my dev board,
wrong linker script, a &lt;code&gt;#[entry]&lt;/code&gt; macro from a runtime I don’t have
checked out, a &lt;code&gt;loop { wfi() }&lt;/code&gt; at the end I need to delete because
I’m hosting the binary myself. By the time I’ve matched the example
to my hardware, I’ve spent an hour and I haven’t learned anything
about the chip — I’ve learned about the crate author’s bring-up
preferences for the board they happened to own.&lt;/p&gt;
&lt;p&gt;A &lt;a href=&quot;/posts/writing-embedded-drivers-without-an-mcu/&quot;&gt;previous post&lt;/a&gt;
made the case for moving the bus host-side: a Pi Pico 2 acting as a
USB-attached I²C/SPI/GPIO adapter, so the driver crate itself stays
unchanged and runs from a &lt;code&gt;std&lt;/code&gt; binary on your laptop. This post is
the other half. Once the bus moved, the &lt;em&gt;driver crate’s&lt;/em&gt; &lt;code&gt;examples/&lt;/code&gt;
directory changed shape too, and the conventions that fell out are
worth writing down. The worked example is
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/OpenDevicePartnership/tmp108&quot;&gt;&lt;code&gt;tmp108&lt;/code&gt;&lt;/a&gt; — at the
time of writing, the only crate on crates.io that lists
&lt;code&gt;pico-de-gallo-hal&lt;/code&gt; as a (dev-)dependency at all. Five example files,
five distinct chip behaviors, every one of them runs on a normal dev
machine as soon as you have the chip on a breadboard.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;Most driver crates I reach for ship an &lt;code&gt;examples/&lt;/code&gt; directory that I
have to &lt;em&gt;port&lt;/em&gt; before I can read it. Wrong PAC for my dev board,
wrong linker script, a &lt;code&gt;#[entry]&lt;/code&gt; macro from a runtime I don’t have
checked out, a &lt;code&gt;loop { wfi() }&lt;/code&gt; at the end I need to delete because
I’m hosting the binary myself. By the time I’ve matched the example
to my hardware, I’ve spent an hour and I haven’t learned anything
about the chip — I’ve learned about the crate author’s bring-up
preferences for the board they happened to own.&lt;/p&gt;
&lt;p&gt;A &lt;a href=&quot;/posts/writing-embedded-drivers-without-an-mcu/&quot;&gt;previous post&lt;/a&gt;
made the case for moving the bus host-side: a Pi Pico 2 acting as a
USB-attached I²C/SPI/GPIO adapter, so the driver crate itself stays
unchanged and runs from a &lt;code&gt;std&lt;/code&gt; binary on your laptop. This post is
the other half. Once the bus moved, the &lt;em&gt;driver crate’s&lt;/em&gt; &lt;code&gt;examples/&lt;/code&gt;
directory changed shape too, and the conventions that fell out are
worth writing down. The worked example is
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/OpenDevicePartnership/tmp108&quot;&gt;&lt;code&gt;tmp108&lt;/code&gt;&lt;/a&gt; — at the
time of writing, the only crate on crates.io that lists
&lt;code&gt;pico-de-gallo-hal&lt;/code&gt; as a (dev-)dependency at all. Five example files,
five distinct chip behaviors, every one of them runs on a normal dev
machine as soon as you have the chip on a breadboard.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;
&lt;p&gt;What follows is what I do, and why. None of it is novel; most of it
is the kind of thing that becomes obvious once the constraint
“every example must be runnable, by anyone, on the actual chip”
stops being aspirational.&lt;/p&gt;
&lt;h2 id=&quot;the-opening-line-is-the-chip-s-pinout&quot;&gt;The opening line is the chip’s pinout&lt;/h2&gt;
&lt;p&gt;Every example in &lt;code&gt;tmp108/examples/&lt;/code&gt; opens with the same shape. The
&lt;code&gt;oneshot.rs&lt;/code&gt; body, in full:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; hal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Hal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; i2c&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; hal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;i2c&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; mut&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; tmp&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tmp108&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;new_with_a0_gnd&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;i2c&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; temperature&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; tmp&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;temperature&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;map_err&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; anyhow!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;Failed to read temperature&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;println!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;Temperature: &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;temperature:.2&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; C&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first two lines are the entire host-side preamble. They’re
contract: ignore me, the rest of this file is only about the chip.
After a few examples, the reader’s eye learns to skip past them and
read the rest as if it were library prose. There’s no clock-tree
setup, no peripheral init, no &lt;code&gt;#[entry]&lt;/code&gt; decorator, no panic handler.
The bus is just &lt;em&gt;there&lt;/em&gt;, the way &lt;code&gt;std::fs::File::open&lt;/code&gt; is just there
in a Linux program.&lt;/p&gt;
&lt;p&gt;The third line is the one that does the work. &lt;code&gt;Tmp108::new_with_a0_gnd(i2c)&lt;/code&gt;:
the constructor name &lt;em&gt;is&lt;/em&gt; the pinout. A0 tied to GND means I²C
address &lt;code&gt;0x48&lt;/code&gt;. The TMP108 takes one of four addresses depending on
the state of its A0 pin, and the driver exposes one constructor per
choice — &lt;code&gt;new_with_a0_gnd&lt;/code&gt;, &lt;code&gt;new_with_a0_vplus&lt;/code&gt;, &lt;code&gt;new_with_a0_sda&lt;/code&gt;,
&lt;code&gt;new_with_a0_scl&lt;/code&gt;. There is no &lt;code&gt;new(addr: u8)&lt;/code&gt;. There is no way to
construct a &lt;code&gt;Tmp108&lt;/code&gt; whose address doesn’t correspond to a real
wiring choice on the part.&lt;/p&gt;
&lt;p&gt;The example file tells you which jumper you’re looking at by
&lt;em&gt;naming it in the constructor&lt;/em&gt;. A reader who has the chip strapped
to V+ can see, without consulting the README, that this particular
example assumes A0 is grounded — and that swapping &lt;code&gt;_a0_gnd&lt;/code&gt; for
&lt;code&gt;_a0_vplus&lt;/code&gt; is the only change they need to make. The constructor
name is doing documentation work that would otherwise have lived in
a code comment, where I would have forgotten to update it the next
time I edited the file.&lt;/p&gt;
&lt;h2 id=&quot;one-file-per-chip-behavior-not-per-method&quot;&gt;One file per chip behavior, not per method&lt;/h2&gt;
&lt;p&gt;There are five files in &lt;code&gt;tmp108/examples/&lt;/code&gt;: &lt;code&gt;oneshot.rs&lt;/code&gt;,
&lt;code&gt;continuous.rs&lt;/code&gt;, &lt;code&gt;alert_comparator.rs&lt;/code&gt;, &lt;code&gt;alert_interrupt.rs&lt;/code&gt;, and
&lt;code&gt;sensor_trait.rs&lt;/code&gt;. Each one demonstrates a thing the TMP108 &lt;em&gt;does&lt;/em&gt;
— a single-shot conversion, a continuous-conversion loop, the
ALERT pin in comparator mode, the ALERT pin in interrupt mode, and
the cross-crate &lt;code&gt;embedded-sensors-hal&lt;/code&gt; trait integration. Not
&lt;code&gt;new_with_a0_gnd.rs&lt;/code&gt;, &lt;code&gt;new_with_a0_vplus.rs&lt;/code&gt;, &lt;code&gt;new_with_a0_sda.rs&lt;/code&gt;
— that would be per-constructor, which is per-API, which is
library-shaped. Per-behavior is chip-shaped, which is the grain the
reader is here for.&lt;/p&gt;
&lt;p&gt;The grain of the directory matches the grain of the datasheet’s “Modes
of operation” chapter, not the grain of the Rust struct’s &lt;code&gt;impl&lt;/code&gt;
block. A reader who wants to know “how do I use the ALERT pin in
interrupt mode” goes to &lt;code&gt;alert_interrupt.rs&lt;/code&gt;; a reader who wants to
know “what does the public API look like” can read the
&lt;a rel=&quot;external&quot; href=&quot;https://docs.rs/tmp108&quot;&gt;docs.rs&lt;/a&gt; page. Two different audiences, two
different artifacts, and they don’t have to compete for space in the
same file.&lt;/p&gt;
&lt;p&gt;The two ALERT examples are a good test of whether “chip behavior”
is genuinely the right axis. The TMP108’s ALERT pin has two
operating modes that the &lt;a rel=&quot;external&quot; href=&quot;https://github.com/OpenDevicePartnership/tmp108#gotchas&quot;&gt;README’s Gotchas
section&lt;/a&gt;
spells out: in comparator mode the pin stays asserted until the
temperature returns inside the hysteresis band; in interrupt mode
the pin clears as soon as the configuration register is read. Two
modes, two examples — not because the trait has two methods (it
doesn’t; it has one &lt;code&gt;wait_for_temperature_threshold&lt;/code&gt;), but because
the &lt;em&gt;chip&lt;/em&gt; genuinely behaves differently in the two configurations.
The split exists because the reader who’s debugging “why doesn’t
my ALERT line release” needs to know which mode they configured,
and the example that demonstrates each mode is the most economical
place to learn it.&lt;/p&gt;
&lt;h2 id=&quot;same-file-both-flavors&quot;&gt;Same file, both flavors&lt;/h2&gt;
&lt;p&gt;The TMP108 driver ships both a blocking &lt;code&gt;Tmp108&lt;/code&gt; and an async
&lt;code&gt;AsyncTmp108&lt;/code&gt;, gated on the &lt;code&gt;async&lt;/code&gt; Cargo feature. The straightforward
thing to do would have been &lt;code&gt;oneshot_blocking.rs&lt;/code&gt; and
&lt;code&gt;oneshot_async.rs&lt;/code&gt;, two files, one per build configuration – one can
certainly follow that model too. That isn’t what I did. The
&lt;code&gt;oneshot.rs&lt;/code&gt; file has &lt;em&gt;two&lt;/em&gt; &lt;code&gt;main()&lt;/code&gt; functions in it, picked between
by &lt;code&gt;cfg&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;cfg&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;not&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;feature &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;async&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; main&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Result&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; ... blocking body&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;cfg&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;feature &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;async&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;tokio&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;async&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; fn&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; main&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Result&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; ... async body&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why one file? Because a reader who wants to learn &lt;em&gt;what changes
when you flip to async&lt;/em&gt; would, with two files, have to diff them in
their head. With one file, the diff is &lt;em&gt;in the file&lt;/em&gt;, visible to the
eye, enforced by the compiler. The two preambles at the top are
identical. The chip operations are nearly identical. The only things
that change are the &lt;code&gt;.await&lt;/code&gt;, the &lt;code&gt;#[tokio::main]&lt;/code&gt; macro, and the
import that picks &lt;code&gt;AsyncTmp108&lt;/code&gt; instead of &lt;code&gt;Tmp108&lt;/code&gt;. The reader sees
it, side by side, in the smallest unit of code that demonstrates the
difference.&lt;/p&gt;
&lt;p&gt;The trade-off is honest: the file is busier — every example that
supports both flavors carries two &lt;code&gt;main&lt;/code&gt; functions and at least one
pair of &lt;code&gt;#[cfg]&lt;/code&gt; arms. The payoff is that blocking/async parity is
&lt;em&gt;observable in the source&lt;/em&gt;, not just claimed in the README. Building
with default features picks the blocking &lt;code&gt;main&lt;/code&gt;; building with
&lt;code&gt;--features async&lt;/code&gt; picks the async one. (&lt;code&gt;continuous.rs&lt;/code&gt;,
&lt;code&gt;alert_comparator.rs&lt;/code&gt;, and &lt;code&gt;alert_interrupt.rs&lt;/code&gt; are async-only,
because the underlying API is; their blocking &lt;code&gt;main&lt;/code&gt; stub prints
the required feature flags and exits, which is itself a form of
documentation — the file refuses to build silently into nothing.)&lt;/p&gt;
&lt;h2 id=&quot;a-four-line-header-that-tells-you-what-you-need&quot;&gt;A four-line header that tells you what you need&lt;/h2&gt;
&lt;p&gt;Every example opens with a doc-comment header that says, in the same
shape every time, who the example is for and what it requires.
&lt;code&gt;oneshot.rs&lt;/code&gt;’s header in full:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;! TMP108 one-shot conversion example.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;! # Hardware&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;! - Pico de Gallo USB-attached host adapter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;! - TMP108 on the default I2C bus, A0 → GND (address `0x48`)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;! # Cargo features&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;! Works with default features (blocking). Building with `--features async`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;! produces the async variant.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;! # Register interactions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;! Single read of the temperature register at address `0x00`.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;# Hardware&lt;/code&gt; says what pico de gallo connections the example needs:
USB-attached host adapter, TMP108 on the default I²C bus, A0
strapped to GND. &lt;code&gt;# Cargo features&lt;/code&gt; says how to flip blocking/async
— default features for blocking, &lt;code&gt;--features async&lt;/code&gt; for async.
&lt;code&gt;# Register interactions&lt;/code&gt; names exactly which TMP108 register the
example touches, so the reader who is following along with the
datasheet open can find the relevant chapter without guessing. For
&lt;code&gt;oneshot.rs&lt;/code&gt;, that’s a single read of the temperature register at
address &lt;code&gt;0x00&lt;/code&gt;. For &lt;code&gt;continuous.rs&lt;/code&gt;, it’s a five-step sequence
(read config, write config with M=Continuous, read config, loop on
temperature reads, restore M=Shutdown on exit). The header for each
example calls out the register interactions specific to that
example, in the order they happen.&lt;/p&gt;
&lt;p&gt;This is the section the reader skims first to decide “is this the
example I want?” — a six-line table of contents per file, in the
place a Rust reader expects documentation to be. It costs nothing
to write and removes the most common pre-run question, which is
some flavor of “wait, what do I need to have plugged in for this
one?” The reader doesn’t have to chase the answer through the body
of the file or the README; the answer is already at the top.&lt;/p&gt;
&lt;h2 id=&quot;what-this-doesn-t-cover&quot;&gt;What this doesn’t cover&lt;/h2&gt;
&lt;p&gt;A few honest limits before I oversell any of this.&lt;/p&gt;
&lt;p&gt;The pattern works because pico de gallo can host an I²C bus, an SPI
bus, and a handful of GPIOs from a laptop. Drivers that need a real
MCU peripheral — USB device, Ethernet MAC, anything DMA-bound,
anything where the chip itself drives timing the host can’t
sustain — can’t run this way. The conventions in this post are
opinionated &lt;em&gt;about&lt;/em&gt; I²C/SPI/GPIO sensor and peripheral drivers; I
have not tried any of this on a driver that needs more than those
buses, and I would not assume any of it generalizes.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tmp108&lt;/code&gt; is also one crate. At the time of writing it is the &lt;em&gt;only&lt;/em&gt;
crate on crates.io that depends on &lt;code&gt;pico-de-gallo-hal&lt;/code&gt; — &lt;a rel=&quot;external&quot; href=&quot;https://crates.io/crates/pico-de-gallo-hal/reverse_dependencies&quot;&gt;the
reverse-deps
page&lt;/a&gt;
lists exactly one, and it’s a dev-dependency at that. These
conventions are battle-tested on one chip&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-mcxa-1&quot;&gt;&lt;a href=&quot;#fn-mcxa&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;, with one set of bus
requirements, by one author. Take them as one person’s house style,
not as a recommendation that has survived contact with a population of
users and other crates.&lt;/p&gt;
&lt;p&gt;What I do think holds up, on the evidence of this one crate, is the
shape: the constraint “every example must run on a normal dev
machine against real silicon, with no porting step in between”
forces an &lt;code&gt;examples/&lt;/code&gt; directory that reads like the chip’s
datasheet, not like the library’s &lt;code&gt;impl&lt;/code&gt; block — and the
datasheet’s grain is the right grain for the audience the examples
are written for.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-mcxa&quot;&gt;
&lt;p&gt;That statement is not exactly true as I have used Pico de
Gallo to run weekend long soak tests on MCXA I²C controller and target
drivers. &lt;a href=&quot;#fr-mcxa-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Waking up without a critical section</title>
    <published>2026-06-13T21:38:00+00:00</published>
    <updated>2026-06-13T21:38:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/lockless-atomic-waker-embassy/" type="text/html"/>
    <id>https://balbi.sh/posts/lockless-atomic-waker-embassy/</id>
    <summary type="html">&lt;p&gt;A GPIO interrupt fires. The handler’s job is small: flip a status
bit, call &lt;code&gt;waker.wake()&lt;/code&gt;, return. The task that was parked on that
pin is now ready to run, and the executor will get to it on the next
poll.&lt;/p&gt;
&lt;p&gt;Until you look at what &lt;code&gt;waker.wake()&lt;/code&gt; actually did. On every embassy
target, until very recently, that one call took out a critical
section — a global interrupt disable, held for the duration of the
register-or-wake operation — because the &lt;code&gt;AtomicWaker&lt;/code&gt; in
&lt;code&gt;embassy-sync&lt;/code&gt; was a typedef-style alias for
&lt;code&gt;GenericAtomicWaker&amp;lt;CriticalSectionRawMutex&amp;gt;&lt;/code&gt;. On Cortex-M that is
exactly what it sounds like: the wake path raised BASEPRI (or
disabled IRQs outright on older parts), did its bookkeeping, and
lowered it again. A higher-priority interrupt that arrived in the
middle had to wait. One async task’s wake imposed a jitter floor on
every other interrupt in the system.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;A GPIO interrupt fires. The handler’s job is small: flip a status
bit, call &lt;code&gt;waker.wake()&lt;/code&gt;, return. The task that was parked on that
pin is now ready to run, and the executor will get to it on the next
poll.&lt;/p&gt;
&lt;p&gt;Until you look at what &lt;code&gt;waker.wake()&lt;/code&gt; actually did. On every embassy
target, until very recently, that one call took out a critical
section — a global interrupt disable, held for the duration of the
register-or-wake operation — because the &lt;code&gt;AtomicWaker&lt;/code&gt; in
&lt;code&gt;embassy-sync&lt;/code&gt; was a typedef-style alias for
&lt;code&gt;GenericAtomicWaker&amp;lt;CriticalSectionRawMutex&amp;gt;&lt;/code&gt;. On Cortex-M that is
exactly what it sounds like: the wake path raised BASEPRI (or
disabled IRQs outright on older parts), did its bookkeeping, and
lowered it again. A higher-priority interrupt that arrived in the
middle had to wait. One async task’s wake imposed a jitter floor on
every other interrupt in the system.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/embassy-rs/embassy/pull/6111&quot;&gt;embassy-rs/embassy#6111&lt;/a&gt;
replaces that. The new &lt;code&gt;AtomicWaker&lt;/code&gt; is a port of
&lt;a rel=&quot;external&quot; href=&quot;https://docs.rs/futures/latest/futures/task/struct.AtomicWaker.html&quot;&gt;&lt;code&gt;futures::task::AtomicWaker&lt;/code&gt;&lt;/a&gt;
— the one that has been doing this on &lt;code&gt;std&lt;/code&gt; targets for years —
adapted for &lt;code&gt;no_std&lt;/code&gt;. The trade I made is concrete: a small atomic
state machine in place of the critical section, plus one documented
semantics change that &lt;code&gt;Future::poll&lt;/code&gt; already required callers to
honor. The old behavior is kept verbatim under a new name,
&lt;code&gt;CriticalSectionWaker&lt;/code&gt;, for the chips and the call sites that still
want it.&lt;/p&gt;
&lt;h2 id=&quot;what-the-type-used-to-be&quot;&gt;What the type used to be&lt;/h2&gt;
&lt;p&gt;The pre-#6111 definition is essentially one line:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;pub&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; type&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; AtomicWaker&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; GenericAtomicWaker&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;CriticalSectionRawMutex&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;GenericAtomicWaker&amp;lt;M&amp;gt;&lt;/code&gt; wraps an &lt;code&gt;Option&amp;lt;Waker&amp;gt;&lt;/code&gt; in a
&lt;code&gt;Mutex&amp;lt;M, Cell&amp;lt;...&amp;gt;&amp;gt;&lt;/code&gt;. With &lt;code&gt;M = CriticalSectionRawMutex&lt;/code&gt;, every
&lt;code&gt;register()&lt;/code&gt; and every &lt;code&gt;wake()&lt;/code&gt; calls &lt;code&gt;critical_section::with(...)&lt;/code&gt;,
which on Cortex-M expands to “disable interrupts, run the closure,
restore the previous mask.” The closure itself is tiny — replace a
cell, maybe clone, maybe call &lt;code&gt;wake_by_ref&lt;/code&gt; — but the whole CPU is
now refusing to acknowledge anything else for the duration.&lt;/p&gt;
&lt;p&gt;That is fine when &lt;code&gt;wake()&lt;/code&gt; is a background bookkeeping call. It is
much less fine when &lt;code&gt;wake()&lt;/code&gt; is itself a high-priority IRQ handler
whose latency you care about, or when a &lt;em&gt;different&lt;/em&gt; high-priority
IRQ arrives during the wake and has to wait for an unrelated
cell-swap to finish before it gets to run.&lt;/p&gt;
&lt;h2 id=&quot;the-three-state-machine&quot;&gt;The three-state machine&lt;/h2&gt;
&lt;p&gt;The new type, in &lt;code&gt;embassy-sync/src/waitqueue/atomic_waker.rs&lt;/code&gt;,
replaces the mutex with a packed &lt;code&gt;AtomicUsize&lt;/code&gt; and three constants:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; WAITING&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; usize&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;     =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; REGISTERING&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; usize&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0b01&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; WAKING&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; usize&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;      =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0b10&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important thing about those constants — and this came out of
review, so it’s now spelled out in the source comments — is that
they are &lt;strong&gt;flags, not exclusive states&lt;/strong&gt;. &lt;code&gt;REGISTERING | WAKING&lt;/code&gt;
(0b11) is a legal, intentional state of the machine. It means a
&lt;code&gt;register()&lt;/code&gt; is mid-flight &lt;em&gt;and&lt;/em&gt; a &lt;code&gt;wake()&lt;/code&gt; arrived while it was
running, and the wake has been flagged as pending. The encoding is
load-bearing: only the holder of &lt;code&gt;REGISTERING&lt;/code&gt; or &lt;code&gt;WAKING&lt;/code&gt; (and
never both at once, by virtue of the protocol) is allowed to touch
the underlying waker cell, and the 0b11 state is what tells the
in-flight &lt;code&gt;register()&lt;/code&gt; that there is wake duty waiting for it on
the way out.&lt;/p&gt;
&lt;p&gt;The wake side is short and worth showing in full:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; self&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;fetch_or&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;WAKING&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; AcqRel&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    WAITING&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;        //&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; We own the cell. Fire the wake, release.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        unsafe&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            if&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; let&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Some&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;w&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span&gt;waker&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;                w&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;wake_by_ref&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;        self&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;swap&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;WAITING&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Release&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;    _&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;        //&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; Somebody else owns the cell. They&amp;#39;ll deliver the wake.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two arms, and that is the whole thing. If the prior state was
&lt;code&gt;WAITING&lt;/code&gt;, we are now the sole owner of the cell via the &lt;code&gt;WAKING&lt;/code&gt;
bit; we invoke the stored waker by reference, then drop the bit. If
the prior state was &lt;code&gt;REGISTERING&lt;/code&gt;, the OR has produced
&lt;code&gt;REGISTERING | WAKING&lt;/code&gt;, and we deliberately do nothing else,
because the in-flight &lt;code&gt;register()&lt;/code&gt; will see our bit on its way out
and deliver the wake itself.&lt;/p&gt;
&lt;h2 id=&quot;why-fetch-or-and-not-compare-exchange&quot;&gt;Why fetch_or and not compare_exchange&lt;/h2&gt;
&lt;p&gt;This is the load-bearing decision in the whole PR, and review asked
the obvious question: shouldn’t this be a &lt;code&gt;compare_exchange&lt;/code&gt;? It’s
the safer default, the one you reach for first.&lt;/p&gt;
&lt;p&gt;It would be silently wrong here. Consider the race:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A task calls &lt;code&gt;register(cx.waker())&lt;/code&gt;. The CAS from &lt;code&gt;WAITING&lt;/code&gt; to
&lt;code&gt;REGISTERING&lt;/code&gt; succeeds; we now own the cell and start installing
the new waker.&lt;/li&gt;
&lt;li&gt;Mid-installation, an interrupt fires and calls &lt;code&gt;wake()&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If &lt;code&gt;wake()&lt;/code&gt; did &lt;code&gt;compare_exchange(WAITING, WAKING, ...)&lt;/code&gt;, that CAS
would observe &lt;code&gt;REGISTERING&lt;/code&gt;, fail, and &lt;em&gt;silently return&lt;/em&gt; — there is no
prior value it could have CAS-ed against that would have encoded “a
wake is pending.” The in-flight &lt;code&gt;register()&lt;/code&gt; would then finish, CAS
its own state cleanly back from &lt;code&gt;REGISTERING&lt;/code&gt; to &lt;code&gt;WAITING&lt;/code&gt;, and never
learn that anything happened. The waker is installed, nobody is going
to invoke it, the task is hung. The kind of bug you find at 2am after
staring at the screen for an hour wondering why an interrupt that you
can &lt;em&gt;see&lt;/em&gt; on the registers isn’t producing a task wakeup.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;fetch_or(WAKING, AcqRel)&lt;/code&gt; doesn’t have that failure mode. The
wake’s contribution to the state word is unconditional and visible
to whoever observes the state next. The in-flight &lt;code&gt;register()&lt;/code&gt;
tries to release with &lt;code&gt;compare_exchange(REGISTERING, WAITING, ...)&lt;/code&gt;
on its way out; that CAS will &lt;em&gt;fail&lt;/em&gt; exactly when a wake raced it,
because the observed state is now &lt;code&gt;REGISTERING | WAKING&lt;/code&gt;, not
&lt;code&gt;REGISTERING&lt;/code&gt;. The failure branch is the handoff signal: it takes
the freshly-installed waker out of the cell, clears both bits with
a &lt;code&gt;swap&lt;/code&gt;, and invokes the waker &lt;em&gt;after&lt;/em&gt; releasing the cell — so
that a user waker that re-enters &lt;code&gt;register()&lt;/code&gt; or &lt;code&gt;wake()&lt;/code&gt; on the
same &lt;code&gt;AtomicWaker&lt;/code&gt; from inside &lt;code&gt;wake()&lt;/code&gt; cannot deadlock against
our own state.&lt;/p&gt;
&lt;p&gt;The wake is delivered exactly once, by whichever side observes the
contention. The handoff is structural, not best-effort. &lt;code&gt;fetch_or&lt;/code&gt;
is doing more than “atomic OR” here; it is the data structure that
makes the race path correct.&lt;/p&gt;
&lt;h2 id=&quot;what-changed-in-the-contract&quot;&gt;What changed in the contract&lt;/h2&gt;
&lt;p&gt;There is one user-visible semantic difference, and the PR owns it
in the CHANGELOG: &lt;code&gt;AtomicWaker::register()&lt;/code&gt; now wakes the
&lt;em&gt;previously registered&lt;/em&gt; waker if you replace it with a different
one. This matches what
&lt;a rel=&quot;external&quot; href=&quot;https://docs.rs/embassy-sync/latest/embassy_sync/waitqueue/struct.WakerRegistration.html&quot;&gt;&lt;code&gt;WakerRegistration&lt;/code&gt;&lt;/a&gt;
has always done. The pre-#6111 &lt;code&gt;AtomicWaker&lt;/code&gt; would silently drop
the old waker on the floor.&lt;/p&gt;
&lt;p&gt;The reason this is safe is that &lt;code&gt;Future::poll&lt;/code&gt; is already
documented to require it. The relevant line in the
&lt;a rel=&quot;external&quot; href=&quot;https://doc.rust-lang.org/std/future/trait.Future.html#tymethod.poll&quot;&gt;&lt;code&gt;Future::poll&lt;/code&gt;&lt;/a&gt;
contract:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that on multiple calls to &lt;code&gt;poll&lt;/code&gt;, only the &lt;code&gt;Waker&lt;/code&gt; from the
&lt;code&gt;Context&lt;/code&gt; passed to the most recent call should be scheduled to
receive a wakeup.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A correctly-written driver already re-registers on every poll,
because the &lt;code&gt;Waker&lt;/code&gt; it gets on the &lt;em&gt;n&lt;/em&gt;-th poll is not guaranteed
to be the one it got on the &lt;em&gt;(n-1)&lt;/em&gt;-th. Waking the evicted
predecessor formalizes that: if a different task ends up polling
the same future, the displaced task gets a chance to re-register
itself instead of vanishing. This was a late commit, prompted by
review, and it is what the CHANGELOG entry calls out by name.&lt;/p&gt;
&lt;h2 id=&quot;two-siblings-now-on-purpose&quot;&gt;Two siblings now, on purpose&lt;/h2&gt;
&lt;p&gt;The previous behavior didn’t disappear. It moved into its own
module, &lt;code&gt;embassy-sync/src/waitqueue/critical_section_waker.rs&lt;/code&gt;, and
got a name that says what it is:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;pub&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; struct&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; CriticalSectionWaker&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;    waker&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; GenericAtomicWaker&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;CriticalSectionRawMutex&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a verbatim preservation of the old &lt;code&gt;AtomicWaker&lt;/code&gt; body —
the same &lt;code&gt;wake_by_ref + restore&lt;/code&gt; flow, the same lack of an atomic
state machine, the same critical section on the wake path. Call
sites that depended on the old “replace without waking” semantics
keep working with a one-word rename.&lt;/p&gt;
&lt;p&gt;The asymmetry is deliberate, and I want to be explicit about it
because it matters for anyone reading older code: the new
&lt;code&gt;AtomicWaker&lt;/code&gt; wakes evicted predecessors; &lt;code&gt;GenericAtomicWaker&amp;lt;M&amp;gt;&lt;/code&gt;
does not, and &lt;code&gt;CriticalSectionWaker&lt;/code&gt; (being a wrapper around it)
does not either. Review caught an earlier commit that tried to
bring &lt;code&gt;GenericAtomicWaker&lt;/code&gt; along to the new semantics, and I
reverted it. A public type that has been in the tree for years
should not change behavior under callers without a much louder
migration story than this PR is in a position to write — and
there’s no need for one. Callers who want the new semantics have
&lt;code&gt;AtomicWaker&lt;/code&gt;; callers who want the old have the type they were
already using.&lt;/p&gt;
&lt;h2 id=&quot;thumbv6-doesn-t-have-the-atomics&quot;&gt;thumbv6 doesn’t have the atomics&lt;/h2&gt;
&lt;p&gt;The lockless state machine assumes you can do
&lt;code&gt;AtomicUsize::{compare_exchange, swap, fetch_or}&lt;/code&gt; on the target.
Cortex-M0/M0+ (thumbv6m) can’t — no LDREX/STREX in the instruction
set, so the LLVM atomics for anything past naked load/store aren’t
there. The same goes for a handful of other targets in the embassy
matrix (Xtensa S2, AVR, RV32I — anything missing the relevant
&lt;code&gt;target_has_atomic&lt;/code&gt; cfg).&lt;/p&gt;
&lt;p&gt;The PR’s solution is the obvious one: gate the new type on
&lt;code&gt;#[cfg(target_has_atomic = &quot;32&quot;)]&lt;/code&gt;, and on every other target make
&lt;code&gt;AtomicWaker&lt;/code&gt; a type alias for &lt;code&gt;CriticalSectionWaker&lt;/code&gt;. Callers see
the same type name; the implementation behind it is the one the
hardware can actually run. The sibling type isn’t there for
nostalgia. Some chips genuinely need it.&lt;/p&gt;
&lt;h2 id=&quot;what-the-wake-path-no-longer-does&quot;&gt;What the wake path no longer does&lt;/h2&gt;
&lt;p&gt;The benchmark on the bench was a GPIO IRQ wake on an FRDM-MCXA266
(Cortex-M33 at 180 MHz); the lockless &lt;code&gt;wake()&lt;/code&gt; body came out about
290 ns shorter than the critical-section one. I’m not making that
the headline. The wall-clock delta is small enough to be well
within run-to-run noise on a 180 MHz core; if the structural
change weren’t real, I would not trust that number to mean
anything.&lt;/p&gt;
&lt;p&gt;The structural change is real, and it is what the PR is actually
about. On any embassy target with &lt;code&gt;AtomicUsize&lt;/code&gt;-class atomics —
which is most of them — the wake path of &lt;code&gt;AtomicWaker&lt;/code&gt; no longer
disables interrupts. An IRQ handler that calls &lt;code&gt;waker.wake()&lt;/code&gt;
doesn’t enter a critical section, doesn’t pay the enter/exit cost
on every wake, and doesn’t impose a jitter floor on whatever
higher-priority interrupt would otherwise have preempted it. The
worst case for a preempting IRQ is now an &lt;code&gt;AtomicUsize&lt;/code&gt; op or two
and a &lt;code&gt;wake_by_ref&lt;/code&gt; call. On chips that &lt;em&gt;can’t&lt;/em&gt; do the atomics, the
fallback is the type that was already in the tree; nothing
regresses.&lt;/p&gt;
&lt;p&gt;None of this is novel. &lt;code&gt;futures::task::AtomicWaker&lt;/code&gt; has been doing
this on &lt;code&gt;std&lt;/code&gt; for years, and the algorithm here is a direct port.
What changes is that &lt;code&gt;embassy-sync&lt;/code&gt; — and every embassy driver
that uses &lt;code&gt;AtomicWaker&lt;/code&gt; for IRQ-to-task wakeups — gets that
property by default. A single-waiter waker can hand off the
register-vs-wake race with a &lt;code&gt;fetch_or&lt;/code&gt;-based state machine instead
of a critical section, and the cost is one documented semantics
change that &lt;code&gt;Future::poll&lt;/code&gt; already required callers to honor.&lt;/p&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>What did the boot ROM just do to my RAM?</title>
    <published>2026-06-11T09:00:00+00:00</published>
    <updated>2026-06-11T09:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/rambo-rom-collateral-damage/" type="text/html"/>
    <id>https://balbi.sh/posts/rambo-rom-collateral-damage/</id>
    <summary type="html">&lt;p&gt;A while back I was trying to get &lt;a rel=&quot;external&quot; href=&quot;https://github.com/embassy-rs/teleprobe&quot;&gt;teleprobe&lt;/a&gt;
to run a test suite on an RT685-EVK. Teleprobe’s trick is that it takes
your test, compiled as a standalone firmware binary linked to execute
directly from SRAM, loads it into the target’s RAM over the probe, and
runs it from there — flash never gets touched. On every board I’d used
it with before, it just worked. On the RT685, every run ended in a
HardFault before the first test got a chance to do anything.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;A while back I was trying to get &lt;a rel=&quot;external&quot; href=&quot;https://github.com/embassy-rs/teleprobe&quot;&gt;teleprobe&lt;/a&gt;
to run a test suite on an RT685-EVK. Teleprobe’s trick is that it takes
your test, compiled as a standalone firmware binary linked to execute
directly from SRAM, loads it into the target’s RAM over the probe, and
runs it from there — flash never gets touched. On every board I’d used
it with before, it just worked. On the RT685, every run ended in a
HardFault before the first test got a chance to do anything.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;
&lt;p&gt;The diagnosis took longer than the bug deserved. The RT685’s boot ROM
was power-cycling almost every SRAM partition on the way to user code —
including the one the test binary was linked into. The instructions
were being scribbled over between the load and the first fetch, so the
core would dutifully jump to what &lt;em&gt;had been&lt;/em&gt; code, hit garbage, and
fault. Nothing in the reference manual said which partitions the ROM
touches at startup, and the HardFault itself only told me where
execution died, not why the instructions there were no longer the ones
I’d loaded. I worked it out by reading whatever I could find, writing a
few throwaway scripts, and squinting at probe dumps.&lt;/p&gt;
&lt;p&gt;That experience is why I wrote &lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/rambo&quot;&gt;&lt;code&gt;rambo&lt;/code&gt;&lt;/a&gt;.
It’s a small CLI whose only job is to answer the question I should have
been able to look up: &lt;em&gt;which parts of this chip’s RAM does the boot ROM
clobber before my first instruction runs?&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-rambo-actually-does&quot;&gt;What rambo actually does&lt;/h2&gt;
&lt;p&gt;The core loop is short enough to describe in a paragraph. For every RAM
region probe-rs knows about for a given chip, rambo writes a deterministic
pattern across the whole region — each 32-bit word stores its own
address. Then it issues a reset and halts the core on the vector catch,
&lt;em&gt;before&lt;/em&gt; any user firmware runs. It reads the region back and classifies
every block as one of four things: SAFE (the pattern survived), ZERO
(filled with &lt;code&gt;0x00000000&lt;/code&gt;), ONES (filled with &lt;code&gt;0xFFFFFFFF&lt;/code&gt;), or CHANGED
(rewritten with something else — ROM scratch, stack, or working data,
and sometimes just garbage from a partition the ROM power-cycled).
Output is a colored heatmap, a run-length-encoded summary of contiguous
regions sharing a class, and per-region totals.&lt;/p&gt;
&lt;p&gt;It is deliberately a plain stdout program with ANSI colors. Not a TUI.
Not a simulator. Not a fancy GUI. It’s something you can read in your
terminal, pipe to a file, or paste into a bug report when you ask a
vendor why their ROM is doing what it’s doing. The only thing it depends
on at runtime is a debug probe and a chip that probe-rs supports — which,
in practice, is most of the Cortex-M world.&lt;/p&gt;
&lt;p&gt;It does not flash anything. It does not modify your firmware. It writes
a pattern, resets, reads back. That’s it.&lt;/p&gt;
&lt;h2 id=&quot;why-writing-the-address-as-data-matters&quot;&gt;Why writing the address as data matters&lt;/h2&gt;
&lt;p&gt;The “each word stores its own address” pattern is the kind of thing that
looks arbitrary until you’ve tried the alternatives. A solid bit pattern
like &lt;code&gt;0xDEADBEEF&lt;/code&gt; repeated everywhere will tell you whether RAM was
clobbered, but it won’t tell you whether the readback came from the
address you think it did — if the chip aliases memory or has unmapped
windows in a reserved range, you can read the same &lt;code&gt;0xDEADBEEF&lt;/code&gt; back
from two different addresses and not notice. With &lt;code&gt;addr-as-data&lt;/code&gt;, every
location is uniquely identifiable. A SAFE word is unambiguous. A ZERO
or ONES word is unambiguous. A CHANGED word that &lt;em&gt;also&lt;/em&gt; happens to
equal some &lt;em&gt;other&lt;/em&gt; valid address is a strong hint that something is
aliased. The pattern is essentially free to generate, and it carries
its own coordinate system.&lt;/p&gt;
&lt;h2 id=&quot;a-small-tour-of-the-optional-modes&quot;&gt;A small tour of the optional modes&lt;/h2&gt;
&lt;p&gt;The default survey answers the headline question. The optional modes are
there for when the answer to “what is the ROM doing here?” is more
interesting than “nothing” or “everything.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--fingerprint&lt;/code&gt;&lt;/strong&gt; classifies the &lt;em&gt;kind&lt;/em&gt; of clobber inside CHANGED
blocks. Is the block mostly zeros with a few non-zero words at the top?
That looks like a stack frame. Is every word the same non-zero value?
That’s a constant fill. Is it an ascending counter, a repeating motif,
or an address-plus-offset pattern? Each of those points at a different
ROM behavior — a memset, a struct initialization, a copy with a fixed
offset, an uninitialized scratch buffer the ROM forgot to clear. None
of these tell you exactly what the ROM is doing, but they let you skip
straight past “is this even structured data” and into “this looks like
a 16-byte header followed by a counter.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--dual-pattern&lt;/code&gt;&lt;/strong&gt; writes &lt;code&gt;addr&lt;/code&gt; in one reset cycle and &lt;code&gt;~addr&lt;/code&gt; in a
second. Both passes get classified independently and compared. If a
block came back SAFE in both passes — pattern survived both times —
then the ROM genuinely didn’t touch it. If a block came back CHANGED
in both passes but with &lt;em&gt;the same&lt;/em&gt; value, the ROM is actively writing
there regardless of what was already in memory. This disambiguates two
flavors of “the ROM didn’t touch this” that single-pass surveys can’t
tell apart: &lt;em&gt;undriven&lt;/em&gt; (the ROM left it alone) versus &lt;em&gt;coincidentally
the same as my pattern&lt;/em&gt; (extraordinarily unlikely with addr-as-data,
but possible in pathological cases).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--write-readback&lt;/code&gt;&lt;/strong&gt; is the opposite trick: skip the reset entirely.
Write the pattern, immediately read it back, classify what comes out.
On normal RAM, this is boring — everything classifies as SAFE. On
reserved address ranges, on code-bus aliases of system RAM, on
peripheral windows that look like memory in the chip description but
aren’t, this is where you find out. Aliasing shows up as the same
content at two addresses. Unmapped windows show up as bus faults or
all-ones reads. It’s worth running once on any new chip just to see
what its memory map actually does, as opposed to what the documentation
claims.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--reset-cycles N&lt;/code&gt;&lt;/strong&gt; re-runs the read after each of N resets without
re-writing the pattern in between. If the ROM is deterministic — same
chip, same reset, same post-ROM RAM contents every time — the
classification is identical across cycles. If anything drifts, you’ve
got nondeterminism worth investigating: timing-sensitive scratch,
uninitialized stack frames bleeding in from whatever was happening
microseconds before the reset, or RAM that hasn’t fully settled.
This one is mostly useful when something &lt;em&gt;should&lt;/em&gt; be the same every
boot but isn’t, and you need evidence before you can start arguing
about it.&lt;/p&gt;
&lt;h2 id=&quot;the-ci-bit&quot;&gt;The CI bit&lt;/h2&gt;
&lt;p&gt;The mode I’m most happy with isn’t in the survey — it’s in the two flags
that turn rambo into a regression gate.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;--json &amp;lt;path&amp;gt;&lt;/code&gt; writes a stable, schema-versioned JSON report of the
entire run. Use it as a CI artifact, archive it across firmware
versions, diff it when something changes.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;--expectations &amp;lt;path&amp;gt;&lt;/code&gt; takes a small declarative file — a “RAM
contract” — and evaluates each clause against the survey. Each entry
names a range and one of three claims:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;main_sram_crash_dump_area&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;0x20030000&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;0x20031000&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;safe&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;  &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;rationale&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;Reserved for crash-dump recovery after watchdog reset.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The clauses are &lt;code&gt;expect: &amp;lt;class&amp;gt;&lt;/code&gt; (every block must be that class),
&lt;code&gt;expect_any_of: [&amp;lt;class&amp;gt;, ...]&lt;/code&gt; (every block must be one of these), and
&lt;code&gt;expect_not: &amp;lt;class&amp;gt;&lt;/code&gt; (no block may be that class). Exit code is 0 if
every expectation holds, 1 if any fails. Ranges are checked for
alignment and bounds &lt;em&gt;before&lt;/em&gt; any probe I/O happens, so a typo can’t
brick a run.&lt;/p&gt;
&lt;p&gt;What this gets you is a small, version-controlled answer to “the
bootloader assumes these regions survive the ROM” that runs on every
PR and on every new silicon rev. The next time a chip vendor patches
their ROM and quietly starts touching memory they didn’t touch before,
your CI tells you. It’s a tiny, boring thing. It’s also the kind of
thing that would have saved me a few days on the RT685.&lt;/p&gt;
&lt;h2 id=&quot;what-rambo-isn-t&quot;&gt;What rambo isn’t&lt;/h2&gt;
&lt;p&gt;A few honest limits before you reach for it.&lt;/p&gt;
&lt;p&gt;It’s only as accurate as probe-rs’s chip database. If a chip’s
&lt;code&gt;memory_map&lt;/code&gt; omits a region, rambo can’t survey it. Most of the time
this is fine; occasionally a vendor leaves a peripheral-attached SRAM
out of the description and you’ll need to add it yourself (or
&lt;code&gt;--chip-description-path&lt;/code&gt; your own).&lt;/p&gt;
&lt;p&gt;Some CMSIS-Pack descriptions list code-bus &lt;em&gt;aliases&lt;/em&gt; of system-bus
RAM as separate regions. Rambo treats every region in the map as
independent, so you’ll see the same physical RAM surveyed twice under
different addresses. That’s almost always what you want — the
aliases sometimes behave differently — but it can look surprising the
first time.&lt;/p&gt;
&lt;p&gt;And “SAFE” only means “the ROM didn’t write here on &lt;em&gt;this&lt;/em&gt; chip rev,
with &lt;em&gt;this&lt;/em&gt; fuse configuration, on &lt;em&gt;this&lt;/em&gt; boot mode.” It does not
mean the ROM will never write there. That gap is exactly why the
contract file and CI gate exist.&lt;/p&gt;
&lt;h2 id=&quot;try-it&quot;&gt;Try it&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;cargo install rambo&lt;/code&gt;, or grab a pre-built binary from the
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/rambo/releases&quot;&gt;releases page&lt;/a&gt; for
Linux, macOS (Intel + Apple Silicon), or Windows. Source is on
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/felipebalbi/rambo&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you run it on a chip I haven’t tested with — so far that’s
RT685, RT633, MCXA266, MCXA276, and MCXA577 — I’d genuinely like
to hear what came back. Especially if it surprises you.&lt;/p&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Writing embedded drivers without an MCU</title>
    <published>2026-06-10T16:00:00+00:00</published>
    <updated>2026-06-10T16:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/writing-embedded-drivers-without-an-mcu/" type="text/html"/>
    <id>https://balbi.sh/posts/writing-embedded-drivers-without-an-mcu/</id>
    <summary type="html">&lt;p&gt;Let me clarify the title up front: there &lt;em&gt;is&lt;/em&gt; an MCU. There’s a
Raspberry Pi Pico 2 sitting on my desk. What I mean is that the MCU
isn’t in my dev loop. I don’t flash it. I don’t reset it. I don’t
attach a probe to it. I treat it like a USB peripheral that happens
to speak the buses I need — I²C, SPI, UART, GPIOs, ADC, and 1-Wire
— and I write all of my driver code as a normal &lt;code&gt;cargo run&lt;/code&gt; binary
that runs on my laptop, against the real sensor, over a real bus.&lt;/p&gt;
&lt;p&gt;This post is about how that works, why it’s nice, and what you can do
once your “embedded” driver is just a Rust program with &lt;code&gt;std&lt;/code&gt;.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;Let me clarify the title up front: there &lt;em&gt;is&lt;/em&gt; an MCU. There’s a
Raspberry Pi Pico 2 sitting on my desk. What I mean is that the MCU
isn’t in my dev loop. I don’t flash it. I don’t reset it. I don’t
attach a probe to it. I treat it like a USB peripheral that happens
to speak the buses I need — I²C, SPI, UART, GPIOs, ADC, and 1-Wire
— and I write all of my driver code as a normal &lt;code&gt;cargo run&lt;/code&gt; binary
that runs on my laptop, against the real sensor, over a real bus.&lt;/p&gt;
&lt;p&gt;This post is about how that works, why it’s nice, and what you can do
once your “embedded” driver is just a Rust program with &lt;code&gt;std&lt;/code&gt;.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;the-loop-i-m-trying-to-escape&quot;&gt;The loop I’m trying to escape&lt;/h2&gt;
&lt;p&gt;The traditional embedded dev loop looks something like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Edit a line of code.&lt;/li&gt;
&lt;li&gt;Wait for &lt;code&gt;cargo build --target=thumbv8m.main-none-eabihf&lt;/code&gt; to
finish.&lt;/li&gt;
&lt;li&gt;Reflash the chip (a few seconds with a probe, longer over the
bootloader).&lt;/li&gt;
&lt;li&gt;Either power-cycle the board or punch the reset button.&lt;/li&gt;
&lt;li&gt;Squint at &lt;code&gt;defmt&lt;/code&gt; output through a probe.&lt;/li&gt;
&lt;li&gt;If you need to reproduce a bug, also: physically interact with the
board (push a button, breathe on the sensor, jiggle a wire).&lt;/li&gt;
&lt;li&gt;Goto 1.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;On a good day with a fast build and a good probe, that’s two minutes
per iteration. On a bad day — a stale build cache, a probe that
disconnects, a chip in some weird stuck state — it’s five or ten.&lt;/p&gt;
&lt;p&gt;The frustrating part isn’t the time. The frustrating part is that
&lt;strong&gt;none of it is about the driver I’m writing&lt;/strong&gt;. The driver is just
some byte-level logic that talks to a chip over I²C. The chip
doesn’t care which CPU is initiating the transactions. The chip
certainly doesn’t care that I had to write a linker script and a
clock-tree configuration to get there.&lt;/p&gt;
&lt;p&gt;So: why not skip all of that, and write the protocol logic somewhere
where iteration is free?&lt;/p&gt;
&lt;h2 id=&quot;enter-pico-de-gallo&quot;&gt;Enter Pico de Gallo&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;/pico-de-gallo/&quot;&gt;&lt;em&gt;Pico de Gallo&lt;/em&gt;&lt;/a&gt; is a project I’ve been building
for exactly this. It’s a small landing board that turns a Pi Pico 2
into a USB-attached protocol bridge. Plug it into your laptop, and
your laptop now has an I²C bus, a SPI bus, a UART, a handful of
GPIOs, two PWM channels, an ADC, and a 1-Wire master — addressable
from any host-side program.&lt;/p&gt;
&lt;p&gt;A few things it &lt;em&gt;isn’t&lt;/em&gt;, because it gets confused with each of these:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It’s not a logic analyzer. It drives the bus; it doesn’t sniff
somebody else’s.&lt;/li&gt;
&lt;li&gt;It’s not a USB-to-I²C dongle in the FTDI sense. The chip
underneath is a Pi Pico 2, the firmware speaks
&lt;a rel=&quot;external&quot; href=&quot;https://docs.rs/postcard-rpc/&quot;&gt;&lt;code&gt;postcard-rpc&lt;/code&gt;&lt;/a&gt;, and the host side
is a real Rust crate with proper types and async support.&lt;/li&gt;
&lt;li&gt;It’s not a debug probe. There’s no SWD on the other end of the
cable. There’s only the device you wired up.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What it actually does is let you write this:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; pico_de_gallo_hal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Hal&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; embedded_hal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;i2c&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;I2c&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; hal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Hal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; mut&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; i2c&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; hal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;i2c&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; mut&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; buf&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;u8&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;i2c&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;write_read&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0x48&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0x00&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;mut&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; buf&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;unwrap&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;…on your laptop, and have the bytes appear on a real I²C wire,
addressed to a real chip, with the reply coming back into &lt;code&gt;buf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The Pico de Gallo book has &lt;a href=&quot;/pico-de-gallo/&quot;&gt;the full setup&lt;/a&gt; — what
to assemble, how to flash the firmware, the pinout, and a tour of
each interface. I won’t repeat any of that here. From this point I
assume you have a Pico de Gallo plugged in, a TMP108 wired to the
I²C connector, and the &lt;code&gt;gallo&lt;/code&gt; CLI installed.&lt;/p&gt;
&lt;h2 id=&quot;feeling-the-bus-before-writing-a-line-of-code&quot;&gt;Feeling the bus before writing a line of code&lt;/h2&gt;
&lt;p&gt;The first thing I do with any new chip is poke at it through the CLI.
This is the equivalent of the “wiggle a probe at it” step from the
hardware world, except it’s three commands.&lt;/p&gt;
&lt;p&gt;Did the chip enumerate at the address I think it did?&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; gallo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; i2c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; scan&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;╭────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────╮&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;│&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;    │&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  0&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  1&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  2&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  3&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  4&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  5&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  6&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  7&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  8&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;  9&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;  a&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;  b&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;  c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;  d&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;  e&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;  f&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;├────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;│&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;  │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; RR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; RR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; RR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; RR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; RR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; RR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; RR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; RR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;│&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;  │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;│&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;  │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;│&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 3&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;  │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;│&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 4&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;  │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 48&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;│&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 5&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;  │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;│&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 6&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;  │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;│&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 7&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;  │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; RR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; RR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; RR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; RR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; RR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; RR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; RR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; RR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;╰────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────╯&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yes. The TMP108 with its A0 pin to ground sits at 0x48, and there it
is. If this had come back empty, I’d know it was wiring, not
software, and I’d know it before I’d touched a Rust file.&lt;/p&gt;
&lt;p&gt;Can I read its temperature register?&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; gallo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; i2c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; write-read&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-address&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x48&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-bytes&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x00&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;-count&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;0x18&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 0x40&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two bytes, just as the datasheet promised. Register 0x00 is the
temperature register; the top 12 bits of the returned 16 are the
signed reading in 0.0625 °C/LSB. So 0x1840 → 0x184 → 388 → 388 ×
0.0625 → &lt;strong&gt;24.25 °C&lt;/strong&gt;. The room I’m in is about 24 °C.&lt;/p&gt;
&lt;p&gt;Three commands, no Rust, and I know:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;My wiring works.&lt;/li&gt;
&lt;li&gt;The address I expected is the address the chip enumerated at.&lt;/li&gt;
&lt;li&gt;The data format matches the datasheet.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Those three facts cost me about a minute of reading effort each on a
real MCU, every time something goes wrong, because they’re all
entangled with everything else. Here they’re isolated and free.&lt;/p&gt;
&lt;h2 id=&quot;reading-it-from-rust&quot;&gt;Reading it from Rust&lt;/h2&gt;
&lt;p&gt;Now for the actual program. The &lt;a rel=&quot;external&quot; href=&quot;https://crates.io/crates/tmp108&quot;&gt;&lt;code&gt;tmp108&lt;/code&gt;
crate&lt;/a&gt; on crates.io already exists
— it’s a regular &lt;code&gt;embedded-hal&lt;/code&gt; driver, &lt;code&gt;#[no_std]&lt;/code&gt;, written for
anything that implements &lt;code&gt;embedded_hal::i2c::I2c&lt;/code&gt;. Normally you’d
pull it into a firmware project, build for a Cortex-M target, and
flash. We’re going to pull it into a &lt;code&gt;std&lt;/code&gt; binary instead.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;dependencies&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;pico-de-gallo-hal&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;tmp108&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; pico_de_gallo_hal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Hal&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; tmp108&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Tmp108&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; main&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; anyhow&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Result&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;    let&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; hal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Hal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;    let&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; i2c&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; hal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;i2c&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;    let&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; mut&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; tmp&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tmp108&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;new_with_a0_gnd&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;i2c&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;    let&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; temperature&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; tmp&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;temperature&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;    println!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;Temperature: &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;temperature:.2&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; °C&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    Ok&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s the whole program. &lt;code&gt;cargo run&lt;/code&gt;, and the bytes go out, the
reply comes back, and a number prints.&lt;/p&gt;
&lt;p&gt;The interesting thing to notice is what isn’t there. The &lt;code&gt;tmp108&lt;/code&gt;
crate doesn’t know that &lt;code&gt;pico-de-gallo&lt;/code&gt; exists. It has no
conditional compilation for “host” or “USB.” Its only requirement is
“give me something that implements &lt;code&gt;embedded_hal::i2c::I2c&lt;/code&gt;,” and
&lt;code&gt;pico-de-gallo-hal&lt;/code&gt; does. The exact same &lt;code&gt;Tmp108::new_with_a0_gnd&lt;/code&gt;
call, with the exact same crate, the exact same version, will run
unmodified on an RP2350, an STM32, an nRF52, or any other chip that
has an &lt;code&gt;embedded-hal&lt;/code&gt; I²C implementation.&lt;/p&gt;
&lt;p&gt;This is not emulation. It’s not mocking. It’s not simulation. The
bus is real. The chip is real. The driver is real. The only thing
that’s different is what’s underneath the &lt;code&gt;I2c&lt;/code&gt; trait — and that’s
the whole point of the trait existing in the first place.&lt;/p&gt;
&lt;p&gt;One honest caveat on that portability claim: of the interfaces
Pico de Gallo exposes, only &lt;strong&gt;I²C, SPI, GPIO, and &lt;code&gt;Delay&lt;/code&gt;&lt;/strong&gt; have
stable, widely-adopted &lt;code&gt;embedded-hal&lt;/code&gt; traits today. The TMP108
example above sails through because it’s I²C, and the alert
examples in &lt;a rel=&quot;external&quot; href=&quot;https://github.com/OpenDevicePartnership/tmp108/tree/main/examples&quot;&gt;tmp108’s &lt;code&gt;examples/&lt;/code&gt;
folder&lt;/a&gt;
sail through because they only also need GPIO. A driver for, say,
a 1-Wire temperature sensor or a UART-attached GPS module won’t be
quite as plug-and-play — &lt;code&gt;embedded-hal&lt;/code&gt; doesn’t have stable
abstractions for those yet, so each crate tends to invent its own.
The “unmodified upstream driver” story is strongest for parts
whose driver only needs I²C, SPI, GPIO, and timing. For everything
else, the host-side loop still works; you just may have to do a
bit of shimming.&lt;/p&gt;
&lt;h2 id=&quot;the-dev-loop-side-by-side&quot;&gt;The dev loop, side by side&lt;/h2&gt;
&lt;p&gt;Here’s the same edit-and-test cycle from above, redrawn against this
program:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Edit a line of code.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cargo run&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That’s it. A debug build of this program takes under a second to
recompile and start. There’s no flash step, no reset, no probe to
attach. If the program panics, the stack trace points at the line
that panicked, in source, with debug symbols, in the same terminal I
just ran it from.&lt;/p&gt;
&lt;p&gt;Other things that just work, because it’s &lt;code&gt;std&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cargo test&lt;/code&gt; runs against the real chip.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cargo bench&lt;/code&gt; measures real round-trip latency over USB.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cargo flamegraph&lt;/code&gt; and &lt;code&gt;perf&lt;/code&gt; and &lt;code&gt;dtrace&lt;/code&gt; and Instruments all
work, because it’s a normal native process.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;println!&lt;/code&gt;, &lt;code&gt;dbg!&lt;/code&gt;, &lt;code&gt;eprintln!&lt;/code&gt;, &lt;code&gt;tracing&lt;/code&gt;, &lt;code&gt;env_logger&lt;/code&gt; — any
of them. Whatever you’d use in a normal Rust program.&lt;/li&gt;
&lt;li&gt;The debugger you actually like — &lt;code&gt;rust-lldb&lt;/code&gt;, &lt;code&gt;rust-gdb&lt;/code&gt;, the
one built into your editor — attaches the way it always does.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The obvious question at this point is what the catch is. We’ll
get there.&lt;/p&gt;
&lt;h2 id=&quot;what-having-std-actually-unlocks&quot;&gt;What having &lt;code&gt;std&lt;/code&gt; actually unlocks&lt;/h2&gt;
&lt;p&gt;The temperature-printing program is the warm-up. The thing that
makes this approach worth talking about is what becomes easy &lt;em&gt;next&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Here is a small &lt;code&gt;ratatui&lt;/code&gt; dashboard that reads the TMP108 four times
a second and plots a live sparkline of the last 60 samples
alongside running min / avg / max:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://balbi.sh/posts/writing-embedded-drivers-without-an-mcu/dashboard.png&quot; alt=&quot;Live TMP108 dashboard rendered in the terminal: the top row showscurrent temperature, minimum, average, and maximum across thewindow; the bottom panel is a sparkline of the last 60 samples,making short-term drift and the warm-up curve visible at aglance.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The whole program is about 150 lines and uses three dependencies:
&lt;code&gt;pico-de-gallo-hal&lt;/code&gt;, &lt;code&gt;tmp108&lt;/code&gt;, and &lt;code&gt;ratatui&lt;/code&gt;. The interesting bits
look like this:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; HISTORY&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; usize&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 60&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; SAMPLE_INTERVAL&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Duration&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Duration&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;from_millis&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;250&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; hal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Hal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; i2c&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; hal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;i2c&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; mut&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; tmp&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Tmp108&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;new_with_a0_gnd&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;i2c&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; mut&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; samples&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; VecDeque&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;f32&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; VecDeque&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;with_capacity&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;HISTORY&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; mut&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; next_sample&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Instant&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;now&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;loop&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    if&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Instant&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;now&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; next_sample&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;        let&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; t&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; tmp&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;temperature&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        if&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; samples&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;len&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; HISTORY&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;            samples&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;pop_front&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;        samples&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;push_back&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;t&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;        next_sample&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; +=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; SAMPLE_INTERVAL&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;    terminal&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;draw&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; draw&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;frame&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;samples&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; ...event handling: quit on &amp;#39;q&amp;#39; or Esc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;draw&lt;/code&gt; function splits the screen vertically into a stats row
and a sparkline:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; draw&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;mut&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Frame&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;_&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; samples&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;VecDeque&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;f32&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;    let&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; chunks&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Layout&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;default&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        .&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;direction&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Direction&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Vertical&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        .&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;constraints&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Constraint&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;Length&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;3&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Constraint&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;Min&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        .&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;area&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;    frame&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;render_widget&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;stats&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;samples&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; chunks&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;    frame&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;render_widget&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;sparkline&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;samples&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; chunks&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;…and &lt;code&gt;sparkline&lt;/code&gt; rescales the float window into the integer bars
that &lt;code&gt;ratatui::Sparkline&lt;/code&gt; wants, so even tiny drifts are visible:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; sparkline&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;samples&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;VecDeque&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;f32&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Sparkline&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;_&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;    let&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; bars&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Vec&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;u64&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; if&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; samples&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;is_empty&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;        Vec&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;        let&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; min&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; samples&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;iter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;copied&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;fold&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;f32&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;INFINITY&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; f32&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;min&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;        let&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; max&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; samples&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;iter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;copied&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;fold&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;f32&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;NEG_INFINITY&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; f32&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;max&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;        let&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; span&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; min&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;max&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;        samples&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            .&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;iter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            .&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;|&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; min&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; /&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; span&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt; 1000&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;round&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; as&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; u64&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;            .&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;collect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    Sparkline&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;default&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        .&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;data&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;bars&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        .&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;max&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;1000&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        .&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;style&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Style&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;default&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;fg&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Yellow&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;        .&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;block&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Block&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;bordered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;title&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; last 60 samples  (q to quit) &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it. Read in a loop, push into a ring buffer, redraw. The
total code is shorter than the LCD-init sequence I’d have written
the first time I tried to do this on an MCU. And because it’s a
normal terminal program, &lt;code&gt;Ctrl-C&lt;/code&gt; cleans up and exits.&lt;/p&gt;
&lt;p&gt;Once you’re in this world, a lot of other things become “just write
the program”:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data logging.&lt;/strong&gt; A few lines of &lt;code&gt;serde&lt;/code&gt; + &lt;code&gt;csv&lt;/code&gt; and every sample
gets a wall-clock timestamp on disk. Post-process with whatever you
like — &lt;code&gt;polars&lt;/code&gt;, &lt;code&gt;pandas&lt;/code&gt;, &lt;code&gt;jq&lt;/code&gt;, a quick shell pipeline:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt; mut&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; wtr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; csv&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Writer&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;from_path&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;temps.csv&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;wtr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;write_record&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;timestamp_ms&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;temperature_c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-6 z-l-i z-d-3 z-d-i&quot;&gt; inside the loop:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;wtr&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;write_record&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;    chrono&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;Utc&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;now&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;timestamp_millis&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;to_string&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;    format!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;t:.4&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Bus tracing.&lt;/strong&gt; Wrap the driver in a &lt;code&gt;tracing&lt;/code&gt; span and you get
structured records of every transaction, every reading, every error,
with timestamps and process metadata. Pipe it into
&lt;code&gt;tracing-subscriber&lt;/code&gt;, &lt;code&gt;tokio-console&lt;/code&gt;, OpenTelemetry — pick your
poison.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stress testing.&lt;/strong&gt; Spin up a &lt;code&gt;tokio&lt;/code&gt; runtime, share the bus through
a &lt;code&gt;Mutex&amp;lt;Bus&amp;gt;&lt;/code&gt;, and have a dozen tasks all reading and configuring
the chip concurrently. See what falls over. This is something you
&lt;em&gt;could&lt;/em&gt; do on the MCU, but it’s the kind of thing you’d never set up
just to find out.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A/B testing.&lt;/strong&gt; Run two versions of the driver against the same
chip back-to-back, diff their outputs sample-for-sample. Catch a
regression in the new version before it makes it anywhere near a
production board.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real CI.&lt;/strong&gt; This is the one I’m most excited about. &lt;code&gt;cargo test&lt;/code&gt;
runs on a CI runner that has a Pico de Gallo plugged into it, with
real chips wired up. Every PR exercises the driver against real
silicon. No simulation. No “we’ll catch it in hardware testing
later.” (More on CI in the &lt;a href=&quot;/pico-de-gallo/driver/testing.html&quot;&gt;testing
chapter&lt;/a&gt; of the book.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Runnable examples in your crate.&lt;/strong&gt; This one flips the framing.
So far we’ve talked about Pico de Gallo as something &lt;em&gt;consumers&lt;/em&gt; of
a driver crate use. But it works just as well for the &lt;em&gt;author&lt;/em&gt; of
the crate. Cargo lets you put a &lt;code&gt;examples/&lt;/code&gt; folder in your library
and have each file be a binary; if those binaries can target a
real, running version of your chip without anyone reaching for a
soldering iron, they stop being “snippets you’d have to port to
your board” and start being “things a reader can &lt;code&gt;cargo run&lt;/code&gt; after
plugging in two cables.”&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://github.com/OpenDevicePartnership/tmp108/tree/main/examples&quot;&gt;&lt;code&gt;tmp108&lt;/code&gt; crate’s
examples&lt;/a&gt;
are exactly this. There are five of them — one-shot read,
continuous read, comparator-mode ALERT, interrupt-mode ALERT, and
a &lt;code&gt;embedded-sensors-hal&lt;/code&gt; trait demo — and every single one starts
with &lt;code&gt;let hal = Hal::new(); let i2c = hal.i2c();&lt;/code&gt; (the ALERT ones
also grab a GPIO for the interrupt line). A reader who wants to
understand any of the modes runs &lt;code&gt;cargo run --example oneshot&lt;/code&gt; and
gets a real temperature back from a real chip. No board-bringup
chapter. No “you’ll need to adapt this for your target.” The
documentation &lt;em&gt;is&lt;/em&gt; the running program.&lt;/p&gt;
&lt;p&gt;Each of these was technically possible before. Each one required
setting up its own bespoke pipeline, often with a custom MCU build,
a custom log format, and a custom analysis layer on top. With a real
bus available to a real &lt;code&gt;std&lt;/code&gt; program, they all collapse into “use
the crate that already exists.”&lt;/p&gt;
&lt;h2 id=&quot;the-catch&quot;&gt;The catch&lt;/h2&gt;
&lt;p&gt;One might ask, what’s the catch? Here is the honest list.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;USB latency.&lt;/strong&gt; Every transaction is a USB round-trip. In practice
that’s about a millisecond. For polling a temperature sensor every
250 ms, that’s invisible. For a tight register-poll loop at 100 kHz
trying to catch a hardware event in software — no, that does not
work, and pretending it does would be a lie. Use the chip’s &lt;code&gt;ALERT&lt;/code&gt;
pin and an interrupt-driven path for that, which Pico de Gallo also
supports via the GPIO &lt;code&gt;Wait&lt;/code&gt; impl.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Timing is host-mediated.&lt;/strong&gt; You cannot reproduce sub-millisecond
interrupt-to-handler timing on a host. You can validate the protocol
your interrupt handler implements, but not the latency of getting
&lt;em&gt;to&lt;/em&gt; the handler. For that you eventually need the real MCU.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Single master, single bus.&lt;/strong&gt; The Pico de Gallo is the only master
on the bus. You can’t reproduce a scenario where two different MCUs
are arbitrating for the same wires.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The driver still has to be &lt;code&gt;no_std&lt;/code&gt;.&lt;/strong&gt; Pico de Gallo lets you
exercise the &lt;em&gt;logic&lt;/em&gt; of the driver in a comfortable environment. It
does not validate that the driver compiles for your real target.
You still need a one-line “build for &lt;code&gt;thumbv8m.main-none-eabihf&lt;/code&gt;”
step in CI to catch accidentally pulling in &lt;code&gt;std&lt;/code&gt;. The &lt;code&gt;tmp108&lt;/code&gt;
crate has &lt;a rel=&quot;external&quot; href=&quot;https://github.com/OpenDevicePartnership/tmp108/actions/workflows/nostd.yml&quot;&gt;exactly that
job&lt;/a&gt;
running on every push for this reason.&lt;/p&gt;
&lt;p&gt;None of these are surprises. They’re the natural consequence of
moving the dev environment off the target. The point is that for the
work that &lt;em&gt;is&lt;/em&gt; portable — and a sensor driver is essentially all
portable work — you don’t have to pay the cost of the target during
development.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;A device driver is protocol logic. Protocol logic doesn’t care what
CPU is initiating the transactions. So write it where iteration is
cheap, where you have a debugger and a test runner and a profiler,
where you can wrap it in any visualization or analysis tool you
want, and where adding “log every sample to a CSV” is two lines of
code instead of two weeks of integration work.&lt;/p&gt;
&lt;p&gt;Pico de Gallo is one way to do that. It happens to be the one I
built, because nothing else existed that gave me exactly this. If
you want to read more, the &lt;a href=&quot;/pico-de-gallo/&quot;&gt;book&lt;/a&gt; covers the
hardware, the firmware, the wire protocol, and a full chapter on
writing a driver from scratch against a different temperature chip
(TMP102). The &lt;a rel=&quot;external&quot; href=&quot;https://crates.io/crates/tmp108&quot;&gt;&lt;code&gt;tmp108&lt;/code&gt; crate&lt;/a&gt; used
in this post is on crates.io, and its
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/OpenDevicePartnership/tmp108/tree/main/examples&quot;&gt;&lt;code&gt;examples/&lt;/code&gt;&lt;/a&gt;
directory is full of &lt;code&gt;cargo run --example&lt;/code&gt;-able programs that all
target a real chip through Pico de Gallo. The full ratatui dashboard
code lives in a tiny standalone project that you can copy and adapt.&lt;/p&gt;
&lt;p&gt;If you build something with it — a driver, a logger, a CI rig, a
dashboard for a chip you’ve been wanting to characterize — I’d love
to hear about it.&lt;/p&gt;
</content>
  </entry>
  
  <entry xml:lang="en">
    <title>Hello, world</title>
    <published>2026-06-10T08:00:00+00:00</published>
    <updated>2026-06-10T08:00:00+00:00</updated>
    <author><name>Felipe Balbi</name></author>
    <link rel="alternate" href="https://balbi.sh/posts/hello-world/" type="text/html"/>
    <id>https://balbi.sh/posts/hello-world/</id>
    <summary type="html">&lt;p&gt;This is the first post on the new version of &lt;a href=&quot;/&quot;&gt;balbi.sh&lt;/a&gt;. The
previous incarnation was a single HTML file with one link; this one is
built with &lt;a rel=&quot;external&quot; href=&quot;https://www.getzola.org/&quot;&gt;Zola&lt;/a&gt; and styled after
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/protesilaos/ef-themes&quot;&gt;ef-melissa-light&lt;/a&gt; — a warm,
legible Emacs theme by Protesilaos Stavrou. Body text is set in
&lt;strong&gt;Aporetic Serif&lt;/strong&gt;, headings and UI in &lt;strong&gt;Aporetic Sans&lt;/strong&gt;, and code in
&lt;strong&gt;Aporetic Sans Mono&lt;/strong&gt; — all from the same author as the colour
palette.&lt;/p&gt;</summary>
    
    <content type="html">&lt;p&gt;This is the first post on the new version of &lt;a href=&quot;/&quot;&gt;balbi.sh&lt;/a&gt;. The
previous incarnation was a single HTML file with one link; this one is
built with &lt;a rel=&quot;external&quot; href=&quot;https://www.getzola.org/&quot;&gt;Zola&lt;/a&gt; and styled after
&lt;a rel=&quot;external&quot; href=&quot;https://github.com/protesilaos/ef-themes&quot;&gt;ef-melissa-light&lt;/a&gt; — a warm,
legible Emacs theme by Protesilaos Stavrou. Body text is set in
&lt;strong&gt;Aporetic Serif&lt;/strong&gt;, headings and UI in &lt;strong&gt;Aporetic Sans&lt;/strong&gt;, and code in
&lt;strong&gt;Aporetic Sans Mono&lt;/strong&gt; — all from the same author as the colour
palette.&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;why-now&quot;&gt;Why now&lt;/h2&gt;
&lt;p&gt;I want a place to keep working notes — short writeups about the things
I run into while building firmware. The bar for publishing here is low
on purpose. If a problem took me more than an hour to figure out, it
probably deserves a post so I (or you) don’t have to figure it out
again next time.&lt;/p&gt;
&lt;h2 id=&quot;what-to-expect&quot;&gt;What to expect&lt;/h2&gt;
&lt;p&gt;Mostly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Embedded Rust&lt;/strong&gt; — Embassy, &lt;code&gt;embedded-hal&lt;/code&gt;, async on tiny chips.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bare-metal C&lt;/strong&gt; — when Rust isn’t an option, which is more often
than I’d like.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tooling&lt;/strong&gt; — probe-rs, defmt, the linker, the assembler, the things
that quietly hold everything together.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Occasional rants&lt;/strong&gt; about debugging hardware at 2am.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to follow along, there’s an &lt;a href=&quot;/atom.xml&quot;&gt;Atom feed&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;code-by-the-way&quot;&gt;Code, by the way&lt;/h2&gt;
&lt;p&gt;It looks like this:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;embassy_executor&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span&gt;task&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;async&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt; fn&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; blinky&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3 z-l-b z-d-6 z-d-b&quot;&gt;mut&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt; led&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt; Output&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;static&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;    loop&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;        led&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;toggle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-8&quot;&gt;        Timer&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-1&quot;&gt;after_millis&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-9&quot;&gt;500&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-5&quot;&gt;await&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s all for now. More soon.&lt;/p&gt;
</content>
  </entry>
  
</feed>
