<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
     xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Felipe Balbi</title>
    <link>https://balbi.sh</link>
    <description>Notes on embedded systems, Rust, and the craft of writing software.</description>
    <generator>Zola</generator>
    <language>en</language>
    <lastBuildDate>Mon, 20 Jul 2026 09:00:00 +0000</lastBuildDate>
    <atom:link href="https://balbi.sh/rss.xml" rel="self" type="application/rss+xml"/>
    
    <item>
      <title>Tamal: The CRC block</title>
      <link>https://balbi.sh/posts/tamal-crc/</link>
      <guid isPermaLink="true">https://balbi.sh/posts/tamal-crc/</guid>
      <pubDate>Mon, 20 Jul 2026 09:00:00 +0000</pubDate>
      <dc:creator>Felipe Balbi</dc:creator>
      <description>&lt;p&gt;As mentioned before &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;felipebalbi&#x2F;tamal&quot;&gt;Tamal&lt;&#x2F;a&gt; is an FPGA eSPI exerciser with its
gateware written in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.haskell.org&#x2F;&quot;&gt;Haskell&lt;&#x2F;a&gt; and compiled to verilog with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;clash-lang.org&quot;&gt;Clash&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;In a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;balbi.sh&#x2F;posts&#x2F;tamal-haskell-primer&#x2F;&quot;&gt;previous&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;</description>
      
      <content:encoded>&lt;p&gt;As mentioned before &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;felipebalbi&#x2F;tamal&quot;&gt;Tamal&lt;&#x2F;a&gt; is an FPGA eSPI exerciser with its
gateware written in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.haskell.org&#x2F;&quot;&gt;Haskell&lt;&#x2F;a&gt; and compiled to verilog with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;clash-lang.org&quot;&gt;Clash&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;In a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;balbi.sh&#x2F;posts&#x2F;tamal-haskell-primer&#x2F;&quot;&gt;previous&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;h2 id=&quot;the-entire-source&quot;&gt;The entire source&lt;&#x2F;h2&gt;
&lt;p&gt;As promised, the entire source fits in a screenfull:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;module&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt; Tamal.Crc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; crc8Update&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; where&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt; Clash.Prelude&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;crc8Update&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ::&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; BitVector&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; BitVector&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; BitVector&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;crc8Update crc byte &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; foldl step crc &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;unpack byte &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Vec&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Bit&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt; where&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;  step&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ::&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; BitVector&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Bit&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; BitVector&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  step c inBit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    |&lt;&#x2F;span&gt;&lt;span&gt; feedbackBit &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;==&lt;&#x2F;span&gt;&lt;span&gt; high &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; shifted &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;`&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;xor&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;`&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0x07&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    |&lt;&#x2F;span&gt;&lt;span&gt; otherwise &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; shifted&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;   where&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    feedbackBit &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; msb c &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;`&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;xor&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;`&lt;&#x2F;span&gt;&lt;span&gt; inBit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    shifted &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; c &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;`&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;shiftL&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;`&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The first thing we notice are the top three lines:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;module&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt; Tamal.Crc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; crc8Update&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; where&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A Haskell file opens by naming itself. &lt;code&gt;module Tamal.Crc&lt;&#x2F;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;&#x2F;code&gt; lives in
&lt;code&gt;src&#x2F;Tamal&#x2F;Crc.hs&lt;&#x2F;code&gt;, a dot for each directory separator, the same
way a Rust &lt;code&gt;crate::module&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;The parentheses are the more interesting part. They are the
module’s &lt;strong&gt;export list&lt;&#x2F;strong&gt; — the complete, deliberate enumeration of
what the outside world is allowed to see:&lt;&#x2F;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;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Only &lt;code&gt;crc8Update&lt;&#x2F;code&gt; is on that list, so only &lt;code&gt;crc8Update&lt;&#x2F;code&gt; leaves
the file. Everything else — and in a moment we will meet &lt;code&gt;step&lt;&#x2F;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;&#x2F;code&gt; with no list at all, Haskell would
export &lt;em&gt;everything&lt;&#x2F;em&gt; by default, &lt;code&gt;step&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;That choice buys more than tidiness. &lt;code&gt;step&lt;&#x2F;code&gt; is an internal detail
of how &lt;em&gt;this&lt;&#x2F;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;&#x2F;code&gt; and never reach for &lt;code&gt;step&lt;&#x2F;code&gt;:
&lt;code&gt;step&lt;&#x2F;code&gt; is not theirs to reach.)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-prelude-swap&quot;&gt;The prelude swap&lt;&#x2F;h2&gt;
&lt;p&gt;The line under the module header looks like ceremony and is
anything but:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt; Clash.Prelude&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Every Haskell file begins in a world someone else furnished. That
furniture is the &lt;em&gt;prelude&lt;&#x2F;em&gt;: the batch of names, types, and
functions that are simply &lt;em&gt;there&lt;&#x2F;em&gt; before you import anything.
Ordinary Haskell hands you the standard &lt;code&gt;Prelude&lt;&#x2F;code&gt; — &lt;code&gt;Int&lt;&#x2F;code&gt;,
&lt;code&gt;Bool&lt;&#x2F;code&gt;, lists, &lt;code&gt;foldl&lt;&#x2F;code&gt;, &lt;code&gt;map&lt;&#x2F;code&gt;, the familiar fittings.
&lt;code&gt;import Clash.Prelude&lt;&#x2F;code&gt; throws that set out and moves a different
one in.&lt;&#x2F;p&gt;
&lt;p&gt;What arrives instead is a hardware vocabulary: the &lt;code&gt;Bit&lt;&#x2F;code&gt;,
&lt;code&gt;BitVector&lt;&#x2F;code&gt;, &lt;code&gt;Vec&lt;&#x2F;code&gt;, and &lt;code&gt;Signal&lt;&#x2F;code&gt; types from the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;balbi.sh&#x2F;posts&#x2F;tamal-haskell-primer&#x2F;&quot;&gt;primer&lt;&#x2F;a&gt;, the
&lt;code&gt;high&lt;&#x2F;code&gt; and &lt;code&gt;low&lt;&#x2F;code&gt; bit literals, &lt;code&gt;msb&lt;&#x2F;code&gt;, &lt;code&gt;shiftL&lt;&#x2F;code&gt;, &lt;code&gt;xor&lt;&#x2F;code&gt;, &lt;code&gt;unpack&lt;&#x2F;code&gt; —
and versions of the everyday functions, &lt;code&gt;foldl&lt;&#x2F;code&gt; among them, that
operate on &lt;code&gt;Vec&lt;&#x2F;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;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Swapping the prelude is what turns a Haskell file into a
hardware description.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Clash leans on the swap so hard that it &lt;em&gt;shadows&lt;&#x2F;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;&#x2F;code&gt; and &lt;code&gt;++&lt;&#x2F;code&gt; would mean the
&lt;code&gt;Vec&lt;&#x2F;code&gt; versions, so gluing plain lists together has to reach for a
list comprehension and &lt;code&gt;&amp;lt;&amp;gt;&lt;&#x2F;code&gt; instead. File that away; for now the
thing to see is that the lone &lt;code&gt;foldl&lt;&#x2F;code&gt; two lines down is one of
these swapped names. It is the &lt;code&gt;Vec&lt;&#x2F;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;&#x2F;em&gt;.&lt;&#x2F;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;&#x2F;h2&gt;
&lt;p&gt;Now the signature, which under the primer’s reading is already
half the documentation:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;crc8Update&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ::&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; BitVector&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; BitVector&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; BitVector&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Two arrows, so — remembering that &lt;code&gt;-&amp;gt;&lt;&#x2F;code&gt; associates to the right and
every function takes one argument — this is “give me a
&lt;code&gt;BitVector 8&lt;&#x2F;code&gt;, then another &lt;code&gt;BitVector 8&lt;&#x2F;code&gt;, and I hand back a
&lt;code&gt;BitVector 8&lt;&#x2F;code&gt;.” Name the three roles and the intent appears: the
first argument is the &lt;strong&gt;running CRC&lt;&#x2F;strong&gt;, the second is the
&lt;strong&gt;incoming byte&lt;&#x2F;strong&gt;, and the result is the &lt;strong&gt;new running CRC&lt;&#x2F;strong&gt;. It
is an accumulator update, one byte at a time.&lt;&#x2F;p&gt;
&lt;p&gt;The type could have been written other ways, and the two decisions
behind it are both load-bearing.&lt;&#x2F;p&gt;
&lt;p&gt;The first is &lt;code&gt;BitVector 8&lt;&#x2F;code&gt; rather than &lt;code&gt;Unsigned 8&lt;&#x2F;code&gt; or a &lt;code&gt;Word&lt;&#x2F;code&gt;.
A &lt;code&gt;BitVector 8&lt;&#x2F;code&gt; is eight wires with &lt;em&gt;no&lt;&#x2F;em&gt; arithmetic meaning
attached — the type of a thing you &lt;code&gt;xor&lt;&#x2F;code&gt;, shift, and index into,
not a thing you add. &lt;code&gt;Unsigned 8&lt;&#x2F;code&gt; would have invited &lt;code&gt;+&lt;&#x2F;code&gt;, &lt;code&gt;*&lt;&#x2F;code&gt;, and
carry, arithmetic this function never performs. Choosing
&lt;code&gt;BitVector&lt;&#x2F;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;&#x2F;code&gt; on a &lt;code&gt;BitVector&lt;&#x2F;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;&#x2F;em&gt; of value, not just its width, is part of the
contract.&lt;&#x2F;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;&#x2F;strong&gt;.
Recall currying — &lt;code&gt;crc8Update crc&lt;&#x2F;code&gt; is a legal value on its own, a
function still waiting for a byte, meaning “advance &lt;em&gt;this&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;One last thing the type says by saying nothing. Every &lt;code&gt;BitVector 8&lt;&#x2F;code&gt;
in, produces a definite &lt;code&gt;BitVector 8&lt;&#x2F;code&gt; out — no missing cases, no
error, no &lt;code&gt;Maybe&lt;&#x2F;code&gt;. That totality &lt;em&gt;is&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;a-crc-is-a-remainder&quot;&gt;A CRC is a remainder&lt;&#x2F;h2&gt;
&lt;p&gt;Strip the acronym away and a CRC is a &lt;strong&gt;remainder&lt;&#x2F;strong&gt; — the leftover
from a division, carried out in a number system with exactly two
digits.&lt;&#x2F;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;&#x2F;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;&#x2F;strong&gt;.
That single fact is why CRC hardware is cheap — every
“subtraction” below is just an &lt;code&gt;xor&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The divisor in this division has a name, the &lt;strong&gt;generator
polynomial&lt;&#x2F;strong&gt;. Tamal’s is the one the module comment spells out,&lt;&#x2F;p&gt;
&lt;p&gt;$$ G(x) = x^8 + x^2 + x + 1 $$&lt;&#x2F;p&gt;
&lt;p&gt;whose coefficients, written as bits, are &lt;code&gt;1 0000 0111&lt;&#x2F;code&gt; — nine bits,
&lt;code&gt;0x107&lt;&#x2F;code&gt;, degree eight. Look at the low eight of those bits: &lt;code&gt;0000 0111&lt;&#x2F;code&gt;, &lt;code&gt;0x07&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;To take the CRC of a message $M$ is to do one thing:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Shift the message up by eight bits, divide by $G$, and keep the
remainder.&lt;&#x2F;p&gt;
&lt;&#x2F;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;&#x2F;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;&#x2F;code&gt;, which as a polynomial is simply $1$. Shift up by
$x^8$ and reduce modulo $G$:&lt;&#x2F;p&gt;
&lt;p&gt;$$ x^8 \equiv x^2 + x + 1 \pmod{G} $$&lt;&#x2F;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;&#x2F;code&gt;,
which is &lt;code&gt;0x07&lt;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;the-left-fold&quot;&gt;The left fold&lt;&#x2F;h2&gt;
&lt;p&gt;With the division in mind, the body is short:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-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;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; foldl step crc &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;unpack byte &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Vec&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Bit&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Two moves live on that line: turn the byte into bits, then fold
over them.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;unpack byte :: Vec 8 Bit&lt;&#x2F;code&gt; takes the opaque eight-wire &lt;code&gt;BitVector&lt;&#x2F;code&gt;
and re-presents it as a &lt;code&gt;Vec 8 Bit&lt;&#x2F;code&gt;, a sequence of eight
individual &lt;code&gt;Bit&lt;&#x2F;code&gt;s you can walk one at a time. The annotation &lt;code&gt;:: Vec 8 Bit&lt;&#x2F;code&gt; is needed because &lt;code&gt;unpack&lt;&#x2F;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;&#x2F;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;&#x2F;strong&gt;, which is
what this CRC (and eSPI, and SMBus) require.&lt;&#x2F;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;&#x2F;a&gt;&lt;&#x2F;sup&gt;
is the most ordinary thing in the world, and it has nothing to do
with mutation:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;foldl&lt;&#x2F;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;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Written out, &lt;code&gt;foldl step crc [b0, b1, .. b7]&lt;&#x2F;code&gt; is nothing but a
nest of calls:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-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;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;crc&lt;&#x2F;code&gt; seeds the accumulator. &lt;code&gt;step&lt;&#x2F;code&gt; folds in &lt;code&gt;b0&lt;&#x2F;code&gt; to produce a
&lt;em&gt;new&lt;&#x2F;em&gt; accumulator; that value feeds into &lt;code&gt;step&lt;&#x2F;code&gt; alongside &lt;code&gt;b1&lt;&#x2F;code&gt; to
produce the next; and so on down the byte until &lt;code&gt;b7&lt;&#x2F;code&gt; yields the
result. Nothing is ever overwritten. Each &lt;code&gt;step&lt;&#x2F;code&gt; &lt;strong&gt;returns&lt;&#x2F;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;&#x2F;a&gt;&lt;&#x2F;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;&#x2F;em&gt;, it is
&lt;em&gt;replaced&lt;&#x2F;em&gt;, eight times, by eight applications of one pure function.&lt;&#x2F;p&gt;
&lt;p&gt;And because the vector has a length the compiler knows — &lt;code&gt;8&lt;&#x2F;code&gt;, right
there in &lt;code&gt;Vec 8 Bit&lt;&#x2F;code&gt; — the hardware reading is the one the primer
promised:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;A fold over a fixed-size &lt;code&gt;Vec&lt;&#x2F;code&gt; is a pipeline unrolled in space,
not a loop unrolled in time.&lt;&#x2F;p&gt;
&lt;&#x2F;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;&#x2F;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;&#x2F;strong&gt;, the textbook circuit
for a bit-serial CRC — except Clash &lt;em&gt;derived&lt;&#x2F;em&gt; it from a &lt;code&gt;foldl&lt;&#x2F;code&gt;
instead of asking anyone to draw it. The accumulator is the
register’s contents, &lt;code&gt;step&lt;&#x2F;code&gt; is the tap logic, and the fold is the
wiring between the stages.&lt;&#x2F;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:&#x2F;&#x2F;www.w3.org&#x2F;2000&#x2F;svg&quot;&gt;
&lt;title id=&quot;lfsr-t&quot;&gt;CRC-8 Galois shift register for polynomial 0x07&lt;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;&gt;&lt;&#x2F;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;&#x2F;&gt;&lt;&#x2F;marker&gt;
&lt;&#x2F;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;&#x2F;&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;&#x2F;&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;&#x2F;&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;&#x2F;&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;&#x2F;&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;&#x2F;&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;&#x2F;&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;&#x2F;&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;&#x2F;&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;&#x2F;&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;&#x2F;&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;&#x2F;&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;&#x2F;&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;&#x2F;&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;&#x2F;&gt;
&lt;text class=&quot;bit&quot; x=&quot;72&quot; y=&quot;150&quot; text-anchor=&quot;middle&quot;&gt;b7&lt;&#x2F;text&gt;
&lt;text class=&quot;bit&quot; x=&quot;160&quot; y=&quot;150&quot; text-anchor=&quot;middle&quot;&gt;b6&lt;&#x2F;text&gt;
&lt;text class=&quot;bit&quot; x=&quot;248&quot; y=&quot;150&quot; text-anchor=&quot;middle&quot;&gt;b5&lt;&#x2F;text&gt;
&lt;text class=&quot;bit&quot; x=&quot;336&quot; y=&quot;150&quot; text-anchor=&quot;middle&quot;&gt;b4&lt;&#x2F;text&gt;
&lt;text class=&quot;bit&quot; x=&quot;424&quot; y=&quot;150&quot; text-anchor=&quot;middle&quot;&gt;b3&lt;&#x2F;text&gt;
&lt;text class=&quot;bit&quot; x=&quot;512&quot; y=&quot;150&quot; text-anchor=&quot;middle&quot;&gt;b2&lt;&#x2F;text&gt;
&lt;text class=&quot;bit&quot; x=&quot;600&quot; y=&quot;150&quot; text-anchor=&quot;middle&quot;&gt;b1&lt;&#x2F;text&gt;
&lt;text class=&quot;bit&quot; x=&quot;688&quot; y=&quot;150&quot; text-anchor=&quot;middle&quot;&gt;b0&lt;&#x2F;text&gt;
&lt;text class=&quot;idx&quot; x=&quot;72&quot; y=&quot;190&quot; text-anchor=&quot;middle&quot;&gt;MSB&lt;&#x2F;text&gt;
&lt;text class=&quot;idx&quot; x=&quot;688&quot; y=&quot;190&quot; text-anchor=&quot;middle&quot;&gt;LSB&lt;&#x2F;text&gt;
&lt;line class=&quot;fb&quot; x1=&quot;72&quot; y1=&quot;116&quot; x2=&quot;72&quot; y2=&quot;67&quot;&#x2F;&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;&#x2F;&gt;
&lt;line class=&quot;fb&quot; x1=&quot;83&quot; y1=&quot;56&quot; x2=&quot;688&quot; y2=&quot;56&quot;&#x2F;&gt;
&lt;line class=&quot;fb&quot; x1=&quot;556&quot; y1=&quot;56&quot; x2=&quot;556&quot; y2=&quot;133&quot;&#x2F;&gt;
&lt;line class=&quot;fb&quot; x1=&quot;644&quot; y1=&quot;56&quot; x2=&quot;644&quot; y2=&quot;133&quot;&#x2F;&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;&#x2F;&gt;
&lt;circle class=&quot;node&quot; cx=&quot;556&quot; cy=&quot;56&quot; r=&quot;3.5&quot;&#x2F;&gt;
&lt;circle class=&quot;node&quot; cx=&quot;644&quot; cy=&quot;56&quot; r=&quot;3.5&quot;&#x2F;&gt;
&lt;circle class=&quot;gate&quot; cx=&quot;72&quot; cy=&quot;56&quot; r=&quot;11&quot;&#x2F;&gt;
&lt;line class=&quot;plus&quot; x1=&quot;66&quot; y1=&quot;56&quot; x2=&quot;78&quot; y2=&quot;56&quot;&#x2F;&gt;
&lt;line class=&quot;plus&quot; x1=&quot;72&quot; y1=&quot;50&quot; x2=&quot;72&quot; y2=&quot;62&quot;&#x2F;&gt;
&lt;circle class=&quot;gate&quot; cx=&quot;556&quot; cy=&quot;144&quot; r=&quot;11&quot;&#x2F;&gt;
&lt;line class=&quot;plus&quot; x1=&quot;550&quot; y1=&quot;144&quot; x2=&quot;562&quot; y2=&quot;144&quot;&#x2F;&gt;
&lt;line class=&quot;plus&quot; x1=&quot;556&quot; y1=&quot;138&quot; x2=&quot;556&quot; y2=&quot;150&quot;&#x2F;&gt;
&lt;circle class=&quot;gate&quot; cx=&quot;644&quot; cy=&quot;144&quot; r=&quot;11&quot;&#x2F;&gt;
&lt;line class=&quot;plus&quot; x1=&quot;638&quot; y1=&quot;144&quot; x2=&quot;650&quot; y2=&quot;144&quot;&#x2F;&gt;
&lt;line class=&quot;plus&quot; x1=&quot;644&quot; y1=&quot;138&quot; x2=&quot;644&quot; y2=&quot;150&quot;&#x2F;&gt;
&lt;text class=&quot;lead&quot; x=&quot;14&quot; y=&quot;46&quot;&gt;message bit&lt;&#x2F;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;&#x2F;text&gt;
&lt;text class=&quot;tap&quot; x=&quot;562&quot; y=&quot;80&quot;&gt;x²&lt;&#x2F;text&gt;
&lt;text class=&quot;tap&quot; x=&quot;650&quot; y=&quot;80&quot;&gt;x¹&lt;&#x2F;text&gt;
&lt;text class=&quot;tap&quot; x=&quot;694&quot; y=&quot;80&quot;&gt;x⁰&lt;&#x2F;text&gt;
&lt;&#x2F;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;&#x2F;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;&#x2F;code&gt;, &lt;code&gt;x¹&lt;&#x2F;code&gt; and &lt;code&gt;x⁰&lt;&#x2F;code&gt; taps — the set bits of &lt;code&gt;0x07&lt;&#x2F;code&gt;. &lt;code&gt;crc8Update&lt;&#x2F;code&gt; &lt;em&gt;is&lt;&#x2F;em&gt; this circuit with the flip-flops replaced by wires: Clash unrolls the &lt;code&gt;foldl&lt;&#x2F;code&gt; into eight combinational copies, one per bit, so a whole byte is absorbed in a single clock.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;h2 id=&quot;step-one-bit-at-a-time&quot;&gt;&lt;code&gt;step&lt;&#x2F;code&gt;: one bit at a time&lt;&#x2F;h2&gt;
&lt;p&gt;Which leaves the heart of the module, the private helper the
export list keeps to itself:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;step&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ::&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; BitVector&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Bit&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; BitVector&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;step c inBit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  |&lt;&#x2F;span&gt;&lt;span&gt; feedbackBit &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;==&lt;&#x2F;span&gt;&lt;span&gt; high &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; shifted &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;`&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;xor&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;`&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0x07&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  |&lt;&#x2F;span&gt;&lt;span&gt; otherwise &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; shifted&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt; where&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  feedbackBit &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; msb c &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;`&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;xor&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;`&lt;&#x2F;span&gt;&lt;span&gt; inBit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  shifted &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; c &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;`&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;shiftL&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;`&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The type tells the whole story of what &lt;code&gt;step&lt;&#x2F;code&gt; is &lt;em&gt;for&lt;&#x2F;em&gt;. It takes a
running CRC and a &lt;strong&gt;single&lt;&#x2F;strong&gt; &lt;code&gt;Bit&lt;&#x2F;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;&#x2F;code&gt; invoking this
function eight times per byte. Read &lt;code&gt;crc8Update&lt;&#x2F;code&gt; and &lt;code&gt;step&lt;&#x2F;code&gt;
together and the division of labour is clean: &lt;code&gt;step&lt;&#x2F;code&gt; knows how to
absorb one bit, and the fold knows how to do it eight times.&lt;&#x2F;p&gt;
&lt;p&gt;Take the two &lt;code&gt;where&lt;&#x2F;code&gt; bindings first, since the guards are written
in terms of them.&lt;&#x2F;p&gt;
&lt;p&gt;The binding &lt;code&gt;shifted = c `shiftL` 1&lt;&#x2F;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;&#x2F;strong&gt;. The bit that just dropped off is the
coefficient that &lt;em&gt;would have&lt;&#x2F;em&gt; become the $x^8$ term — the exact
term $G$ exists to cancel.&lt;&#x2F;p&gt;
&lt;p&gt;The other binding, &lt;code&gt;feedbackBit = msb c `xor` inBit&lt;&#x2F;code&gt;, takes the
CRC’s current top bit (&lt;code&gt;msb c&lt;&#x2F;code&gt; reads out the most significant
&lt;code&gt;Bit&lt;&#x2F;code&gt;) and XORs in the incoming message bit. This is the point
where the message actually enters the division.&lt;&#x2F;p&gt;
&lt;p&gt;Now the guards. The &lt;code&gt;|&lt;&#x2F;code&gt; is a &lt;strong&gt;guard&lt;&#x2F;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;&#x2F;code&gt; is the catch-all that is always true. So the
function reads: if &lt;code&gt;feedbackBit == high&lt;&#x2F;code&gt; — &lt;code&gt;high&lt;&#x2F;code&gt; being the &lt;code&gt;Bit&lt;&#x2F;code&gt;
whose value is one — the answer is &lt;code&gt;shifted `xor` 0x07&lt;&#x2F;code&gt;;
otherwise the answer is just &lt;code&gt;shifted&lt;&#x2F;code&gt;.&lt;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;code&gt;. When &lt;code&gt;feedbackBit&lt;&#x2F;code&gt; is zero there was no overflow and there
is nothing to subtract. That is the complete rule the five lines
encode:&lt;&#x2F;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;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;We can watch it produce the very number the algebra promised. Fold
&lt;code&gt;step&lt;&#x2F;code&gt; from &lt;code&gt;crc = 0&lt;&#x2F;code&gt; across the byte &lt;code&gt;0x01&lt;&#x2F;code&gt;, whose bits MSB-first
are seven zeros and then a one:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;On each of the first seven bits, &lt;code&gt;msb c&lt;&#x2F;code&gt; is &lt;code&gt;0&lt;&#x2F;code&gt; and &lt;code&gt;inBit&lt;&#x2F;code&gt; is
&lt;code&gt;0&lt;&#x2F;code&gt;, so &lt;code&gt;feedbackBit&lt;&#x2F;code&gt; is &lt;code&gt;0&lt;&#x2F;code&gt;; &lt;code&gt;shifted&lt;&#x2F;code&gt; is &lt;code&gt;0&lt;&#x2F;code&gt; shifted, still
&lt;code&gt;0&lt;&#x2F;code&gt;. The accumulator never leaves zero.&lt;&#x2F;li&gt;
&lt;li&gt;On the final bit, &lt;code&gt;inBit&lt;&#x2F;code&gt; is &lt;code&gt;1&lt;&#x2F;code&gt; while &lt;code&gt;msb c&lt;&#x2F;code&gt; is still &lt;code&gt;0&lt;&#x2F;code&gt;, so
&lt;code&gt;feedbackBit&lt;&#x2F;code&gt; is &lt;code&gt;0 xor 1 = 1&lt;&#x2F;code&gt;. &lt;code&gt;shifted&lt;&#x2F;code&gt; is
&lt;code&gt;0 `shiftL` 1 = 0&lt;&#x2F;code&gt;, the guard fires, and the result is
&lt;code&gt;0 xor 0x07 = 0x07&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The fold ends at &lt;code&gt;0x07&lt;&#x2F;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;&#x2F;code&gt; across a whole message,
seeding from &lt;code&gt;0&lt;&#x2F;code&gt; and feeding each result back as the next &lt;code&gt;crc&lt;&#x2F;code&gt;,
and you have divided the entire message by $G$, one byte at a
time.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-tests&quot;&gt;The tests&lt;&#x2F;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;&#x2F;em&gt;, from more than
one direction. The tests come in two flavours that answer two
different questions.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;crc8&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ::&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt;BitVector&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; BitVector&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;crc8 &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt; L.&lt;&#x2F;span&gt;&lt;span&gt;foldl&amp;#39; crc8Update &lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;tests &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  testGroup &lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Crc&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    [&lt;&#x2F;span&gt;&lt;span&gt; testCase &lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;crc8Update 0 0x01 == 0x07&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; $&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        crc8Update &lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0x01&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; @?=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0x07&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;&#x2F;span&gt;&lt;span&gt; testCase &lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;CRC-8&#x2F;SMBUS check &lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;123456789&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; == 0xF4&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; $&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        crc8 &lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;fromIntegral &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;fromEnum c&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; |&lt;&#x2F;span&gt;&lt;span&gt; c &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;lt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;123456789&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; @?=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0xF4&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ,&lt;&#x2F;span&gt;&lt;span&gt; testProperty &lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;residue law&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; $&lt;&#x2F;span&gt;&lt;span&gt; property &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        msg &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;lt;-&lt;&#x2F;span&gt;&lt;span&gt; forAll &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;Gen.&lt;&#x2F;span&gt;&lt;span&gt;list &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;Range.&lt;&#x2F;span&gt;&lt;span&gt;linear &lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 32&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; genByte&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        crc8 &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;msg &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;lt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;crc8 msg&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ===&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;First the helper. &lt;code&gt;crc8Update&lt;&#x2F;code&gt; works one byte at a time, but a
message is a &lt;em&gt;list&lt;&#x2F;em&gt; of bytes, so the tests define &lt;code&gt;crc8 = L.foldl&#x27; crc8Update 0&lt;&#x2F;code&gt; — fold the per-byte update across the whole list,
seeding from the initial value &lt;code&gt;0&lt;&#x2F;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;&#x2F;em&gt; into a byte’s CRC, the test folds &lt;em&gt;bytes&lt;&#x2F;em&gt; into a message’s
CRC. Same &lt;code&gt;foldl&lt;&#x2F;code&gt; idea, twice, at two scales.&lt;&#x2F;p&gt;
&lt;p&gt;A small Clash wrinkle hides in that &lt;code&gt;L.&lt;&#x2F;code&gt;. Because &lt;code&gt;import Clash.Prelude&lt;&#x2F;code&gt; shadowed the list functions with &lt;code&gt;Vec&lt;&#x2F;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;&#x2F;code&gt; under the qualifier &lt;code&gt;L&lt;&#x2F;code&gt;, hence &lt;code&gt;L.foldl&#x27;&lt;&#x2F;code&gt;. The
note at the bottom of the real test file is flagging the same shadow
for &lt;code&gt;map&lt;&#x2F;code&gt; and &lt;code&gt;++&lt;&#x2F;code&gt;, which is why the message is stitched together
with a list comprehension and &lt;code&gt;&amp;lt;&amp;gt;&lt;&#x2F;code&gt; rather than the names you might
expect. That one import casts its shadow all the way into the
tests.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-oracle-tests&quot;&gt;The oracle tests&lt;&#x2F;h3&gt;
&lt;p&gt;The first two entries are &lt;strong&gt;HUnit&lt;&#x2F;strong&gt; cases — plain, hand-checked
assertions of the shape “this exact input yields this exact
output,” where &lt;code&gt;@?=&lt;&#x2F;code&gt; reads “the actual value should equal the
expected one.”&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;crc8Update 0 0x01 @?= 0x07&lt;&#x2F;code&gt; is the smallest witness in the whole
project. We have now derived it &lt;em&gt;twice&lt;&#x2F;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;&#x2F;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;&#x2F;strong&gt;. &lt;code&gt;CRC-8&#x2F;SMBUS&lt;&#x2F;code&gt; is a catalogued CRC, and every
catalogued CRC ships with a standard &lt;em&gt;check value&lt;&#x2F;em&gt;: the CRC of the
nine ASCII bytes &lt;code&gt;&quot;123456789&quot;&lt;&#x2F;code&gt;. For this polynomial and these
parameters that value is &lt;code&gt;0xF4&lt;&#x2F;code&gt;, the same &lt;code&gt;0xF4&lt;&#x2F;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;&#x2F;code&gt;, then it is computing the CRC it &lt;em&gt;claims&lt;&#x2F;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;&#x2F;strong&gt;: known questions with known answers, one derived
in-house, one borrowed from the standard.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-property-test&quot;&gt;The property test&lt;&#x2F;h3&gt;
&lt;p&gt;The third entry is a different animal. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;hedgehog.qa&quot;&gt;Hedgehog&lt;&#x2F;a&gt; is a
property-based testing library: instead of one example, you state a
&lt;em&gt;law&lt;&#x2F;em&gt; that must hold for &lt;em&gt;every&lt;&#x2F;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;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;crc8 &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;msg &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;lt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;crc8 msg&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ===&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Read it aloud: take any message &lt;code&gt;msg&lt;&#x2F;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;&#x2F;code&gt;, is Hedgehog drawing &lt;code&gt;msg&lt;&#x2F;code&gt;
as a random list of up to thirty-two random bytes, with &lt;code&gt;genByte&lt;&#x2F;code&gt;
(from the shared &lt;code&gt;Test.Gen&lt;&#x2F;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;&#x2F;em&gt; fail, Hedgehog does the thing that earns its
keep: it &lt;strong&gt;shrinks&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;Why is this the &lt;em&gt;right&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;The practical reason is that it is exactly how Tamal &lt;em&gt;uses&lt;&#x2F;em&gt; this
CRC on the bus. The &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;balbi.sh&#x2F;posts&#x2F;tamal-introducing&#x2F;&quot;&gt;introduction post&lt;&#x2F;a&gt;’s
&lt;code&gt;peripheral_io_read.s&lt;&#x2F;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;&#x2F;code&gt; followed by &lt;code&gt;bnez t2, bad_crc&lt;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;em&gt; $C$ and &lt;em&gt;subtracting off the remainder&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;what-we-read&quot;&gt;What we read&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;balbi.sh&#x2F;posts&#x2F;tamal-haskell-primer&#x2F;&quot;&gt;primer&lt;&#x2F;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;&#x2F;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;&#x2F;code&gt; stops being a
footnote and the &lt;code&gt;mealy&lt;&#x2F;code&gt; from the primer finally earns its perch at
the top of a module.&lt;&#x2F;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;&#x2F;strong&gt; — also called &lt;code&gt;reduce&lt;&#x2F;code&gt;, &lt;code&gt;accumulate&lt;&#x2F;code&gt;, &lt;code&gt;inject&lt;&#x2F;code&gt;, or
&lt;code&gt;aggregate&lt;&#x2F;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;&#x2F;em&gt; fold (&lt;code&gt;foldl&lt;&#x2F;code&gt;) brackets from the
left and walks front to back, a &lt;em&gt;right&lt;&#x2F;em&gt; fold (&lt;code&gt;foldr&lt;&#x2F;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;&#x2F;code&gt;, &lt;code&gt;filter&lt;&#x2F;code&gt;, &lt;code&gt;scan&lt;&#x2F;code&gt;, and &lt;code&gt;zip&lt;&#x2F;code&gt; in a shared
vocabulary of combinators; the &lt;em&gt;array&lt;&#x2F;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;&#x2F;code&gt;, C++’s &lt;code&gt;std::accumulate&lt;&#x2F;code&gt;, Ruby’s
&lt;code&gt;inject&lt;&#x2F;code&gt;, JavaScript’s &lt;code&gt;reduce&lt;&#x2F;code&gt;, C#’s LINQ &lt;code&gt;Aggregate&lt;&#x2F;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;&#x2F;code&gt;
(and &lt;code&gt;reduce&lt;&#x2F;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;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li id=&quot;fn-returns&quot;&gt;
&lt;p&gt;Technically Haskell functions never &lt;strong&gt;return&lt;&#x2F;strong&gt;, rather
they &lt;strong&gt;evaluate to&lt;&#x2F;strong&gt; a value. &lt;a href=&quot;#fr-returns-1&quot;&gt;↩&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;section&gt;
</content:encoded>
    </item>
    
    <item>
      <title>Enough Haskell to Read Tamal</title>
      <link>https://balbi.sh/posts/tamal-haskell-primer/</link>
      <guid isPermaLink="true">https://balbi.sh/posts/tamal-haskell-primer/</guid>
      <pubDate>Sun, 19 Jul 2026 09:00:00 +0000</pubDate>
      <dc:creator>Felipe Balbi</dc:creator>
      <description>&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;felipebalbi&#x2F;tamal&quot;&gt;Tamal&lt;&#x2F;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:&#x2F;&#x2F;clash-lang.org&quot;&gt;Clash&lt;&#x2F;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;&#x2F;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;&#x2F;code&gt;, &lt;code&gt;-&amp;gt;&lt;&#x2F;code&gt;, &lt;code&gt;\case&lt;&#x2F;code&gt;,
&lt;code&gt;deriving stock&lt;&#x2F;code&gt;, &lt;code&gt;BitVector 32&lt;&#x2F;code&gt;, a lone &lt;code&gt;mealy&lt;&#x2F;code&gt; perched atop a module
like an incantation. None of it is &lt;em&gt;hard&lt;&#x2F;em&gt;. All of it is unfamiliar,
and unfamiliar is enough to stop you at the door.&lt;&#x2F;p&gt;
&lt;p&gt;So before the series spends a whole post on the &lt;code&gt;Uart&lt;&#x2F;code&gt; or the
&lt;code&gt;Engine&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;Two honest caveats about scope. This is &lt;strong&gt;not&lt;&#x2F;strong&gt; a Haskell course —
there are excellent ones, and I’m not writing another — and it
deliberately steers &lt;em&gt;around&lt;&#x2F;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;&#x2F;code&gt; block, a &lt;code&gt;case&lt;&#x2F;code&gt;, and
the &lt;code&gt;mealy&lt;&#x2F;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;&#x2F;em&gt; it. That’s the whole job.&lt;&#x2F;p&gt;</description>
      
      <content:encoded>&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;felipebalbi&#x2F;tamal&quot;&gt;Tamal&lt;&#x2F;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:&#x2F;&#x2F;clash-lang.org&quot;&gt;Clash&lt;&#x2F;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;&#x2F;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;&#x2F;code&gt;, &lt;code&gt;-&amp;gt;&lt;&#x2F;code&gt;, &lt;code&gt;\case&lt;&#x2F;code&gt;,
&lt;code&gt;deriving stock&lt;&#x2F;code&gt;, &lt;code&gt;BitVector 32&lt;&#x2F;code&gt;, a lone &lt;code&gt;mealy&lt;&#x2F;code&gt; perched atop a module
like an incantation. None of it is &lt;em&gt;hard&lt;&#x2F;em&gt;. All of it is unfamiliar,
and unfamiliar is enough to stop you at the door.&lt;&#x2F;p&gt;
&lt;p&gt;So before the series spends a whole post on the &lt;code&gt;Uart&lt;&#x2F;code&gt; or the
&lt;code&gt;Engine&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;Two honest caveats about scope. This is &lt;strong&gt;not&lt;&#x2F;strong&gt; a Haskell course —
there are excellent ones, and I’m not writing another — and it
deliberately steers &lt;em&gt;around&lt;&#x2F;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;&#x2F;code&gt; block, a &lt;code&gt;case&lt;&#x2F;code&gt;, and
the &lt;code&gt;mealy&lt;&#x2F;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;&#x2F;em&gt; it. That’s the whole job.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;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;&#x2F;code&gt; is just &lt;code&gt;f : A → B&lt;&#x2F;code&gt;&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;Haskell writes the exact same statement as &lt;code&gt;f :: A -&amp;gt; B&lt;&#x2F;code&gt;. The &lt;code&gt;-&amp;gt;&lt;&#x2F;code&gt; is
the arrow. The only oddity is the doubled colon: &lt;code&gt;::&lt;&#x2F;code&gt; reads “has
type.” A single &lt;code&gt;:&lt;&#x2F;code&gt; would have been the obvious choice, but it was
already spoken for — in Haskell &lt;code&gt;:&lt;&#x2F;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;&#x2F;code&gt; as “&lt;code&gt;f&lt;&#x2F;code&gt; has
type $A \to B$,” and you are reading the mathematics.&lt;&#x2F;p&gt;
&lt;p&gt;Here is the smallest honest example:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;double&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ::&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Int&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Int&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;double n &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; n &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The first line is the type; the second the definition. And I do mean
&lt;em&gt;mapping&lt;&#x2F;em&gt;, not &lt;em&gt;procedure&lt;&#x2F;em&gt;. &lt;code&gt;double&lt;&#x2F;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;&#x2F;code&gt; with another, no more alive than the
function $x \mapsto 2x$ on a chalkboard. Feed it &lt;code&gt;21&lt;&#x2F;code&gt; and you get &lt;code&gt;42&lt;&#x2F;code&gt;,
today, tomorrow, on every core, with nothing else happening anywhere.
That last clause has a name: &lt;strong&gt;purity&lt;&#x2F;strong&gt;. Same input, same output, no
side effects, no hidden state read or written.&lt;&#x2F;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;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;A block of combinational logic is a pure function of its inputs.&lt;&#x2F;p&gt;
&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;a-quick-tour-of-categories&quot;&gt;A quick tour of categories&lt;&#x2F;h2&gt;
&lt;p&gt;Once functions are arrows, the natural question is: what do you
actually &lt;em&gt;do&lt;&#x2F;em&gt; with them? Strip it down and there are exactly two
moves.&lt;&#x2F;p&gt;
&lt;p&gt;The first is &lt;strong&gt;composition&lt;&#x2F;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;&#x2F;code&gt;, a
single dot:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;--&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; (.) :: (b -&amp;gt; c) -&amp;gt; (a -&amp;gt; b) -&amp;gt; (a -&amp;gt; c)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;pipeline &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; decode &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt; validate &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt; receive&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Read that right to left: &lt;code&gt;receive&lt;&#x2F;code&gt;, then &lt;code&gt;validate&lt;&#x2F;code&gt;, then &lt;code&gt;decode&lt;&#x2F;code&gt;.
The comment is just the arrow-matching rule spelled out — give &lt;code&gt;.&lt;&#x2F;code&gt; an
arrow &lt;code&gt;b -&amp;gt; c&lt;&#x2F;code&gt; and an arrow &lt;code&gt;a -&amp;gt; b&lt;&#x2F;code&gt;, and it hands back the composite
&lt;code&gt;a -&amp;gt; c&lt;&#x2F;code&gt;. If you have ever built a datapath as a chain of stages, each
feeding the next, you have built exactly this.&lt;&#x2F;p&gt;
&lt;p&gt;The second move is humbler still: the &lt;strong&gt;identity&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;Those two ingredients, plus two laws, are the entire definition of a
&lt;strong&gt;category&lt;&#x2F;strong&gt;: some &lt;em&gt;objects&lt;&#x2F;em&gt;, &lt;em&gt;arrows&lt;&#x2F;em&gt; between them, a way to
&lt;em&gt;compose&lt;&#x2F;em&gt; arrows whose ends line up, and an &lt;em&gt;identity&lt;&#x2F;em&gt; arrow on every
object, subject to&lt;&#x2F;p&gt;
&lt;p&gt;$$ f \circ \mathrm{id} = f = \mathrm{id} \circ f $$&lt;&#x2F;p&gt;
&lt;p&gt;$$ (h \circ g) \circ f = h \circ (g \circ f) $$&lt;&#x2F;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;&#x2F;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;&#x2F;strong&gt;. Haskell types
with functions form another. So it can &lt;em&gt;sound&lt;&#x2F;em&gt; like categories are
just “sets, restated.” They are not.&lt;&#x2F;p&gt;
&lt;p&gt;Here is a category with no sets and no functions in sight. Take any
&lt;strong&gt;preorder&lt;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;em&gt;, that one thing sits below
another.&lt;&#x2F;p&gt;
&lt;p&gt;That is the honest version of the “superset” story. Category theory
does not &lt;em&gt;contain&lt;&#x2F;em&gt; set theory — &lt;strong&gt;Set is one example among many.&lt;&#x2F;strong&gt; It
steps back from &lt;em&gt;what the objects are made of&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;You need none of this to type a &lt;code&gt;.&lt;&#x2F;code&gt; into a Haskell file. But it buys
the right mental model for the rest of this post:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Composition is the primary verb.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;A type signature full of &lt;code&gt;-&amp;gt;&lt;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;arrows-in-a-row-currying&quot;&gt;Arrows in a row: currying&lt;&#x2F;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;&#x2F;em&gt; arrows?&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;add&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ::&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Int&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Int&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Int&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;add x y &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; x &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; y&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;addFive&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ::&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Int&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Int&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;addFive &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; add &lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;5&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The resolution is a single rule: &lt;code&gt;-&amp;gt;&lt;&#x2F;code&gt; associates to the &lt;strong&gt;right&lt;&#x2F;strong&gt;. So
&lt;code&gt;Int -&amp;gt; Int -&amp;gt; Int&lt;&#x2F;code&gt; secretly means &lt;code&gt;Int -&amp;gt; (Int -&amp;gt; Int)&lt;&#x2F;code&gt; — “give me an
&lt;code&gt;Int&lt;&#x2F;code&gt;, and I hand you back &lt;em&gt;a function&lt;&#x2F;em&gt; of type &lt;code&gt;Int -&amp;gt; Int&lt;&#x2F;code&gt;.” &lt;code&gt;add&lt;&#x2F;code&gt;
does not take two arguments at all: it takes one &lt;code&gt;Int&lt;&#x2F;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;&#x2F;strong&gt;, after Haskell
Curry, who also lent the language its first name.&lt;&#x2F;p&gt;
&lt;p&gt;The payoff is that you can stop halfway. &lt;code&gt;add 5&lt;&#x2F;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;&#x2F;strong&gt;, and
&lt;code&gt;addFive&lt;&#x2F;code&gt; is exactly that.&lt;&#x2F;p&gt;
&lt;p&gt;The hardware reading is direct, and worth holding onto:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Partial application is a circuit family parameterised by
configuration.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Fix the configuration bits now; feed the data later. &lt;code&gt;add 5&lt;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;types-as-data-algebraic-data-types&quot;&gt;Types as data: algebraic data types&lt;&#x2F;h2&gt;
&lt;p&gt;Now the syntax you will meet more than any other: &lt;code&gt;data&lt;&#x2F;code&gt;, the keyword
that builds new types. Haskell’s are called &lt;strong&gt;algebraic data types&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;Start with the simpler one, the &lt;strong&gt;sum type&lt;&#x2F;strong&gt;. Each &lt;code&gt;|&lt;&#x2F;code&gt; is an
alternative — read it as “or”:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;data&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Color&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; Red&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; |&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; Green&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; |&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; Blue&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A &lt;code&gt;Color&lt;&#x2F;code&gt; is &lt;code&gt;Red&lt;&#x2F;code&gt; &lt;strong&gt;or&lt;&#x2F;strong&gt; &lt;code&gt;Green&lt;&#x2F;code&gt; &lt;strong&gt;or&lt;&#x2F;strong&gt; &lt;code&gt;Blue&lt;&#x2F;code&gt;, and nothing else.
This is a C or Rust &lt;code&gt;enum&lt;&#x2F;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;&#x2F;code&gt; that is &lt;code&gt;Idle&lt;&#x2F;code&gt; or &lt;code&gt;Preamble&lt;&#x2F;code&gt; or &lt;code&gt;Fetch&lt;&#x2F;code&gt; or one
of a handful of other named states, the FSM’s state register written
as a type.&lt;&#x2F;p&gt;
&lt;p&gt;The other operation is the &lt;strong&gt;product type&lt;&#x2F;strong&gt;, better known as a struct
or record — a bundle that holds one field &lt;strong&gt;and&lt;&#x2F;strong&gt; another &lt;strong&gt;and&lt;&#x2F;strong&gt;
another:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;data&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Point&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; Point&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ::&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Int&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; y&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ::&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Int&lt;&#x2F;span&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A &lt;code&gt;Point&lt;&#x2F;code&gt; carries an &lt;code&gt;x&lt;&#x2F;code&gt; and a &lt;code&gt;y&lt;&#x2F;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;&#x2F;code&gt; and you get &lt;code&gt;x :: Point -&amp;gt; Int&lt;&#x2F;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;&#x2F;code&gt; threaded through every step — program
counter, register file, current phase, all in one value.&lt;&#x2F;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;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;data&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Shape&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; Circle&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Double&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  |&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; Rectangle&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Double&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Double&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A &lt;code&gt;Shape&lt;&#x2F;code&gt; is &lt;em&gt;either&lt;&#x2F;em&gt; a &lt;code&gt;Circle&lt;&#x2F;code&gt; carrying one &lt;code&gt;Double&lt;&#x2F;code&gt; (its radius)
&lt;em&gt;or&lt;&#x2F;em&gt; a &lt;code&gt;Rectangle&lt;&#x2F;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;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;So why “algebraic”? Because you are literally doing arithmetic on
types. A sum &lt;strong&gt;adds&lt;&#x2F;strong&gt; possibilities; a product &lt;strong&gt;multiplies&lt;&#x2F;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;&#x2F;code&gt;
has 2 values; the pair &lt;code&gt;(Bool, Bool)&lt;&#x2F;code&gt; has $2 \cdot 2 = 4$;
&lt;code&gt;Either Bool Bool&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;One member of the family deserves special mention, because it is how
Haskell abolishes the null pointer:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;data&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Maybe&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; a&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; Nothing&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; |&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; Just&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; a&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;Maybe a&lt;&#x2F;code&gt; has two constructors: &lt;code&gt;Nothing&lt;&#x2F;code&gt;, carrying no data, or
&lt;code&gt;Just&lt;&#x2F;code&gt;, carrying an &lt;code&gt;a&lt;&#x2F;code&gt;. It is the type of “a value that might be
absent” — but the absence lives &lt;em&gt;in the type&lt;&#x2F;em&gt;, out in the open. There
is no null &lt;code&gt;a&lt;&#x2F;code&gt; hiding inside an ordinary &lt;code&gt;a&lt;&#x2F;code&gt; waiting to segfault you;
if a value might be missing, its type says &lt;code&gt;Maybe&lt;&#x2F;code&gt;, and you can’t use
it as a bare &lt;code&gt;a&lt;&#x2F;code&gt; — you have to pattern-match, and if you skip the
&lt;code&gt;Nothing&lt;&#x2F;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;&#x2F;code&gt; states a fact about hardware:
&lt;em&gt;at most one trace-RAM write happens this cycle&lt;&#x2F;em&gt; — zero or one, never a
smuggled “sometimes.” A write-enable line, promoted to a type.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;taking-types-apart-pattern-matching&quot;&gt;Taking types apart: pattern matching&lt;&#x2F;h2&gt;
&lt;p&gt;If &lt;code&gt;data&lt;&#x2F;code&gt; is how you build a value, &lt;strong&gt;pattern matching&lt;&#x2F;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;&#x2F;code&gt; expression:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;area&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ::&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Shape&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Double&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;area s &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; case&lt;&#x2F;span&gt;&lt;span&gt; s &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;of&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;  Circle&lt;&#x2F;span&gt;&lt;span&gt; r      &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 3.14159&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; r &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt; r&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;  Rectangle&lt;&#x2F;span&gt;&lt;span&gt; w h &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; w &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt; h&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Read &lt;code&gt;case s of&lt;&#x2F;code&gt; as “look at which &lt;code&gt;Shape&lt;&#x2F;code&gt; &lt;code&gt;s&lt;&#x2F;code&gt; actually is.” If it was
built with &lt;code&gt;Circle&lt;&#x2F;code&gt;, the first branch fires and binds &lt;code&gt;r&lt;&#x2F;code&gt; to the
radius packed inside; if &lt;code&gt;Rectangle&lt;&#x2F;code&gt;, the second binds &lt;code&gt;w&lt;&#x2F;code&gt; and &lt;code&gt;h&lt;&#x2F;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;&#x2F;code&gt; is to match &lt;code&gt;Circle r&lt;&#x2F;code&gt;, and inside
that branch the compiler &lt;em&gt;knows&lt;&#x2F;em&gt; a radius is there.&lt;&#x2F;p&gt;
&lt;p&gt;When a function is nothing but one &lt;code&gt;case&lt;&#x2F;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;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;area&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ::&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Shape&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Double&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;area&amp;#39; &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; \&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;case&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;  Circle&lt;&#x2F;span&gt;&lt;span&gt; r      &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 3.14159&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; r &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt; r&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;  Rectangle&lt;&#x2F;span&gt;&lt;span&gt; w h &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; w &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt; h&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Same behaviour, one fewer name to invent; &lt;code&gt;\case&lt;&#x2F;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;&#x2F;em&gt;
a match on some state or opcode.&lt;&#x2F;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;&#x2F;strong&gt;: if your &lt;code&gt;case&lt;&#x2F;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;&#x2F;code&gt; is a sum type and the
engine’s next-state logic is a &lt;code&gt;case&lt;&#x2F;code&gt; over it. That means the question
every FSM review is supposed to ask —&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Did you handle every state?&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;— is answered by the compiler, on every build, for free. Add a new
&lt;code&gt;Phase&lt;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;types-with-behaviour-typeclasses-and-deriving&quot;&gt;Types with behaviour: typeclasses and &lt;code&gt;deriving&lt;&#x2F;code&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The last piece of everyday syntax is how Haskell attaches &lt;em&gt;behaviour&lt;&#x2F;em&gt;
to types. A &lt;strong&gt;typeclass&lt;&#x2F;strong&gt; is an interface — a named set of operations
a type can choose to support:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;class&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Eq&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; a&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; where&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;==&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ::&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; a&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; a&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Bool&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This declares &lt;code&gt;Eq&lt;&#x2F;code&gt;, the class of types whose values can be compared:
an &lt;code&gt;Eq&lt;&#x2F;code&gt; must supply a &lt;code&gt;==&lt;&#x2F;code&gt; taking two of its values and returning a
&lt;code&gt;Bool&lt;&#x2F;code&gt;. A concrete type opts in with an &lt;strong&gt;instance&lt;&#x2F;strong&gt; — the actual &lt;code&gt;==&lt;&#x2F;code&gt;
for &lt;code&gt;Int&lt;&#x2F;code&gt;, for &lt;code&gt;Color&lt;&#x2F;code&gt;, for whatever. If you write Rust this is a
&lt;strong&gt;trait&lt;&#x2F;strong&gt;; in modern C++, a &lt;strong&gt;concept&lt;&#x2F;strong&gt;. What it is &lt;em&gt;not&lt;&#x2F;em&gt;, despite the
keyword, is an object-oriented class: no fields, no constructors, no
inheritance tree, no &lt;code&gt;this&lt;&#x2F;code&gt;. A typeclass is a contract a type
satisfies, not a box a value lives inside.&lt;&#x2F;p&gt;
&lt;p&gt;Typeclasses exist to enable &lt;strong&gt;bounded polymorphism&lt;&#x2F;strong&gt; — one function
that works for &lt;em&gt;any&lt;&#x2F;em&gt; type, provided it supports the operations you
need. The signature carries the requirement:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;elem&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ::&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Eq&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; a&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; a&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;a&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Bool&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Read the &lt;code&gt;=&amp;gt;&lt;&#x2F;code&gt; as a &lt;strong&gt;constraint arrow&lt;&#x2F;strong&gt;: “for &lt;strong&gt;any&lt;&#x2F;strong&gt; type &lt;code&gt;a&lt;&#x2F;code&gt; that is
an &lt;code&gt;Eq&lt;&#x2F;code&gt;, &lt;code&gt;elem&lt;&#x2F;code&gt; takes an &lt;code&gt;a&lt;&#x2F;code&gt; and a list of &lt;code&gt;a&lt;&#x2F;code&gt; and says whether the
value is in the list.” The part before &lt;code&gt;=&amp;gt;&lt;&#x2F;code&gt; is a precondition on the
caller — &lt;em&gt;given that &lt;code&gt;a&lt;&#x2F;code&gt; can be compared&lt;&#x2F;em&gt; — and the part after is the
ordinary type. &lt;code&gt;elem&lt;&#x2F;code&gt; does not care whether &lt;code&gt;a&lt;&#x2F;code&gt; is &lt;code&gt;Int&lt;&#x2F;code&gt; or &lt;code&gt;Color&lt;&#x2F;code&gt;; it
only asks that &lt;code&gt;==&lt;&#x2F;code&gt; exist.&lt;&#x2F;p&gt;
&lt;p&gt;Which brings us to the incantation stapled to nearly every &lt;code&gt;data&lt;&#x2F;code&gt;
declaration in Tamal:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;data&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Phase&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; Idle&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; |&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; Running&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; |&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; Halted&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  deriving&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; stock&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt;Generic&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Show&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Eq&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Enum&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Bounded&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  deriving&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; anyclass&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt;NFDataX&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;deriving&lt;&#x2F;code&gt; is the compiler offering to write the boring instances for
you, so you never hand-roll a &lt;code&gt;==&lt;&#x2F;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;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Show&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — the type is printable, so a failing property test can
show you the exact value it choked on.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Eq&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — values can be compared with &lt;code&gt;==&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Enum&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; and &lt;strong&gt;&lt;code&gt;Bounded&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — “list me every constructor, in
order,” which is exactly what an exhaustive test over all &lt;code&gt;Phase&lt;&#x2F;code&gt;s
needs.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Generic&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — a structural, machine-readable description of the
type that other libraries build on top of; plumbing, rarely read by
humans.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;NFDataX&lt;&#x2F;code&gt;&lt;&#x2F;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;&#x2F;strong&gt; — that
its values, including “undefined at power-up,” are well-defined
enough to be stored in a flip-flop.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The &lt;code&gt;stock&lt;&#x2F;code&gt; versus &lt;code&gt;anyclass&lt;&#x2F;code&gt; split is only &lt;em&gt;which machinery&lt;&#x2F;em&gt; writes
the instance — a built-in strategy for the classic classes, a
generics-based one for &lt;code&gt;NFDataX&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;types-that-count-widths-in-the-type&quot;&gt;Types that count: widths in the type&lt;&#x2F;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;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;     ::&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; BitVector&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 32&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;   --&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; exactly 32 bits&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;lanes&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ::&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Vec&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Bit&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;      --&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; exactly 4 elements&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;i&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;     ::&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Index&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 5&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;        --&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; a value 0..4, and never 5&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;32&lt;&#x2F;code&gt;, the &lt;code&gt;4&lt;&#x2F;code&gt;, and the &lt;code&gt;5&lt;&#x2F;code&gt; are not runtime values or comments.
They are &lt;em&gt;part of the type&lt;&#x2F;em&gt;, checked at compile time like everything
else. &lt;code&gt;BitVector 32&lt;&#x2F;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;&#x2F;code&gt; is a vector of
exactly four bits. &lt;code&gt;Index 5&lt;&#x2F;code&gt; is a number in the range &lt;code&gt;0..4&lt;&#x2F;code&gt; and, by
construction, &lt;em&gt;never&lt;&#x2F;em&gt; 5 — the natural type for an index into something
with five slots.&lt;&#x2F;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;&#x2F;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;&#x2F;code&gt; into a port expecting a &lt;code&gt;BitVector 8&lt;&#x2F;code&gt;; they are different
types and the mismatch is a compile error. The clean way to say what
that buys you:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;The compiler is a wire-width checker.&lt;&#x2F;p&gt;
&lt;&#x2F;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;&#x2F;code&gt;,
where &lt;code&gt;AW&lt;&#x2F;code&gt; is the address width, carries the program-counter size in
the type; an &lt;code&gt;Index 5&lt;&#x2F;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;&#x2F;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;&#x2F;code&gt; and &lt;code&gt;mealy&lt;&#x2F;code&gt;&lt;&#x2F;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;&#x2F;strong&gt;. Clash spells time with a type called &lt;code&gt;Signal&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;counter&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ::&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Signal&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; dom&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt;Unsigned&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Read &lt;code&gt;Signal dom a&lt;&#x2F;code&gt; as “a value of type &lt;code&gt;a&lt;&#x2F;code&gt; that may change on every
clock tick, in clock domain &lt;code&gt;dom&lt;&#x2F;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;&#x2F;em&gt; once you stop looking at a single instant and watch it over the
whole run. The &lt;code&gt;dom&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;The bridge from the pure world to the clocked one is a single function,
&lt;code&gt;mealy&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;--&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&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;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;step&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ::&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; State&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Input&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt;State&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; Output&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;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;&#x2F;code&gt; takes those two things
and hands back a function on &lt;code&gt;Signal&lt;&#x2F;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;&#x2F;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;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This is the punchline behind the &lt;a href=&quot;https:&#x2F;&#x2F;balbi.sh&#x2F;posts&#x2F;tamal-introducing&#x2F;&quot;&gt;introduction post&lt;&#x2F;a&gt;’s claim
that its tests run in under a second. &lt;code&gt;stepM&lt;&#x2F;code&gt; is just a function —
&lt;code&gt;State&lt;&#x2F;code&gt; and &lt;code&gt;Input&lt;&#x2F;code&gt; in, new &lt;code&gt;State&lt;&#x2F;code&gt; and &lt;code&gt;Output&lt;&#x2F;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;&#x2F;em&gt; you trust it
does &lt;code&gt;mealy&lt;&#x2F;code&gt; make it hardware. The pure core is the part you test;
&lt;code&gt;mealy&lt;&#x2F;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;&#x2F;code&gt; at the top of a
module and read it as “clocked state machine, its brain the pure
function beside it.”&lt;&#x2F;p&gt;
&lt;h2 id=&quot;where-to-go-next&quot;&gt;Where to go next&lt;&#x2F;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;&#x2F;code&gt; block is sums and products, with
&lt;code&gt;Maybe&lt;&#x2F;code&gt; for the things that might be absent; a &lt;code&gt;case&lt;&#x2F;code&gt; or &lt;code&gt;\case&lt;&#x2F;code&gt; takes
those values apart, exhaustively, with the compiler checking your work;
the stack of &lt;code&gt;deriving&lt;&#x2F;code&gt; clauses is the compiler filling in equality,
printing, enumeration, and “fits in a register”; a &lt;code&gt;BitVector n&lt;&#x2F;code&gt; or an
&lt;code&gt;Index n&lt;&#x2F;code&gt; is a wire whose width the type system guards; and the &lt;code&gt;mealy&lt;&#x2F;code&gt;
at the top of a module is a clocked state machine wrapped around an
ordinary, testable function.&lt;&#x2F;p&gt;
&lt;p&gt;None of that is the &lt;em&gt;interesting&lt;&#x2F;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;&#x2F;p&gt;
</content:encoded>
    </item>
    
    <item>
      <title>Tamal: an eSPI exerciser</title>
      <link>https://balbi.sh/posts/tamal-introducing/</link>
      <guid isPermaLink="true">https://balbi.sh/posts/tamal-introducing/</guid>
      <pubDate>Mon, 13 Jul 2026 09:00:00 +0000</pubDate>
      <dc:creator>Felipe Balbi</dc:creator>
      <description>&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;felipebalbi&#x2F;mole&quot;&gt;mole&lt;&#x2F;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;&#x2F;em&gt; is that same idea aimed at a different, meaner bus: Intel’s
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.intel.com&#x2F;content&#x2F;www&#x2F;us&#x2F;en&#x2F;support&#x2F;articles&#x2F;000020952&#x2F;software&#x2F;chipset-software.html&quot;&gt;Enhanced Serial Peripheral Interface&lt;&#x2F;a&gt; (eSPI, base spec
rev 1.0).&lt;&#x2F;p&gt;</description>
      
      <content:encoded>&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;felipebalbi&#x2F;mole&quot;&gt;mole&lt;&#x2F;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;&#x2F;em&gt; is that same idea aimed at a different, meaner bus: Intel’s
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.intel.com&#x2F;content&#x2F;www&#x2F;us&#x2F;en&#x2F;support&#x2F;articles&#x2F;000020952&#x2F;software&#x2F;chipset-software.html&quot;&gt;Enhanced Serial Peripheral Interface&lt;&#x2F;a&gt; (eSPI, base spec
rev 1.0).&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;The one-line pitch, which is the vision more than the current state:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;A programmable eSPI controller&#x2F;target that turns compliance testing
into a reproducible, fully-observable, byte-for-byte deterministic
exercise.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Read that as the destination, not the odometer. Tamal is
&lt;strong&gt;experimental&lt;&#x2F;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;&#x2F;strong&gt; only (target role is designed but
not built), at a &lt;strong&gt;single 20 MHz clock&lt;&#x2F;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;&#x2F;em&gt;, not on silicon.&lt;&#x2F;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:&#x2F;&#x2F;github.com&#x2F;felipebalbi&#x2F;tamal&quot;&gt;github.com&#x2F;felipebalbi&#x2F;tamal&lt;&#x2F;a&gt;, public and split-licensed. Read
on before you clone it expecting a finished product; it isn’t one yet.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;&#x2F;h2&gt;
&lt;p&gt;eSPI is the bus that replaced LPC on modern platforms: a short,
fast-ish serial link between a chipset&#x2F;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;&#x2F;strong&gt;, &lt;strong&gt;Virtual
Wire&lt;&#x2F;strong&gt;, &lt;strong&gt;OOB&lt;&#x2F;strong&gt; (tunneled SMBus), and &lt;strong&gt;Runtime Flash Access&lt;&#x2F;strong&gt; — over
one set of pins, with command&#x2F;response framing, turnaround phases,
CRC, alerts, and a status register that gates who’s allowed to talk.&lt;&#x2F;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;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Producing a legal transaction is the easy 20%. Producing a specific
&lt;em&gt;illegal&lt;&#x2F;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;&#x2F;em&gt; cycle, correct or
malformed, with deterministic timing, and capture everything the bus
did in response.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-name&quot;&gt;The name&lt;&#x2F;h2&gt;
&lt;p&gt;eSPI wraps four logical channels into one serial packet stream. A
&lt;em&gt;tamal&lt;&#x2F;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;&#x2F;em&gt; → &lt;em&gt;tamal&lt;&#x2F;em&gt; lineage (both wrapped, both
Mesoamerican, both about what’s hidden inside) was too good to pass
up.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-key-insight&quot;&gt;The key insight&lt;&#x2F;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;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is &lt;strong&gt;not&lt;&#x2F;strong&gt; a throughput problem. It is an &lt;strong&gt;external timing
alignment&lt;&#x2F;strong&gt; problem.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;eSPI tops out at 66 MHz. On an Artix-7 that’s &lt;em&gt;slow&lt;&#x2F;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;&#x2F;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;&#x2F;em&gt;. The parts that will actually hurt you are turnaround&#x2F;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;&#x2F;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;&#x2F;em&gt; about when it drives,
when it samples, and when it lets go of the pins.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-shape-three-planes&quot;&gt;The shape: three planes&lt;&#x2F;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;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Control plane&lt;&#x2F;strong&gt; (host → FPGA): load a compiled test program,
select role (controller today; target is designed but not yet built),
IO mode (single&#x2F;dual&#x2F;quad), CRC on or off, and triggers. Error
injection isn’t a knob here — it lives &lt;em&gt;in the program&lt;&#x2F;em&gt;, where the
test author writes the malformed cycle directly (more on that below).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Bus plane&lt;&#x2F;strong&gt; (FPGA ↔ DUT): the eSPI link itself — &lt;code&gt;CS#&lt;&#x2F;code&gt;, &lt;code&gt;CLK&lt;&#x2F;code&gt;,
&lt;code&gt;IO[3:0]&lt;&#x2F;code&gt;, &lt;code&gt;ALERT#&lt;&#x2F;code&gt;, &lt;code&gt;RESET#&lt;&#x2F;code&gt; — driven or sampled against the device
under test.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Trace &#x2F; result plane&lt;&#x2F;strong&gt; (FPGA → host): observed transactions,
channel decode, captured cycles, and verdicts.&lt;&#x2F;li&gt;
&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;On the FPGA, that shape is deliberately boring:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-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;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      ◄─UART──── drain  ◄──── ring BRAM ◄──────┘   (IO[3:0], CS#, SCK,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                                                     RESET#, ALERT#)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;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;&#x2F;code&gt; the drainer sweeps the ring back
out over the same UART.&lt;&#x2F;p&gt;
&lt;p&gt;The whole thing is &lt;strong&gt;one clock domain&lt;&#x2F;strong&gt; — &lt;code&gt;Dom100&lt;&#x2F;code&gt;, 100 MHz — with no
clock-domain crossings and no FIFOs. The trace ring BRAM &lt;em&gt;is&lt;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;the-repo&quot;&gt;The repo&lt;&#x2F;h2&gt;
&lt;p&gt;Two toolchains, cleanly separated:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;crates&#x2F;                 Rust host tooling (Cargo workspace)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tamal-abi&#x2F;            bytecode&#x2F;ISA encoding + COBS&#x2F;CRC-8 wire format + typed trace decode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tamal-asm&#x2F;            assembler: RISC-V-flavored source -&amp;gt; tamal bytecode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tamal-asm-cli&#x2F;        `tamal-asm` binary (clap front-end)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tamal-loader&#x2F;         host-side loader: load -&amp;gt; trigger -&amp;gt; drain over a transport&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  tamal-loader-cli&#x2F;     `tamal-loader` binary&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;hdl&#x2F;                    Clash gateware + Vivado&#x2F;Quartus build (self-contained)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;docs&#x2F;                   design specs + implementation plans&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;tamal-abi&lt;&#x2F;code&gt; is the crate that matters most: it’s the ABI, the single
source of truth for the bytecode encoding, the COBS&#x2F;CRC-8 wire format,
and the typed trace decode. Everything else — assembler, loader,
gateware — agrees with &lt;code&gt;tamal-abi&lt;&#x2F;code&gt; byte for byte or it’s a bug. It is
deliberately &lt;strong&gt;transport-agnostic&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;The licensing follows the &lt;code&gt;crates&#x2F;&lt;&#x2F;code&gt; ↔ &lt;code&gt;hdl&#x2F;&lt;&#x2F;code&gt; seam. The Rust host
tooling is &lt;strong&gt;MIT&lt;&#x2F;strong&gt;. The Clash gateware is &lt;strong&gt;CERN-OHL-W-2.0&lt;&#x2F;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;&#x2F;code&gt; file under &lt;code&gt;hdl&#x2F;&lt;&#x2F;code&gt; carries a REUSE-style SPDX header
to make that boundary machine-checkable.&lt;&#x2F;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;&#x2F;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;&#x2F;strong&gt;&lt;&#x2F;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;&#x2F;code&gt;..&lt;code&gt;x31&lt;&#x2F;code&gt; with &lt;code&gt;x0&lt;&#x2F;code&gt; hardwired to zero (v1
implements 16 physical registers, with &lt;code&gt;x16&lt;&#x2F;code&gt;..&lt;code&gt;x31&lt;&#x2F;code&gt; aliasing their
low-4 twin); the R&#x2F;I&#x2F;S&#x2F;B&#x2F;U&#x2F;J instruction formats; and the ABI register
names (&lt;code&gt;zero&lt;&#x2F;code&gt;, &lt;code&gt;ra&lt;&#x2F;code&gt;, &lt;code&gt;sp&lt;&#x2F;code&gt;, &lt;code&gt;t0&lt;&#x2F;code&gt;, &lt;code&gt;s0&lt;&#x2F;code&gt;, &lt;code&gt;a0&lt;&#x2F;code&gt;, …). The assembler follows
the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;riscv-non-isa&#x2F;riscv-asm-manual&quot;&gt;riscv-asm-manual&lt;&#x2F;a&gt; conventions for directives, labels,
and pseudo-instructions — but &lt;em&gt;follows&lt;&#x2F;em&gt; is doing real work in that
sentence. What’s implemented today is the subset the examples exercise
(&lt;code&gt;.text&lt;&#x2F;code&gt;, &lt;code&gt;.globl&lt;&#x2F;code&gt;, &lt;code&gt;.equ&lt;&#x2F;code&gt;, the &lt;code&gt;li&lt;&#x2F;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;&#x2F;code&gt;, &lt;code&gt;.macro&lt;&#x2F;code&gt;, numeric local labels (&lt;code&gt;1f&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;1b&lt;&#x2F;code&gt;),
&lt;code&gt;mv&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;j&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;call&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;ret&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;beqz&lt;&#x2F;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;&#x2F;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&#x2F;verdict. The upshot is a hard rule: Tamal
bytecode is &lt;strong&gt;not&lt;&#x2F;strong&gt; interchangeable with a stock RISC-V toolchain, and
the project never claims otherwise. It’s RISC-V-&lt;em&gt;flavored&lt;&#x2F;em&gt;, not
RISC-V.&lt;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;em&gt;,
and every &lt;em&gt;outbound&lt;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;the-shape-of-a-program&quot;&gt;The shape of a program&lt;&#x2F;h2&gt;
&lt;p&gt;The smallest interesting program reads one byte from an eSPI
Peripheral-channel I&#x2F;O port. Here’s the real &lt;code&gt;peripheral_io_read.s&lt;&#x2F;code&gt;
from the repo, reading I&#x2F;O port &lt;code&gt;0x64&lt;&#x2F;code&gt; — the classic 8042
keyboard-controller status port:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;asm&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function&quot;&gt;equ&lt;&#x2F;span&gt;&lt;span&gt;  PUT_IORD1,      &lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;0x44&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;    # PUT_IORD_SHORT, length = 1 byte&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function&quot;&gt;equ&lt;&#x2F;span&gt;&lt;span&gt;  RSP_WAIT_STATE, &lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;0x0F&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;    # eSPI WAIT_STATE response code&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function&quot;&gt;equ&lt;&#x2F;span&gt;&lt;span&gt;  VERDICT_OK,     &lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;0x00&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;    # host verdict codes (written by halt)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function&quot;&gt;equ&lt;&#x2F;span&gt;&lt;span&gt;  VERDICT_CRC,    &lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;0x11&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    .text&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    .globl _start&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;_start&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    set_config CONTROLLER, X1, SCK20, ALERT_PIN  &lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; # controller, x1 IO, 20 MHz&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    cs_assert                      &lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; # begin frame: CS# low&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;    # --- command phase: host-built eSPI packet ---&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    put_byte PUT_IORD1             &lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; # CMD:  PUT_IORD_SHORT (1 byte)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    put_byte &lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;0x00&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;                   # addr [15:8]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    put_byte &lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;0x64&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;                   # addr [7:0]  -&amp;gt; I&#x2F;O port 0x64&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    put_byte &lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;0x16&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;                   # TX CRC-8 over the 3 bytes above (poly 0x07)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    tar &lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;2&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;                           # legal turnaround; tar 3 &#x2F; tar 1 = deliberate violation&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;    # --- response phase: WAIT_STATE poll + RX CRC residue verdict ---&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;poll&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    crc_reset                      &lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; # drop any prior WAIT_STATE byte from the residue&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    get_byte t0                    &lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; # response code (auto-updates RX CRC-8)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    li   t1, RSP_WAIT_STATE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    beq  t0, t1, poll              &lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; # WAIT_STATE -&amp;gt; keep polling&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    get_byte t0                    &lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; # read data byte (port 0x64 value)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    get_byte t0                    &lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; # status [7:0]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    get_byte t0                    &lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; # status [15:8]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    get_byte t0                    &lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; # trailing CRC byte -&amp;gt; drives residue to 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    rdsr t2, CRC                   &lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; # RX CRC-8 residue (0 == good packet)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    cs_deassert                    &lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; # end frame: CS# high&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    bnez t2, bad_crc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    halt VERDICT_OK&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;bad_crc&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    halt VERDICT_CRC&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Read it top to bottom and the whole model is visible. &lt;code&gt;set_config&lt;&#x2F;code&gt;
picks controller role, single (x1) IO, a 20 MHz clock (&lt;code&gt;SCK20&lt;&#x2F;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;&#x2F;code&gt;
pulls &lt;code&gt;CS#&lt;&#x2F;code&gt; low. Then a run of &lt;code&gt;put_byte&lt;&#x2F;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;&#x2F;em&gt;
computed, not the engine. &lt;code&gt;tar 2&lt;&#x2F;code&gt; performs a legal turnaround;
swapping in &lt;code&gt;tar 3&lt;&#x2F;code&gt; or &lt;code&gt;tar 1&lt;&#x2F;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;&#x2F;code&gt; writes a verdict code the host reads back from
the trace.&lt;&#x2F;p&gt;
&lt;p&gt;The load-bearing observation: &lt;strong&gt;only the command-phase &lt;code&gt;put_byte&lt;&#x2F;code&gt;s are
channel-specific.&lt;&#x2F;strong&gt; The CS framing, the WAIT_STATE poll, and the CRC
residue check are byte-for-byte identical across the Peripheral I&#x2F;O
read, the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;felipebalbi&#x2F;tamal&#x2F;blob&#x2F;main&#x2F;examples&#x2F;virtual_wire_pltrst.s&quot;&gt;Virtual Wire&lt;&#x2F;a&gt; example that deasserts &lt;code&gt;PLTRST#&lt;&#x2F;code&gt;,
and every other per-channel program in &lt;code&gt;examples&#x2F;&lt;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;why-clash-why-haskell&quot;&gt;Why Clash, why Haskell&lt;&#x2F;h2&gt;
&lt;p&gt;The gateware is written in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;clash-lang.org&quot;&gt;Clash&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;The Tamal test suite runs in &lt;strong&gt;under a second.&lt;&#x2F;strong&gt; Not the synthesis —
the &lt;em&gt;tests&lt;&#x2F;em&gt;. Every pure leaf of the design (&lt;code&gt;stepM&lt;&#x2F;code&gt;, &lt;code&gt;ringWrite&lt;&#x2F;code&gt;,
&lt;code&gt;rigState&lt;&#x2F;code&gt;, &lt;code&gt;ledPattern&lt;&#x2F;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:&#x2F;&#x2F;hedgehog.qa&quot;&gt;hedgehog&lt;&#x2F;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;&#x2F;code&gt;, a second later I know.&lt;&#x2F;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;&#x2F;code&gt;s — no &lt;code&gt;BiSignal&lt;&#x2F;code&gt; in the core, the tri-state only
appears in the thin board shell — the &lt;em&gt;entire system&lt;&#x2F;em&gt; simulates in
Haskell. There’s a whole-system cosim (&lt;code&gt;Test.Top&lt;&#x2F;code&gt;) that serializes a
real &lt;code&gt;LOAD_PROGRAM&lt;&#x2F;code&gt; + &lt;code&gt;TRIGGER&lt;&#x2F;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;&#x2F;code&gt; terminator) &lt;em&gt;and&lt;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;code&gt; + Vivado&#x2F;Quartus) is more to stand up than &lt;code&gt;verilator&lt;&#x2F;code&gt;. For
this project, on this bus, that sub-second feedback loop won the
argument easily.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;lessons-learned&quot;&gt;Lessons learned&lt;&#x2F;h2&gt;
&lt;p&gt;A few things I didn’t know going in, in case they save you some time:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The bus is not the hard part; the pins are.&lt;&#x2F;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;&#x2F;em&gt; problem — especially for the
not-yet-built target role — is what made the architecture click.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;A dumb engine is a feature.&lt;&#x2F;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;&#x2F;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;&#x2F;em&gt; and never
learns what a packet is.)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Clash’s tri-state lowering has sharp edges.&lt;&#x2F;strong&gt; Four scalar &lt;code&gt;inout&lt;&#x2F;code&gt;
lanes (&lt;code&gt;io0&lt;&#x2F;code&gt;..&lt;code&gt;io3&lt;&#x2F;code&gt;) exist instead of a tidy &lt;code&gt;Vec&lt;&#x2F;code&gt; because Clash
fuses a per-lane &lt;code&gt;BiSignalIn&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;BiSignalOut&lt;&#x2F;code&gt; pair into one &lt;code&gt;inout&lt;&#x2F;code&gt;
port — but a &lt;code&gt;Vec&lt;&#x2F;code&gt; of BiSignals does &lt;em&gt;not&lt;&#x2F;em&gt;; it silently lowers to a
plain input. That one cost me an afternoon.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;The no-reset power-up design is deliberate.&lt;&#x2F;strong&gt; The top ties reset
permanently de-asserted and relies on power-up &lt;code&gt;init&lt;&#x2F;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;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Some ghc-options are load-bearing.&lt;&#x2F;strong&gt; The &lt;code&gt;common-options&lt;&#x2F;code&gt; in
&lt;code&gt;tamal.cabal&lt;&#x2F;code&gt; aren’t optional style — Clash needs them. Trimming them
to tidy up breaks codegen in non-obvious ways.&lt;&#x2F;li&gt;
&lt;&#x2F;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;&#x2F;h2&gt;
&lt;p&gt;Now the honest part, because this is the whole reason for the
&lt;strong&gt;experimental&lt;&#x2F;strong&gt; label I put on it up top.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Gateware: v1 complete, in &lt;em&gt;simulation&lt;&#x2F;em&gt;.&lt;&#x2F;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&#x2F;CRC-8 wire format, the
UART load&#x2F;drain loader, the tri-state eSPI pad boundary, and the
&lt;code&gt;topEntity&lt;&#x2F;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;&#x2F;code&gt; emits a
synthesizable top, and &lt;code&gt;cd hdl &amp;amp;&amp;amp; make&lt;&#x2F;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;&#x2F;strong&gt;
Simulation-complete is a real milestone; it is not the same as “works
on the bench.”&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Host tooling: v1 implemented and tested.&lt;&#x2F;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&#x2F;fail &lt;em&gt;conformance verdict engine&lt;&#x2F;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;&#x2F;code&gt;&#x2F;&lt;code&gt;TRAP&lt;&#x2F;code&gt; with a
host-defined verdict code; a real conformance catalog is a later
phase.&lt;&#x2F;p&gt;
&lt;p&gt;So, plainly: &lt;strong&gt;Tamal is early.&lt;&#x2F;strong&gt; What it most needs next is validation
against &lt;em&gt;real&lt;&#x2F;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;&#x2F;em&gt; makes, and models are exactly the
thing an exerciser exists to distrust.&lt;&#x2F;p&gt;
&lt;p&gt;The roadmap is four phases:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Link + transaction bring-up.&lt;&#x2F;strong&gt; SPI-style framing,
command&#x2F;response&#x2F;turnaround, Get Status &#x2F; Get Configuration, CRC;
controller role over single IO. &lt;em&gt;(This is where v1 sits — done in
sim, next on hardware.)&lt;&#x2F;em&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;The four channels.&lt;&#x2F;strong&gt; Peripheral, Virtual Wire, OOB (tunneled
SMBus), Runtime Flash Access; the ISA, assembler, and loader path;
result streaming.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Target role, alerts, dual&#x2F;quad IO, faster clocks.&lt;&#x2F;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;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Determinism and verdicts.&lt;&#x2F;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&#x2F;fail judgment.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;where-to-look&quot;&gt;Where to look&lt;&#x2F;h2&gt;
&lt;p&gt;The source is &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;felipebalbi&#x2F;tamal&quot;&gt;on GitHub&lt;&#x2F;a&gt;. The gateware’s story is in
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;felipebalbi&#x2F;tamal&#x2F;blob&#x2F;main&#x2F;hdl&#x2F;README.md&quot;&gt;&lt;code&gt;hdl&#x2F;README.md&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; and &lt;code&gt;hdl&#x2F;PLAN.md&lt;&#x2F;code&gt;; the design specs live
under &lt;code&gt;docs&#x2F;&lt;&#x2F;code&gt;; &lt;code&gt;AGENTS.md&lt;&#x2F;code&gt; is the fastest orientation to how the
pieces fit. The &lt;code&gt;examples&#x2F;&lt;&#x2F;code&gt; directory is the best way to get the feel
of the ISA — start with &lt;code&gt;smoke_halt.s&lt;&#x2F;code&gt;, then &lt;code&gt;peripheral_io_read.s&lt;&#x2F;code&gt;,
then the per-channel programs.&lt;&#x2F;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;&#x2F;p&gt;
</content:encoded>
    </item>
    
    <item>
      <title>From &#x27;it works&#x27; to &#x27;I trust it&#x27;: two torture loops on a NOR driver</title>
      <link>https://balbi.sh/posts/flexspi-mcxa-hardening/</link>
      <guid isPermaLink="true">https://balbi.sh/posts/flexspi-mcxa-hardening/</guid>
      <pubDate>Sun, 28 Jun 2026 09:00:00 +0000</pubDate>
      <dc:creator>Felipe Balbi</dc:creator>
      <description>&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;&#x2F;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;&#x2F;p&gt;</description>
      
      <content:encoded>&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;&#x2F;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;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;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;&#x2F;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:&#x2F;&#x2F;github.com&#x2F;embassy-rs&#x2F;embassy&#x2F;pull&#x2F;6389&quot;&gt;embassy-rs&#x2F;embassy#6389&lt;&#x2F;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:&#x2F;&#x2F;github.com&#x2F;embassy-rs&#x2F;embassy&#x2F;pull&#x2F;6386&quot;&gt;AHBCR&lt;&#x2F;a&gt;, &lt;code&gt;DLLCR&lt;&#x2F;code&gt;, &lt;code&gt;FLSHCR2&lt;&#x2F;code&gt;) from
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;embassy-rs&#x2F;embassy&#x2F;pull&#x2F;6386&quot;&gt;#6386&lt;&#x2F;a&gt; — more on how &lt;em&gt;those&lt;&#x2F;em&gt; got found in a moment, because it
was the same rig.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-a-torture-loop-has-to-be&quot;&gt;What a torture loop has to be&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;It has to be &lt;strong&gt;adversarial&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;It has to be &lt;strong&gt;reproducible&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;It has to &lt;strong&gt;protect itself from itself&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;Stripped of the bookkeeping, each round looks like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;loop&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; seed&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; rng&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;next&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;          &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; logged, so any failure replays&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; window&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; next_window&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;seed&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; prime-stride rotation, wear-spread&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;    erase&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;window&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;?&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;    verify_erased&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;window&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;?&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; write&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; random_writes&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;seed&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; window&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;        program&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;write&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;addr&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; write&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;bytes&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;?&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;    &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; The load-bearing assertion: two independent read paths must&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;    &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; agree with each other and with what we wrote.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;    assert_eq!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;read_ip&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;window&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;   expected&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;seed&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;    assert_eq!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;read_mmap&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;window&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; expected&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;seed&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;    assert_canary_intact&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;?&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;        &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; nobody wrote where they shouldn&amp;#39;t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; elapsed&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; LIMIT&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; break&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;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;&#x2F;code&gt; addressing bug surfaced — I fixed that
separately in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;embassy-rs&#x2F;embassy&#x2F;pull&#x2F;6386&quot;&gt;#6386&lt;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;code&gt; of 5 or
7, and &lt;em&gt;another&lt;&#x2F;em&gt; write followed in the same page, the byte immediately
&lt;em&gt;before&lt;&#x2F;em&gt; the second write’s start address came back as &lt;code&gt;0x00&lt;&#x2F;code&gt;. Not the
bytes I wrote — those were fine. The byte &lt;em&gt;before&lt;&#x2F;em&gt; them, which I hadn’t
touched in that operation at all.&lt;&#x2F;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;&#x2F;code&gt; in their SDK. It was identical.
The driver was filling the FIFO exactly the way the vendor does.&lt;&#x2F;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;&#x2F;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;&#x2F;em&gt;, loudly, instead of letting it
silently corrupt an adjacent byte. &lt;code&gt;page_program&lt;&#x2F;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;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; The 8 is a property of the FlexSPI IP&#x2F;DMA FIFO window,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; not of the flash chip. Name it so nobody &amp;quot;optimizes&amp;quot; it away.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;const&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; WRITE_GRANULARITY&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; usize&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; check_program&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;addr&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; u32&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; len&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; usize&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; flash_size&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; u32&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; IoError&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; addr&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; usize&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; %&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; WRITE_GRANULARITY&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; !=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; |&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;|&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; len&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; %&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; WRITE_GRANULARITY&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; !=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Err&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;IoError&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;Misaligned&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    match&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; addr&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;checked_add&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;len&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; u32&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;        Some&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;end&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; if&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; end&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;lt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; flash_size&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;        _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; return&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Err&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;IoError&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;OutOfBounds&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;    &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; ...plus the single-page check...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;    Ok&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;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;&#x2F;code&gt;), so the async&#x2F;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;&#x2F;code&gt;’s &lt;code&gt;WRITE_SIZE&lt;&#x2F;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;&#x2F;code&gt;, &lt;code&gt;erase_sector&lt;&#x2F;code&gt;, and
&lt;code&gt;program&lt;&#x2F;code&gt; against the configured flash size, returning
&lt;code&gt;IoError::OutOfBounds&lt;&#x2F;code&gt; instead of wrapping around. Both new variants
went onto an already &lt;code&gt;#[non_exhaustive]&lt;&#x2F;code&gt; &lt;code&gt;IoError&lt;&#x2F;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;&#x2F;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;&#x2F;code&gt;-byte partial write became &lt;code&gt;96&lt;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;strong&gt;.&lt;&#x2F;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;&#x2F;code&gt;, or race it in a &lt;code&gt;select!&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;So the second rig, &lt;code&gt;flexspi-cancel-soak&lt;&#x2F;code&gt;, does that on purpose,
thousands of times. It runs the same erase&#x2F;program&#x2F;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;&#x2F;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;&#x2F;em&gt; operation and asserts that one returns correct data.&lt;&#x2F;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;&#x2F;em&gt;. So
the test simply &lt;em&gt;reaching the next round&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;It stopped.&lt;&#x2F;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;&#x2F;h2&gt;
&lt;p&gt;The symptom was a hang, deep in &lt;code&gt;prepare_ip_transfer&lt;&#x2F;code&gt;, spinning in
&lt;code&gt;wait_idle()&lt;&#x2F;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;&#x2F;em&gt; operation walked in, asked the engine to be idle, and waited for
a condition that the &lt;em&gt;previous, cancelled&lt;&#x2F;em&gt; operation had made
permanently false. One cancelled future, and the controller was bricked
until reset.&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; prepare_ip_transfer&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language&quot;&gt; self&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; IoError&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;    &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; A cancelled op leaves the sequence engine non-idle: CS still&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;    &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; asserted, TX FIFO underrun. Detect that fingerprint and recover.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;    &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;    &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; One SEQIDLE sample is authoritative here: SEQIDLE only deasserts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;    &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; on a fresh IPCMD trigger, and none is pending at this point.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; !&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language&quot;&gt;self&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;seq_idle&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;        &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Force idle, de-assert CS, reset the instruction pointer,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;        &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; flush the FIFOs. The LUT and controller config survive a&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;        &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; software reset -- the init path already relies on that.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-language&quot;&gt;        self&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;software_reset&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-language&quot;&gt;    self&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;wait_idle&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;    &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; ...load the IP command, trigger IPCMD...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;    Ok&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;There’s a deliberate design decision hiding in what this fix &lt;em&gt;doesn’t&lt;&#x2F;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;&#x2F;code&gt; out of its hot path entirely. Timeout &lt;em&gt;policy&lt;&#x2F;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;&#x2F;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;&#x2F;em&gt;. The self-healing recovery is precisely what lets a caller wrap
any operation in &lt;code&gt;with_timeout&lt;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;the-payoff&quot;&gt;The payoff&lt;&#x2F;h2&gt;
&lt;p&gt;With both fixes in, the two rigs ran clean:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;flexspi-soak&lt;&#x2F;code&gt;&lt;&#x2F;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;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;flexspi-cancel-soak&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; (async + DMA): 388 rounds, &lt;strong&gt;5,044
in-flight cancellations&lt;&#x2F;strong&gt;, 3,880 clean recoveries, zero wedges, zero
data corruption.&lt;&#x2F;li&gt;
&lt;&#x2F;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;&#x2F;code&gt; firing at a moment that, a week
earlier, would have bricked the controller until the next reset.&lt;&#x2F;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;&#x2F;h2&gt;
&lt;p&gt;A few things generalize past this one chip:&lt;&#x2F;p&gt;
&lt;p&gt;A happy-path self-test proves a driver &lt;em&gt;can&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Cancel-safety is a first-class property of an async driver&lt;&#x2F;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;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Make illegal states loud.&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Seed your randomness.&lt;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;a-note-on-trusting-it&quot;&gt;A note on trusting it&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;what-this-doesn-t-cover&quot;&gt;What this doesn’t cover&lt;&#x2F;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;&#x2F;em&gt; FlexSPI block, and I’d be unsurprised if it looked nothing like
that on other silicon — the &lt;em&gt;shape&lt;&#x2F;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;&#x2F;code&gt; traits yet, though the write-size contract was
designed so it maps straight onto &lt;code&gt;WRITE_SIZE = 8&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;ERASE_SIZE = 4096&lt;&#x2F;code&gt; when it does.&lt;&#x2F;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:&#x2F;&#x2F;github.com&#x2F;embassy-rs&#x2F;embassy&#x2F;pull&#x2F;6389&quot;&gt;embassy-rs&#x2F;embassy#6389&lt;&#x2F;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;&#x2F;p&gt;
</content:encoded>
    </item>
    
    <item>
      <title>Pico de Gallo: Release Announcement</title>
      <link>https://balbi.sh/posts/pico-de-gallo-reliability-release/</link>
      <guid isPermaLink="true">https://balbi.sh/posts/pico-de-gallo-reliability-release/</guid>
      <pubDate>Mon, 22 Jun 2026 09:00:00 +0000</pubDate>
      <dc:creator>Felipe Balbi</dc:creator>
      <description>&lt;p&gt;&lt;a href=&quot;&#x2F;pico-de-gallo&#x2F;&quot;&gt;Pico de Gallo&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;</description>
      
      <content:encoded>&lt;p&gt;&lt;a href=&quot;&#x2F;pico-de-gallo&#x2F;&quot;&gt;Pico de Gallo&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;This is a &lt;strong&gt;lockstep release&lt;&#x2F;strong&gt;. The wire protocol in
&lt;code&gt;pico-de-gallo-internal&lt;&#x2F;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;&#x2F;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:&#x2F;&#x2F;balbi.sh&#x2F;posts&#x2F;pico-de-gallo-reliability-release&#x2F;#upgrade-and-compatibility&quot;&gt;upgrade notes&lt;&#x2F;a&gt; at the bottom have the full
version table.&lt;&#x2F;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;&#x2F;em&gt; could
slip past validation and corrupt decoding silently. Each crate’s share
of the fix is below.&lt;&#x2F;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;&#x2F;code&gt; 0.6.0 — the wire protocol&lt;&#x2F;h2&gt;
&lt;p&gt;This is the crate every other one depends on, and the breaking schema
bump that drives the lockstep.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GpioWaitRequest&lt;&#x2F;code&gt; gained a &lt;code&gt;timeout_ms: u32&lt;&#x2F;code&gt; field, used by all five
&lt;code&gt;gpio&#x2F;wait-*&lt;&#x2F;code&gt; endpoints (&lt;code&gt;wait-high&lt;&#x2F;code&gt;, &lt;code&gt;wait-low&lt;&#x2F;code&gt;, &lt;code&gt;wait-rising&lt;&#x2F;code&gt;,
&lt;code&gt;wait-falling&lt;&#x2F;code&gt;, &lt;code&gt;wait-any&lt;&#x2F;code&gt;). A value of &lt;code&gt;0&lt;&#x2F;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;&#x2F;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;&#x2F;em&gt; until you
power-cycled the board.&lt;&#x2F;li&gt;
&lt;li&gt;New &lt;code&gt;system&#x2F;reset-subscriptions&lt;&#x2F;code&gt; endpoint (request &lt;code&gt;()&lt;&#x2F;code&gt;, response
&lt;code&gt;u8&lt;&#x2F;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&#x2F;unsubscribe&lt;&#x2F;code&gt; would strand those pins until a power cycle. This
endpoint is the recovery path.&lt;&#x2F;li&gt;
&lt;&#x2F;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;&#x2F;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;&#x2F;code&gt; 0.6.0 — the Rust host library&lt;&#x2F;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;&#x2F;code&gt;
methods take a &lt;code&gt;std::time::Duration&lt;&#x2F;code&gt; and return
&lt;code&gt;Err(PicoDeGalloError::Endpoint(GpioError::Timeout))&lt;&#x2F;code&gt; on expiry. The
existing two-argument methods keep waiting forever by sending
&lt;code&gt;timeout_ms: 0&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;New &lt;code&gt;system_reset_subscriptions()&lt;&#x2F;code&gt; method returns the number of
subscriptions it reset. The recommended connect sequence is now
&lt;code&gt;new()&lt;&#x2F;code&gt; → &lt;code&gt;validate().await?&lt;&#x2F;code&gt; → &lt;code&gt;system_reset_subscriptions().await?&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;validate()&lt;&#x2F;code&gt; now checks &lt;code&gt;schema_major&lt;&#x2F;code&gt; in addition to
&lt;code&gt;schema_minor&lt;&#x2F;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;&#x2F;code&gt;
now carries &lt;code&gt;expected_major&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;actual_major&lt;&#x2F;code&gt;, and its &lt;code&gt;Display&lt;&#x2F;code&gt;
shows the full &lt;code&gt;MAJOR.MINOR.x&lt;&#x2F;code&gt; skew.&lt;&#x2F;li&gt;
&lt;li&gt;Fixed: &lt;code&gt;validate()&lt;&#x2F;code&gt; no longer mis-classifies transport,
postcard-decode, and frame-size errors as
&lt;code&gt;ValidateError::LegacyFirmware&lt;&#x2F;code&gt;. Only the postcard-rpc “no handler
for that key” signals (&lt;code&gt;UnknownKey&lt;&#x2F;code&gt;, &lt;code&gt;KeyTooSmall&lt;&#x2F;code&gt;) map to
&lt;code&gt;LegacyFirmware&lt;&#x2F;code&gt;; everything else routes to &lt;code&gt;Comms&lt;&#x2F;code&gt;, so you stop
being told to upgrade firmware that is already current.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;MAX_BATCH_OPS&lt;&#x2F;code&gt; and &lt;code&gt;MAX_TRANSFER_SIZE&lt;&#x2F;code&gt; are now re-exported, so you
don’t have to depend on the wire crate just to validate batch sizes.&lt;&#x2F;li&gt;
&lt;&#x2F;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;&#x2F;code&gt; 0.6.0 — the &lt;code&gt;embedded-hal&lt;&#x2F;code&gt; layer&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;New &lt;code&gt;Hal::new_validated()&lt;&#x2F;code&gt; and &lt;code&gt;Hal::new_validated_with_serial_number()&lt;&#x2F;code&gt;
constructors call &lt;code&gt;validate()&lt;&#x2F;code&gt; before returning, failing loudly on a
disconnected device or a schema mismatch. The lazy &lt;code&gt;Hal::new()&lt;&#x2F;code&gt; still
defers failures to the first RPC if that’s what you want. A standalone
&lt;code&gt;Hal::validate()&lt;&#x2F;code&gt; accessor lets you check after the fact.&lt;&#x2F;li&gt;
&lt;li&gt;New &lt;code&gt;Hal::system_reset_subscriptions() -&amp;gt; Result&amp;lt;u8, SystemHalError&amp;gt;&lt;&#x2F;code&gt;
exposes the subscription teardown that previously required dropping
down to &lt;code&gt;pico-de-gallo-lib&lt;&#x2F;code&gt;. Recommended right after
&lt;code&gt;new_validated()&lt;&#x2F;code&gt; in any app that uses GPIO subscriptions.&lt;&#x2F;li&gt;
&lt;li&gt;New &lt;code&gt;Gpio::wait_for_*_with_timeout&lt;&#x2F;code&gt; inherent async methods accept a
&lt;code&gt;Duration&lt;&#x2F;code&gt; and return &lt;code&gt;GpioError::Timeout&lt;&#x2F;code&gt; on expiry. They’re
inherent methods rather than trait methods because
&lt;code&gt;embedded-hal-async&lt;&#x2F;code&gt;’s &lt;code&gt;Wait&lt;&#x2F;code&gt; trait has no notion of a timeout; the
trait methods keep their wait-forever semantics.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;AdcChannel&lt;&#x2F;code&gt;, &lt;code&gt;AdcConfigurationInfo&lt;&#x2F;code&gt;, &lt;code&gt;GpioDirection&lt;&#x2F;code&gt;, &lt;code&gt;GpioEdge&lt;&#x2F;code&gt;,
and &lt;code&gt;GpioPull&lt;&#x2F;code&gt; are now re-exported — driver authors no longer need
&lt;code&gt;pico-de-gallo-lib&lt;&#x2F;code&gt; in their &lt;code&gt;Cargo.toml&lt;&#x2F;code&gt; just for these types.&lt;&#x2F;li&gt;
&lt;li&gt;New &lt;code&gt;HalInitError&lt;&#x2F;code&gt; and &lt;code&gt;SystemHalError&lt;&#x2F;code&gt; types, and a fix for a stale
doc-comment that referenced a &lt;code&gt;Hal::uart_set_config&lt;&#x2F;code&gt; method that
never existed.&lt;&#x2F;li&gt;
&lt;&#x2F;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;&#x2F;code&gt; 0.7.0 — the C bindings&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;New &lt;code&gt;gallo_init_strict()&lt;&#x2F;code&gt; and &lt;code&gt;gallo_init_strict_with_serial_number()&lt;&#x2F;code&gt;
call &lt;code&gt;validate()&lt;&#x2F;code&gt; internally and return &lt;code&gt;NULL&lt;&#x2F;code&gt; on device-not-found,
schema mismatch, or legacy firmware. Prefer these over the lazy
&lt;code&gt;gallo_init&lt;&#x2F;code&gt; in production C — failures surface at construct time
instead of on the first RPC.&lt;&#x2F;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;&#x2F;code&gt;
functions. &lt;code&gt;timeout_ms == 0&lt;&#x2F;code&gt; keeps the wait-forever behavior;
non-zero bounds it and returns &lt;code&gt;Status::GpioTimeout&lt;&#x2F;code&gt; (&lt;code&gt;-70&lt;&#x2F;code&gt;). These
need firmware schema 0.6+; older firmware returns
&lt;code&gt;Status::SchemaMismatch&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;New &lt;code&gt;gallo_system_reset_subscriptions()&lt;&#x2F;code&gt; with
&lt;code&gt;SystemResetSubscriptionsFailed&lt;&#x2F;code&gt; (&lt;code&gt;-69&lt;&#x2F;code&gt;).&lt;&#x2F;li&gt;
&lt;li&gt;The high-throughput primitives &lt;code&gt;gallo_spi_transfer&lt;&#x2F;code&gt;, &lt;code&gt;gallo_spi_batch&lt;&#x2F;code&gt;,
and &lt;code&gt;gallo_i2c_batch&lt;&#x2F;code&gt; are now reachable from C, via the tagged structs
&lt;code&gt;GalloSpiBatchOp&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;GalloI2cBatchOp&lt;&#x2F;code&gt;. On a per-operation failure an
optional &lt;code&gt;out_failed_op&lt;&#x2F;code&gt; receives the zero-based index of the failing
op. New status codes: &lt;code&gt;I2cBatchFailed&lt;&#x2F;code&gt; (&lt;code&gt;-66&lt;&#x2F;code&gt;), &lt;code&gt;SpiBatchFailed&lt;&#x2F;code&gt;
(&lt;code&gt;-67&lt;&#x2F;code&gt;), &lt;code&gt;SpiTransferFailed&lt;&#x2F;code&gt; (&lt;code&gt;-68&lt;&#x2F;code&gt;). The wire protocol is unchanged
here — this is pure FFI surface over existing endpoints.&lt;&#x2F;li&gt;
&lt;li&gt;All &lt;code&gt;gallo_*&lt;&#x2F;code&gt; functions now take &lt;code&gt;const PicoDeGallo *&lt;&#x2F;code&gt; for the device
handle. The ABI is unchanged, but C consumers that previously cast
away &lt;code&gt;const&lt;&#x2F;code&gt; on every call can drop those casts, and headers built
with &lt;code&gt;-Wcast-qual&lt;&#x2F;code&gt; stop warning. The handle remains &lt;code&gt;Send + Sync&lt;&#x2F;code&gt; and
interior-mutable.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;pyco-de-gallo-0-4-2-the-python-bindings&quot;&gt;&lt;code&gt;pyco-de-gallo&lt;&#x2F;code&gt; 0.4.2 — the Python bindings&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;New &lt;code&gt;pyco_de_gallo.open_strict()&lt;&#x2F;code&gt; and
&lt;code&gt;open_strict_with_serial_number(serial_number)&lt;&#x2F;code&gt; call &lt;code&gt;validate()&lt;&#x2F;code&gt;
before returning the handle and raise &lt;code&gt;RuntimeError&lt;&#x2F;code&gt; on
device-not-found, schema mismatch, or legacy firmware. Prefer these
over the lazy &lt;code&gt;open()&lt;&#x2F;code&gt; in production Python.&lt;&#x2F;li&gt;
&lt;li&gt;New &lt;code&gt;gpio_wait_for_*_with_timeout(timeout_ms: int)&lt;&#x2F;code&gt; methods — &lt;code&gt;0&lt;&#x2F;code&gt;
waits forever, non-zero raises &lt;code&gt;RuntimeError&lt;&#x2F;code&gt; on &lt;code&gt;GpioError::Timeout&lt;&#x2F;code&gt;.
Requires firmware schema 0.6+.&lt;&#x2F;li&gt;
&lt;li&gt;New &lt;code&gt;system_reset_subscriptions()&lt;&#x2F;code&gt; returns an &lt;code&gt;int&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;gallo-cli-0-7-0-the-command-line-tool&quot;&gt;&lt;code&gt;gallo&lt;&#x2F;code&gt; (CLI) 0.7.0 — the command-line tool&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gallo&lt;&#x2F;code&gt; now calls &lt;code&gt;validate()&lt;&#x2F;code&gt; at the top of every subcommand except
&lt;code&gt;list&lt;&#x2F;code&gt; and &lt;code&gt;version&lt;&#x2F;code&gt;. A schema-version mismatch is reported up front
with an actionable message that points at &lt;code&gt;gallo version&lt;&#x2F;code&gt; and tells
you to re-flash the firmware or install a matching &lt;code&gt;gallo&lt;&#x2F;code&gt;, instead
of surfacing as a confusing &lt;code&gt;CommsFailed&lt;&#x2F;code&gt; on the first RPC. &lt;code&gt;list&lt;&#x2F;code&gt; is
exempt because it doesn’t touch a connected device; &lt;code&gt;version&lt;&#x2F;code&gt; is
exempt because it &lt;em&gt;is&lt;&#x2F;em&gt; the diagnostic that reports schema skew.&lt;&#x2F;li&gt;
&lt;li&gt;Everything else is unchanged. The existing &lt;code&gt;gpio&lt;&#x2F;code&gt; subcommands
(&lt;code&gt;get&lt;&#x2F;code&gt;, &lt;code&gt;put&lt;&#x2F;code&gt;, &lt;code&gt;set-config&lt;&#x2F;code&gt;, &lt;code&gt;monitor&lt;&#x2F;code&gt;) keep working. The CLI doesn’t
expose &lt;code&gt;gpio wait-for-*&lt;&#x2F;code&gt; subcommands, so bounded waits stay available
through the Rust, C, and Python libraries.&lt;&#x2F;li&gt;
&lt;&#x2F;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;&#x2F;code&gt; 0.10.0 — the device&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gpio_wait_for_*&lt;&#x2F;code&gt; handlers now honor the per-request &lt;code&gt;timeout_ms&lt;&#x2F;code&gt;.
Non-zero values wrap embassy’s &lt;code&gt;wait_for_*_edge()&lt;&#x2F;code&gt; future in
&lt;code&gt;embassy_time::with_timeout(...)&lt;&#x2F;code&gt; and return &lt;code&gt;GpioError::Timeout&lt;&#x2F;code&gt; on
expiry; &lt;code&gt;0&lt;&#x2F;code&gt; keeps the pre-0.6 wait-forever behavior.&lt;&#x2F;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;&#x2F;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;&#x2F;code&gt; keeps debugger sessions from resetting
the chip.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;i2c_scan_handler&lt;&#x2F;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;&#x2F;li&gt;
&lt;li&gt;New &lt;code&gt;system&#x2F;reset-subscriptions&lt;&#x2F;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;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Together these close the dispatcher-wedge regression (a &lt;code&gt;gpio_wait&lt;&#x2F;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;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;upgrade-and-compatibility&quot;&gt;Upgrade and compatibility&lt;&#x2F;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;&#x2F;strong&gt;. Mixed versions won’t talk to each
other — by design, the new validation refuses rather than mis-decodes.&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Crate&lt;&#x2F;th&gt;&lt;th&gt;Old&lt;&#x2F;th&gt;&lt;th&gt;New&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pico-de-gallo-internal&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;0.5.0&lt;&#x2F;td&gt;&lt;td&gt;0.6.0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pico-de-gallo-lib&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;0.5.0&lt;&#x2F;td&gt;&lt;td&gt;0.6.0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pico-de-gallo-hal&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;0.5.0&lt;&#x2F;td&gt;&lt;td&gt;0.6.0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pico-de-gallo-ffi&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;0.6.0&lt;&#x2F;td&gt;&lt;td&gt;0.7.0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;gallo&lt;&#x2F;code&gt; (CLI)&lt;&#x2F;td&gt;&lt;td&gt;0.6.0&lt;&#x2F;td&gt;&lt;td&gt;0.7.0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pyco-de-gallo&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;0.2.0&lt;&#x2F;td&gt;&lt;td&gt;0.4.2&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pico-de-gallo-firmware&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;0.9.0&lt;&#x2F;td&gt;&lt;td&gt;0.10.0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;After flashing, point the host at the device and confirm the schema
lines up:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellsession&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$&lt;&#x2F;span&gt;&lt;span&gt; gallo version&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;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;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Rust library: &lt;code&gt;PicoDeGallo::new()&lt;&#x2F;code&gt; → &lt;code&gt;validate().await?&lt;&#x2F;code&gt; →
&lt;code&gt;system_reset_subscriptions().await?&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;HAL: &lt;code&gt;Hal::new_validated()&lt;&#x2F;code&gt;, then &lt;code&gt;system_reset_subscriptions()&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;C: &lt;code&gt;gallo_init_strict()&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Python: &lt;code&gt;pyco_de_gallo.open_strict()&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;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;&#x2F;p&gt;
</content:encoded>
    </item>
    
    <item>
      <title>I3C target mode on MCXA: three things the datasheet didn&#x27;t warn me about</title>
      <link>https://balbi.sh/posts/i3c-target-mcxa-bring-up-gotchas/</link>
      <guid isPermaLink="true">https://balbi.sh/posts/i3c-target-mcxa-bring-up-gotchas/</guid>
      <pubDate>Sat, 20 Jun 2026 09:00:00 +0000</pubDate>
      <dc:creator>Felipe Balbi</dc:creator>
      <description>&lt;p&gt;I spent the last several weeks bringing up an I3C &lt;em&gt;target&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;The driver landed in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;embassy-rs&#x2F;embassy&#x2F;pull&#x2F;6160&quot;&gt;embassy-rs&#x2F;embassy#6160&lt;&#x2F;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;&#x2F;p&gt;</description>
      
      <content:encoded>&lt;p&gt;I spent the last several weeks bringing up an I3C &lt;em&gt;target&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;The driver landed in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;embassy-rs&#x2F;embassy&#x2F;pull&#x2F;6160&quot;&gt;embassy-rs&#x2F;embassy#6160&lt;&#x2F;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;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;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;&#x2F;em&gt; probably transfers;
the specific register names and timing numbers don’t.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-setup&quot;&gt;The setup&lt;&#x2F;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;&#x2F;code&gt; HAL. The bus runs SDR at
1.5 MHz push-pull &#x2F; 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;&#x2F;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;&#x2F;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;&#x2F;code&gt; or &lt;code&gt;SdrParity&lt;&#x2F;code&gt; on the target side,
and I’d go back to staring at register dumps.&lt;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;code&gt;
method on both sides — one defmt line each, every &lt;code&gt;M*&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;S*&lt;&#x2F;code&gt;
register as a raw &lt;code&gt;u32&lt;&#x2F;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;&#x2F;code&gt;. I had &lt;code&gt;BAMATCH=1&lt;&#x2F;code&gt; on the mcxa5xx example and
&lt;code&gt;BAMATCH=2&lt;&#x2F;code&gt; on mcxa2xx; the SDK had &lt;code&gt;BAMATCH=11&lt;&#x2F;code&gt;.&lt;&#x2F;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;&#x2F;code&gt; — correct, but only if
&lt;code&gt;fclk&lt;&#x2F;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;&#x2F;code&gt;: SDA contention manifesting one full transaction
downstream of the actual race.&lt;&#x2F;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 &#x2F;1 divider, and the
HAL’s math produces &lt;code&gt;BAMATCH=11&lt;&#x2F;code&gt;. &lt;code&gt;SCONFIG&lt;&#x2F;code&gt; becomes &lt;code&gt;0x140b0019&lt;&#x2F;code&gt;
exactly, matching the SDK. The soak loop went from failing inside
half a million iterations to running indefinitely clean.&lt;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;h2&gt;
&lt;p&gt;With the clocks corrected, the soak rig hit a different failure:
&lt;code&gt;SERRWARN.INVSTART&lt;&#x2F;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;&#x2F;code&gt;: arm TX DMA, set
&lt;code&gt;SCTRL.EVENT = Ibi&lt;&#x2F;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;&#x2F;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;&#x2F;code&gt;, &lt;em&gt;then&lt;&#x2F;em&gt; raise &lt;code&gt;EVENT = Ibi&lt;&#x2F;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;&#x2F;em&gt;,
then await DMA completion while the controller drains the FIFO at
the same rate DMA fills it.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Arm DMA first so the first transfer is in flight.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-language&quot;&gt;self&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;dma_arm_tx&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;buf&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; end_marker&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;await&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;?&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Raise IBI immediately. Controller starts clocking; FIFO drains&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; concurrently with DMA filling it.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-language&quot;&gt;self&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;regs&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;sctrl&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;modify&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;|&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;w&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;|&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; w&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;set_event&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;Event&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;Ibi&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Now wait for DMA. The FIFO is active throughout.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-language&quot;&gt;self&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;dma_wait_complete&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;await&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;?&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;code&gt; immediately after a successful IBI.&lt;&#x2F;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;&#x2F;code&gt;, was emitting an &lt;code&gt;async_stop&lt;&#x2F;code&gt; once the IBI
completed. That turned the next &lt;code&gt;async_read&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;The fix is to delete the &lt;code&gt;async_stop&lt;&#x2F;code&gt;. After a successful IBI the
bus is already in &lt;code&gt;NORMACT&lt;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;what-i-d-do-first-next-time&quot;&gt;What I’d do first next time&lt;&#x2F;h2&gt;
&lt;p&gt;If I were starting an I3C target bring-up on this family of silicon
tomorrow, in this order:&lt;&#x2F;p&gt;
&lt;p&gt;First, &lt;strong&gt;set the I3C functional clock to 12 MHz exactly&lt;&#x2F;strong&gt; and
verify &lt;code&gt;SCONFIG.BAMATCH&lt;&#x2F;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 &#x2F;1
gives you 12 MHz exact and matches the SDK.&lt;&#x2F;p&gt;
&lt;p&gt;Second, &lt;strong&gt;add a &lt;code&gt;dump_registers()&lt;&#x2F;code&gt; method to both controller and
target from day one&lt;&#x2F;strong&gt; and call it after init in the examples. One
defmt line each, every &lt;code&gt;M*&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;S*&lt;&#x2F;code&gt; register as a raw &lt;code&gt;u32&lt;&#x2F;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;&#x2F;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;&#x2F;strong&gt; even if it looks wrong: arm DMA,
signal the bus, &lt;em&gt;then&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;Those three would have saved me the bulk of the bring-up time.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-this-doesn-t-cover&quot;&gt;What this doesn’t cover&lt;&#x2F;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;&#x2F;code&gt; warning leaking across
frames, a racy &lt;code&gt;state == Slvreq&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
</content:encoded>
    </item>
    
    <item>
      <title>Examples that run</title>
      <link>https://balbi.sh/posts/examples-that-run/</link>
      <guid isPermaLink="true">https://balbi.sh/posts/examples-that-run/</guid>
      <pubDate>Mon, 15 Jun 2026 15:58:00 +0000</pubDate>
      <dc:creator>Felipe Balbi</dc:creator>
      <description>&lt;p&gt;Most driver crates I reach for ship an &lt;code&gt;examples&#x2F;&lt;&#x2F;code&gt; directory that I
have to &lt;em&gt;port&lt;&#x2F;em&gt; before I can read it. Wrong PAC for my dev board,
wrong linker script, a &lt;code&gt;#[entry]&lt;&#x2F;code&gt; macro from a runtime I don’t have
checked out, a &lt;code&gt;loop { wfi() }&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;A &lt;a href=&quot;&#x2F;posts&#x2F;writing-embedded-drivers-without-an-mcu&#x2F;&quot;&gt;previous post&lt;&#x2F;a&gt;
made the case for moving the bus host-side: a Pi Pico 2 acting as a
USB-attached I²C&#x2F;SPI&#x2F;GPIO adapter, so the driver crate itself stays
unchanged and runs from a &lt;code&gt;std&lt;&#x2F;code&gt; binary on your laptop. This post is
the other half. Once the bus moved, the &lt;em&gt;driver crate’s&lt;&#x2F;em&gt; &lt;code&gt;examples&#x2F;&lt;&#x2F;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:&#x2F;&#x2F;github.com&#x2F;OpenDevicePartnership&#x2F;tmp108&quot;&gt;&lt;code&gt;tmp108&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; — at the
time of writing, the only crate on crates.io that lists
&lt;code&gt;pico-de-gallo-hal&lt;&#x2F;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;&#x2F;p&gt;</description>
      
      <content:encoded>&lt;p&gt;Most driver crates I reach for ship an &lt;code&gt;examples&#x2F;&lt;&#x2F;code&gt; directory that I
have to &lt;em&gt;port&lt;&#x2F;em&gt; before I can read it. Wrong PAC for my dev board,
wrong linker script, a &lt;code&gt;#[entry]&lt;&#x2F;code&gt; macro from a runtime I don’t have
checked out, a &lt;code&gt;loop { wfi() }&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;A &lt;a href=&quot;&#x2F;posts&#x2F;writing-embedded-drivers-without-an-mcu&#x2F;&quot;&gt;previous post&lt;&#x2F;a&gt;
made the case for moving the bus host-side: a Pi Pico 2 acting as a
USB-attached I²C&#x2F;SPI&#x2F;GPIO adapter, so the driver crate itself stays
unchanged and runs from a &lt;code&gt;std&lt;&#x2F;code&gt; binary on your laptop. This post is
the other half. Once the bus moved, the &lt;em&gt;driver crate’s&lt;&#x2F;em&gt; &lt;code&gt;examples&#x2F;&lt;&#x2F;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:&#x2F;&#x2F;github.com&#x2F;OpenDevicePartnership&#x2F;tmp108&quot;&gt;&lt;code&gt;tmp108&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; — at the
time of writing, the only crate on crates.io that lists
&lt;code&gt;pico-de-gallo-hal&lt;&#x2F;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;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;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;&#x2F;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;&#x2F;h2&gt;
&lt;p&gt;Every example in &lt;code&gt;tmp108&#x2F;examples&#x2F;&lt;&#x2F;code&gt; opens with the same shape. The
&lt;code&gt;oneshot.rs&lt;&#x2F;code&gt; body, in full:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; hal&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Hal&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; i2c&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; hal&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;i2c&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; tmp&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Tmp108&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;new_with_a0_gnd&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;i2c&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; temperature&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; tmp&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;temperature&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;|&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;_&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;|&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; anyhow!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Failed to read temperature&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;?&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;println!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Temperature: &lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;temperature:.2&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; C&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;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;&#x2F;code&gt; decorator, no panic handler.
The bus is just &lt;em&gt;there&lt;&#x2F;em&gt;, the way &lt;code&gt;std::fs::File::open&lt;&#x2F;code&gt; is just there
in a Linux program.&lt;&#x2F;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;&#x2F;code&gt;:
the constructor name &lt;em&gt;is&lt;&#x2F;em&gt; the pinout. A0 tied to GND means I²C
address &lt;code&gt;0x48&lt;&#x2F;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;&#x2F;code&gt;, &lt;code&gt;new_with_a0_vplus&lt;&#x2F;code&gt;, &lt;code&gt;new_with_a0_sda&lt;&#x2F;code&gt;,
&lt;code&gt;new_with_a0_scl&lt;&#x2F;code&gt;. There is no &lt;code&gt;new(addr: u8)&lt;&#x2F;code&gt;. There is no way to
construct a &lt;code&gt;Tmp108&lt;&#x2F;code&gt; whose address doesn’t correspond to a real
wiring choice on the part.&lt;&#x2F;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;&#x2F;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;&#x2F;code&gt; for
&lt;code&gt;_a0_vplus&lt;&#x2F;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;&#x2F;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;&#x2F;h2&gt;
&lt;p&gt;There are five files in &lt;code&gt;tmp108&#x2F;examples&#x2F;&lt;&#x2F;code&gt;: &lt;code&gt;oneshot.rs&lt;&#x2F;code&gt;,
&lt;code&gt;continuous.rs&lt;&#x2F;code&gt;, &lt;code&gt;alert_comparator.rs&lt;&#x2F;code&gt;, &lt;code&gt;alert_interrupt.rs&lt;&#x2F;code&gt;, and
&lt;code&gt;sensor_trait.rs&lt;&#x2F;code&gt;. Each one demonstrates a thing the TMP108 &lt;em&gt;does&lt;&#x2F;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;&#x2F;code&gt; trait integration. Not
&lt;code&gt;new_with_a0_gnd.rs&lt;&#x2F;code&gt;, &lt;code&gt;new_with_a0_vplus.rs&lt;&#x2F;code&gt;, &lt;code&gt;new_with_a0_sda.rs&lt;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;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:&#x2F;&#x2F;docs.rs&#x2F;tmp108&quot;&gt;docs.rs&lt;&#x2F;a&gt; page. Two different audiences, two
different artifacts, and they don’t have to compete for space in the
same file.&lt;&#x2F;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:&#x2F;&#x2F;github.com&#x2F;OpenDevicePartnership&#x2F;tmp108#gotchas&quot;&gt;README’s Gotchas
section&lt;&#x2F;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;&#x2F;code&gt;), but because
the &lt;em&gt;chip&lt;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;same-file-both-flavors&quot;&gt;Same file, both flavors&lt;&#x2F;h2&gt;
&lt;p&gt;The TMP108 driver ships both a blocking &lt;code&gt;Tmp108&lt;&#x2F;code&gt; and an async
&lt;code&gt;AsyncTmp108&lt;&#x2F;code&gt;, gated on the &lt;code&gt;async&lt;&#x2F;code&gt; Cargo feature. The straightforward
thing to do would have been &lt;code&gt;oneshot_blocking.rs&lt;&#x2F;code&gt; and
&lt;code&gt;oneshot_async.rs&lt;&#x2F;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;&#x2F;code&gt; file has &lt;em&gt;two&lt;&#x2F;em&gt; &lt;code&gt;main()&lt;&#x2F;code&gt; functions in it, picked between
by &lt;code&gt;cfg&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;cfg&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;not&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;feature &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;async&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; main&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;    &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; ... blocking body&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;cfg&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;feature &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;async&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;tokio&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;main&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;async&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; fn&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; main&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;    &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; ... async body&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;code&gt;, the &lt;code&gt;#[tokio::main]&lt;&#x2F;code&gt; macro, and the
import that picks &lt;code&gt;AsyncTmp108&lt;&#x2F;code&gt; instead of &lt;code&gt;Tmp108&lt;&#x2F;code&gt;. The reader sees
it, side by side, in the smallest unit of code that demonstrates the
difference.&lt;&#x2F;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;&#x2F;code&gt; functions and at least one
pair of &lt;code&gt;#[cfg]&lt;&#x2F;code&gt; arms. The payoff is that blocking&#x2F;async parity is
&lt;em&gt;observable in the source&lt;&#x2F;em&gt;, not just claimed in the README. Building
with default features picks the blocking &lt;code&gt;main&lt;&#x2F;code&gt;; building with
&lt;code&gt;--features async&lt;&#x2F;code&gt; picks the async one. (&lt;code&gt;continuous.rs&lt;&#x2F;code&gt;,
&lt;code&gt;alert_comparator.rs&lt;&#x2F;code&gt;, and &lt;code&gt;alert_interrupt.rs&lt;&#x2F;code&gt; are async-only,
because the underlying API is; their blocking &lt;code&gt;main&lt;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;code&gt;’s header in full:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;! TMP108 one-shot conversion example.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;! # Hardware&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;! - Pico de Gallo USB-attached host adapter&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;! - TMP108 on the default I2C bus, A0 → GND (address `0x48`)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;! # Cargo features&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;! Works with default features (blocking). Building with `--features async`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;! produces the async variant.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;! # Register interactions&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;! Single read of the temperature register at address `0x00`.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;# Hardware&lt;&#x2F;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;&#x2F;code&gt; says how to flip blocking&#x2F;async
— default features for blocking, &lt;code&gt;--features async&lt;&#x2F;code&gt; for async.
&lt;code&gt;# Register interactions&lt;&#x2F;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;&#x2F;code&gt;, that’s a single read of the temperature register at
address &lt;code&gt;0x00&lt;&#x2F;code&gt;. For &lt;code&gt;continuous.rs&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;what-this-doesn-t-cover&quot;&gt;What this doesn’t cover&lt;&#x2F;h2&gt;
&lt;p&gt;A few honest limits before I oversell any of this.&lt;&#x2F;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;&#x2F;em&gt; I²C&#x2F;SPI&#x2F;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;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;tmp108&lt;&#x2F;code&gt; is also one crate. At the time of writing it is the &lt;em&gt;only&lt;&#x2F;em&gt;
crate on crates.io that depends on &lt;code&gt;pico-de-gallo-hal&lt;&#x2F;code&gt; — &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;pico-de-gallo-hal&#x2F;reverse_dependencies&quot;&gt;the
reverse-deps
page&lt;&#x2F;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;&#x2F;a&gt;&lt;&#x2F;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;&#x2F;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&#x2F;&lt;&#x2F;code&gt; directory that reads like the chip’s
datasheet, not like the library’s &lt;code&gt;impl&lt;&#x2F;code&gt; block — and the
datasheet’s grain is the right grain for the audience the examples
are written for.&lt;&#x2F;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;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;section&gt;
</content:encoded>
    </item>
    
    <item>
      <title>Waking up without a critical section</title>
      <link>https://balbi.sh/posts/lockless-atomic-waker-embassy/</link>
      <guid isPermaLink="true">https://balbi.sh/posts/lockless-atomic-waker-embassy/</guid>
      <pubDate>Sat, 13 Jun 2026 21:38:00 +0000</pubDate>
      <dc:creator>Felipe Balbi</dc:creator>
      <description>&lt;p&gt;A GPIO interrupt fires. The handler’s job is small: flip a status
bit, call &lt;code&gt;waker.wake()&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;Until you look at what &lt;code&gt;waker.wake()&lt;&#x2F;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;&#x2F;code&gt; in
&lt;code&gt;embassy-sync&lt;&#x2F;code&gt; was a typedef-style alias for
&lt;code&gt;GenericAtomicWaker&amp;lt;CriticalSectionRawMutex&amp;gt;&lt;&#x2F;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;&#x2F;p&gt;</description>
      
      <content:encoded>&lt;p&gt;A GPIO interrupt fires. The handler’s job is small: flip a status
bit, call &lt;code&gt;waker.wake()&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;Until you look at what &lt;code&gt;waker.wake()&lt;&#x2F;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;&#x2F;code&gt; in
&lt;code&gt;embassy-sync&lt;&#x2F;code&gt; was a typedef-style alias for
&lt;code&gt;GenericAtomicWaker&amp;lt;CriticalSectionRawMutex&amp;gt;&lt;&#x2F;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;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;embassy-rs&#x2F;embassy&#x2F;pull&#x2F;6111&quot;&gt;embassy-rs&#x2F;embassy#6111&lt;&#x2F;a&gt;
replaces that. The new &lt;code&gt;AtomicWaker&lt;&#x2F;code&gt; is a port of
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;futures&#x2F;latest&#x2F;futures&#x2F;task&#x2F;struct.AtomicWaker.html&quot;&gt;&lt;code&gt;futures::task::AtomicWaker&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;
— the one that has been doing this on &lt;code&gt;std&lt;&#x2F;code&gt; targets for years —
adapted for &lt;code&gt;no_std&lt;&#x2F;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;&#x2F;code&gt; already required callers to
honor. The old behavior is kept verbatim under a new name,
&lt;code&gt;CriticalSectionWaker&lt;&#x2F;code&gt;, for the chips and the call sites that still
want it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-the-type-used-to-be&quot;&gt;What the type used to be&lt;&#x2F;h2&gt;
&lt;p&gt;The pre-#6111 definition is essentially one line:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; type&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; AtomicWaker&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; GenericAtomicWaker&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;CriticalSectionRawMutex&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;GenericAtomicWaker&amp;lt;M&amp;gt;&lt;&#x2F;code&gt; wraps an &lt;code&gt;Option&amp;lt;Waker&amp;gt;&lt;&#x2F;code&gt; in a
&lt;code&gt;Mutex&amp;lt;M, Cell&amp;lt;...&amp;gt;&amp;gt;&lt;&#x2F;code&gt;. With &lt;code&gt;M = CriticalSectionRawMutex&lt;&#x2F;code&gt;, every
&lt;code&gt;register()&lt;&#x2F;code&gt; and every &lt;code&gt;wake()&lt;&#x2F;code&gt; calls &lt;code&gt;critical_section::with(...)&lt;&#x2F;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;&#x2F;code&gt; — but the whole CPU is
now refusing to acknowledge anything else for the duration.&lt;&#x2F;p&gt;
&lt;p&gt;That is fine when &lt;code&gt;wake()&lt;&#x2F;code&gt; is a background bookkeeping call. It is
much less fine when &lt;code&gt;wake()&lt;&#x2F;code&gt; is itself a high-priority IRQ handler
whose latency you care about, or when a &lt;em&gt;different&lt;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;the-three-state-machine&quot;&gt;The three-state machine&lt;&#x2F;h2&gt;
&lt;p&gt;The new type, in &lt;code&gt;embassy-sync&#x2F;src&#x2F;waitqueue&#x2F;atomic_waker.rs&lt;&#x2F;code&gt;,
replaces the mutex with a packed &lt;code&gt;AtomicUsize&lt;&#x2F;code&gt; and three constants:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;const&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; WAITING&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; usize&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;     =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;const&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; REGISTERING&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; usize&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0b01&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;const&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; WAKING&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; usize&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;      =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0b10&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;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;&#x2F;strong&gt;. &lt;code&gt;REGISTERING | WAKING&lt;&#x2F;code&gt;
(0b11) is a legal, intentional state of the machine. It means a
&lt;code&gt;register()&lt;&#x2F;code&gt; is mid-flight &lt;em&gt;and&lt;&#x2F;em&gt; a &lt;code&gt;wake()&lt;&#x2F;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;&#x2F;code&gt; or &lt;code&gt;WAKING&lt;&#x2F;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;&#x2F;code&gt; that there is wake duty waiting for it on
the way out.&lt;&#x2F;p&gt;
&lt;p&gt;The wake side is short and worth showing in full:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;match&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language&quot;&gt; self&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;state&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;fetch_or&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;WAKING&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; AcqRel&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;    WAITING&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;        &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; We own the cell. Fire the wake, release.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        unsafe&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;            if&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; let&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Some&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;w&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language&quot;&gt;self&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;waker&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;                w&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;wake_by_ref&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-language&quot;&gt;        self&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;state&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;swap&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;WAITING&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Release&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;    _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;        &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; Somebody else owns the cell. They&amp;#39;ll deliver the wake.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Two arms, and that is the whole thing. If the prior state was
&lt;code&gt;WAITING&lt;&#x2F;code&gt;, we are now the sole owner of the cell via the &lt;code&gt;WAKING&lt;&#x2F;code&gt;
bit; we invoke the stored waker by reference, then drop the bit. If
the prior state was &lt;code&gt;REGISTERING&lt;&#x2F;code&gt;, the OR has produced
&lt;code&gt;REGISTERING | WAKING&lt;&#x2F;code&gt;, and we deliberately do nothing else,
because the in-flight &lt;code&gt;register()&lt;&#x2F;code&gt; will see our bit on its way out
and deliver the wake itself.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-fetch-or-and-not-compare-exchange&quot;&gt;Why fetch_or and not compare_exchange&lt;&#x2F;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;&#x2F;code&gt;? It’s
the safer default, the one you reach for first.&lt;&#x2F;p&gt;
&lt;p&gt;It would be silently wrong here. Consider the race:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;A task calls &lt;code&gt;register(cx.waker())&lt;&#x2F;code&gt;. The CAS from &lt;code&gt;WAITING&lt;&#x2F;code&gt; to
&lt;code&gt;REGISTERING&lt;&#x2F;code&gt; succeeds; we now own the cell and start installing
the new waker.&lt;&#x2F;li&gt;
&lt;li&gt;Mid-installation, an interrupt fires and calls &lt;code&gt;wake()&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;If &lt;code&gt;wake()&lt;&#x2F;code&gt; did &lt;code&gt;compare_exchange(WAITING, WAKING, ...)&lt;&#x2F;code&gt;, that CAS
would observe &lt;code&gt;REGISTERING&lt;&#x2F;code&gt;, fail, and &lt;em&gt;silently return&lt;&#x2F;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;&#x2F;code&gt; would then finish, CAS
its own state cleanly back from &lt;code&gt;REGISTERING&lt;&#x2F;code&gt; to &lt;code&gt;WAITING&lt;&#x2F;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;&#x2F;em&gt; on the registers isn’t producing a task wakeup.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;fetch_or(WAKING, AcqRel)&lt;&#x2F;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;&#x2F;code&gt;
tries to release with &lt;code&gt;compare_exchange(REGISTERING, WAITING, ...)&lt;&#x2F;code&gt;
on its way out; that CAS will &lt;em&gt;fail&lt;&#x2F;em&gt; exactly when a wake raced it,
because the observed state is now &lt;code&gt;REGISTERING | WAKING&lt;&#x2F;code&gt;, not
&lt;code&gt;REGISTERING&lt;&#x2F;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;&#x2F;code&gt;, and invokes the waker &lt;em&gt;after&lt;&#x2F;em&gt; releasing the cell — so
that a user waker that re-enters &lt;code&gt;register()&lt;&#x2F;code&gt; or &lt;code&gt;wake()&lt;&#x2F;code&gt; on the
same &lt;code&gt;AtomicWaker&lt;&#x2F;code&gt; from inside &lt;code&gt;wake()&lt;&#x2F;code&gt; cannot deadlock against
our own state.&lt;&#x2F;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;&#x2F;code&gt;
is doing more than “atomic OR” here; it is the data structure that
makes the race path correct.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-changed-in-the-contract&quot;&gt;What changed in the contract&lt;&#x2F;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;&#x2F;code&gt; now wakes the
&lt;em&gt;previously registered&lt;&#x2F;em&gt; waker if you replace it with a different
one. This matches what
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;embassy-sync&#x2F;latest&#x2F;embassy_sync&#x2F;waitqueue&#x2F;struct.WakerRegistration.html&quot;&gt;&lt;code&gt;WakerRegistration&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;
has always done. The pre-#6111 &lt;code&gt;AtomicWaker&lt;&#x2F;code&gt; would silently drop
the old waker on the floor.&lt;&#x2F;p&gt;
&lt;p&gt;The reason this is safe is that &lt;code&gt;Future::poll&lt;&#x2F;code&gt; is already
documented to require it. The relevant line in the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;future&#x2F;trait.Future.html#tymethod.poll&quot;&gt;&lt;code&gt;Future::poll&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;
contract:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that on multiple calls to &lt;code&gt;poll&lt;&#x2F;code&gt;, only the &lt;code&gt;Waker&lt;&#x2F;code&gt; from the
&lt;code&gt;Context&lt;&#x2F;code&gt; passed to the most recent call should be scheduled to
receive a wakeup.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;A correctly-written driver already re-registers on every poll,
because the &lt;code&gt;Waker&lt;&#x2F;code&gt; it gets on the &lt;em&gt;n&lt;&#x2F;em&gt;-th poll is not guaranteed
to be the one it got on the &lt;em&gt;(n-1)&lt;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;two-siblings-now-on-purpose&quot;&gt;Two siblings now, on purpose&lt;&#x2F;h2&gt;
&lt;p&gt;The previous behavior didn’t disappear. It moved into its own
module, &lt;code&gt;embassy-sync&#x2F;src&#x2F;waitqueue&#x2F;critical_section_waker.rs&lt;&#x2F;code&gt;, and
got a name that says what it is:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; struct&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; CriticalSectionWaker&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;    waker&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; GenericAtomicWaker&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;CriticalSectionRawMutex&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is a verbatim preservation of the old &lt;code&gt;AtomicWaker&lt;&#x2F;code&gt; body —
the same &lt;code&gt;wake_by_ref + restore&lt;&#x2F;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;&#x2F;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;&#x2F;code&gt; wakes evicted predecessors; &lt;code&gt;GenericAtomicWaker&amp;lt;M&amp;gt;&lt;&#x2F;code&gt;
does not, and &lt;code&gt;CriticalSectionWaker&lt;&#x2F;code&gt; (being a wrapper around it)
does not either. Review caught an earlier commit that tried to
bring &lt;code&gt;GenericAtomicWaker&lt;&#x2F;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;&#x2F;code&gt;; callers who want the old have the type they were
already using.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;thumbv6-doesn-t-have-the-atomics&quot;&gt;thumbv6 doesn’t have the atomics&lt;&#x2F;h2&gt;
&lt;p&gt;The lockless state machine assumes you can do
&lt;code&gt;AtomicUsize::{compare_exchange, swap, fetch_or}&lt;&#x2F;code&gt; on the target.
Cortex-M0&#x2F;M0+ (thumbv6m) can’t — no LDREX&#x2F;STREX in the instruction
set, so the LLVM atomics for anything past naked load&#x2F;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;&#x2F;code&gt; cfg).&lt;&#x2F;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;&#x2F;code&gt;, and on every other target make
&lt;code&gt;AtomicWaker&lt;&#x2F;code&gt; a type alias for &lt;code&gt;CriticalSectionWaker&lt;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;what-the-wake-path-no-longer-does&quot;&gt;What the wake path no longer does&lt;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;code&gt;-class atomics —
which is most of them — the wake path of &lt;code&gt;AtomicWaker&lt;&#x2F;code&gt; no longer
disables interrupts. An IRQ handler that calls &lt;code&gt;waker.wake()&lt;&#x2F;code&gt;
doesn’t enter a critical section, doesn’t pay the enter&#x2F;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;&#x2F;code&gt; op or two
and a &lt;code&gt;wake_by_ref&lt;&#x2F;code&gt; call. On chips that &lt;em&gt;can’t&lt;&#x2F;em&gt; do the atomics, the
fallback is the type that was already in the tree; nothing
regresses.&lt;&#x2F;p&gt;
&lt;p&gt;None of this is novel. &lt;code&gt;futures::task::AtomicWaker&lt;&#x2F;code&gt; has been doing
this on &lt;code&gt;std&lt;&#x2F;code&gt; for years, and the algorithm here is a direct port.
What changes is that &lt;code&gt;embassy-sync&lt;&#x2F;code&gt; — and every embassy driver
that uses &lt;code&gt;AtomicWaker&lt;&#x2F;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;&#x2F;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;&#x2F;code&gt; already required callers to honor.&lt;&#x2F;p&gt;
</content:encoded>
    </item>
    
    <item>
      <title>What did the boot ROM just do to my RAM?</title>
      <link>https://balbi.sh/posts/rambo-rom-collateral-damage/</link>
      <guid isPermaLink="true">https://balbi.sh/posts/rambo-rom-collateral-damage/</guid>
      <pubDate>Thu, 11 Jun 2026 09:00:00 +0000</pubDate>
      <dc:creator>Felipe Balbi</dc:creator>
      <description>&lt;p&gt;A while back I was trying to get &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;embassy-rs&#x2F;teleprobe&quot;&gt;teleprobe&lt;&#x2F;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;&#x2F;p&gt;</description>
      
      <content:encoded>&lt;p&gt;A while back I was trying to get &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;embassy-rs&#x2F;teleprobe&quot;&gt;teleprobe&lt;&#x2F;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;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;That experience is why I wrote &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;felipebalbi&#x2F;rambo&quot;&gt;&lt;code&gt;rambo&lt;&#x2F;code&gt;&lt;&#x2F;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;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-rambo-actually-does&quot;&gt;What rambo actually does&lt;&#x2F;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;&#x2F;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;&#x2F;code&gt;), ONES (filled with &lt;code&gt;0xFFFFFFFF&lt;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;why-writing-the-address-as-data-matters&quot;&gt;Why writing the address as data matters&lt;&#x2F;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;&#x2F;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;&#x2F;code&gt; back
from two different addresses and not notice. With &lt;code&gt;addr-as-data&lt;&#x2F;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;&#x2F;em&gt; happens to
equal some &lt;em&gt;other&lt;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;a-small-tour-of-the-optional-modes&quot;&gt;A small tour of the optional modes&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--fingerprint&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; classifies the &lt;em&gt;kind&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--dual-pattern&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; writes &lt;code&gt;addr&lt;&#x2F;code&gt; in one reset cycle and &lt;code&gt;~addr&lt;&#x2F;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;&#x2F;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;&#x2F;em&gt; (the ROM left it alone) versus &lt;em&gt;coincidentally
the same as my pattern&lt;&#x2F;em&gt; (extraordinarily unlikely with addr-as-data,
but possible in pathological cases).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--write-readback&lt;&#x2F;code&gt;&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--reset-cycles N&lt;&#x2F;code&gt;&lt;&#x2F;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;&#x2F;em&gt; be the same every
boot but isn’t, and you need evidence before you can start arguing
about it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-ci-bit&quot;&gt;The CI bit&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;--json &amp;lt;path&amp;gt;&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;--expectations &amp;lt;path&amp;gt;&lt;&#x2F;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;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;main_sram_crash_dump_area&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type&quot;&gt;range&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type&quot;&gt;start&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;0x20030000&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type&quot;&gt;end&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;0x20031000&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;safe&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-type&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type&quot;&gt;rationale&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-type&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Reserved for crash-dump recovery after watchdog reset.&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The clauses are &lt;code&gt;expect: &amp;lt;class&amp;gt;&lt;&#x2F;code&gt; (every block must be that class),
&lt;code&gt;expect_any_of: [&amp;lt;class&amp;gt;, ...]&lt;&#x2F;code&gt; (every block must be one of these), and
&lt;code&gt;expect_not: &amp;lt;class&amp;gt;&lt;&#x2F;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;&#x2F;em&gt; any probe I&#x2F;O happens, so a typo can’t
brick a run.&lt;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;what-rambo-isn-t&quot;&gt;What rambo isn’t&lt;&#x2F;h2&gt;
&lt;p&gt;A few honest limits before you reach for it.&lt;&#x2F;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;&#x2F;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;&#x2F;code&gt; your own).&lt;&#x2F;p&gt;
&lt;p&gt;Some CMSIS-Pack descriptions list code-bus &lt;em&gt;aliases&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;And “SAFE” only means “the ROM didn’t write here on &lt;em&gt;this&lt;&#x2F;em&gt; chip rev,
with &lt;em&gt;this&lt;&#x2F;em&gt; fuse configuration, on &lt;em&gt;this&lt;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;try-it&quot;&gt;Try it&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;code&gt;cargo install rambo&lt;&#x2F;code&gt;, or grab a pre-built binary from the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;felipebalbi&#x2F;rambo&#x2F;releases&quot;&gt;releases page&lt;&#x2F;a&gt; for
Linux, macOS (Intel + Apple Silicon), or Windows. Source is on
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;felipebalbi&#x2F;rambo&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;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;&#x2F;p&gt;
</content:encoded>
    </item>
    
    <item>
      <title>Writing embedded drivers without an MCU</title>
      <link>https://balbi.sh/posts/writing-embedded-drivers-without-an-mcu/</link>
      <guid isPermaLink="true">https://balbi.sh/posts/writing-embedded-drivers-without-an-mcu/</guid>
      <pubDate>Wed, 10 Jun 2026 16:00:00 +0000</pubDate>
      <dc:creator>Felipe Balbi</dc:creator>
      <description>&lt;p&gt;Let me clarify the title up front: there &lt;em&gt;is&lt;&#x2F;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;&#x2F;code&gt; binary
that runs on my laptop, against the real sensor, over a real bus.&lt;&#x2F;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;&#x2F;code&gt;.&lt;&#x2F;p&gt;</description>
      
      <content:encoded>&lt;p&gt;Let me clarify the title up front: there &lt;em&gt;is&lt;&#x2F;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;&#x2F;code&gt; binary
that runs on my laptop, against the real sensor, over a real bus.&lt;&#x2F;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;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;h2 id=&quot;the-loop-i-m-trying-to-escape&quot;&gt;The loop I’m trying to escape&lt;&#x2F;h2&gt;
&lt;p&gt;The traditional embedded dev loop looks something like this:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Edit a line of code.&lt;&#x2F;li&gt;
&lt;li&gt;Wait for &lt;code&gt;cargo build --target=thumbv8m.main-none-eabihf&lt;&#x2F;code&gt; to
finish.&lt;&#x2F;li&gt;
&lt;li&gt;Reflash the chip (a few seconds with a probe, longer over the
bootloader).&lt;&#x2F;li&gt;
&lt;li&gt;Either power-cycle the board or punch the reset button.&lt;&#x2F;li&gt;
&lt;li&gt;Squint at &lt;code&gt;defmt&lt;&#x2F;code&gt; output through a probe.&lt;&#x2F;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;&#x2F;li&gt;
&lt;li&gt;Goto 1.&lt;&#x2F;li&gt;
&lt;&#x2F;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;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;So: why not skip all of that, and write the protocol logic somewhere
where iteration is free?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;enter-pico-de-gallo&quot;&gt;Enter Pico de Gallo&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a href=&quot;&#x2F;pico-de-gallo&#x2F;&quot;&gt;&lt;em&gt;Pico de Gallo&lt;&#x2F;em&gt;&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;A few things it &lt;em&gt;isn’t&lt;&#x2F;em&gt;, because it gets confused with each of these:&lt;&#x2F;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;&#x2F;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:&#x2F;&#x2F;docs.rs&#x2F;postcard-rpc&#x2F;&quot;&gt;&lt;code&gt;postcard-rpc&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, and the host side
is a real Rust crate with proper types and async support.&lt;&#x2F;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;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;What it actually does is let you write this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;use&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt; pico_de_gallo_hal&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;Hal&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;use&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt; embedded_hal&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;i2c&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;I2c&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; hal&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Hal&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; i2c&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; hal&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;i2c&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; buf&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;i2c&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;write_read&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;0x48&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;0x00&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; buf&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;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;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The Pico de Gallo book has &lt;a href=&quot;&#x2F;pico-de-gallo&#x2F;&quot;&gt;the full setup&lt;&#x2F;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;&#x2F;code&gt; CLI installed.&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;Did the chip enumerate at the address I think it did?&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; gallo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; i2c&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; scan&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;╭────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────╮&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;│&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;    │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;  0&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;  1&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;  2&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;  3&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;  4&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;  5&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;  6&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;  7&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;  8&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;  9&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  a&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  b&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  c&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  d&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  e&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  f&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;├────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;│&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; RR&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; RR&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; RR&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; RR&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; RR&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; RR&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; RR&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; RR&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;│&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;│&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;│&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;│&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 48&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;│&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 5&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;│&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 6&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;│&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 7&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; RR&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; RR&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; RR&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; RR&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; RR&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; RR&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; RR&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; RR&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;╰────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────╯&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;Can I read its temperature register?&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; gallo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; i2c&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; write-read&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-address&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0x48&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-bytes&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0x00&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;-count&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;0x18&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0x40&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;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&#x2F;LSB. So 0x1840 → 0x184 → 388 → 388 ×
0.0625 → &lt;strong&gt;24.25 °C&lt;&#x2F;strong&gt;. The room I’m in is about 24 °C.&lt;&#x2F;p&gt;
&lt;p&gt;Three commands, no Rust, and I know:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;My wiring works.&lt;&#x2F;li&gt;
&lt;li&gt;The address I expected is the address the chip enumerated at.&lt;&#x2F;li&gt;
&lt;li&gt;The data format matches the datasheet.&lt;&#x2F;li&gt;
&lt;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;reading-it-from-rust&quot;&gt;Reading it from Rust&lt;&#x2F;h2&gt;
&lt;p&gt;Now for the actual program. The &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;tmp108&quot;&gt;&lt;code&gt;tmp108&lt;&#x2F;code&gt;
crate&lt;&#x2F;a&gt; on crates.io already exists
— it’s a regular &lt;code&gt;embedded-hal&lt;&#x2F;code&gt; driver, &lt;code&gt;#[no_std]&lt;&#x2F;code&gt;, written for
anything that implements &lt;code&gt;embedded_hal::i2c::I2c&lt;&#x2F;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;&#x2F;code&gt; binary instead.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;dependencies&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;pico-de-gallo-hal&lt;&#x2F;span&gt;&lt;span&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;0.5&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;tmp108&lt;&#x2F;span&gt;&lt;span&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;0.5&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;use&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt; pico_de_gallo_hal&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;Hal&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;use&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt; tmp108&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;Tmp108&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; main&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt; anyhow&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; hal&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Hal&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; i2c&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; hal&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;i2c&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; tmp&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Tmp108&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;new_with_a0_gnd&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;i2c&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; temperature&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; tmp&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;temperature&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;?&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;    println!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;Temperature: &lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;temperature:.2&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; °C&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;    Ok&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That’s the whole program. &lt;code&gt;cargo run&lt;&#x2F;code&gt;, and the bytes go out, the
reply comes back, and a number prints.&lt;&#x2F;p&gt;
&lt;p&gt;The interesting thing to notice is what isn’t there. The &lt;code&gt;tmp108&lt;&#x2F;code&gt;
crate doesn’t know that &lt;code&gt;pico-de-gallo&lt;&#x2F;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;&#x2F;code&gt;,” and
&lt;code&gt;pico-de-gallo-hal&lt;&#x2F;code&gt; does. The exact same &lt;code&gt;Tmp108::new_with_a0_gnd&lt;&#x2F;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;&#x2F;code&gt; I²C implementation.&lt;&#x2F;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;&#x2F;code&gt; trait — and that’s
the whole point of the trait existing in the first place.&lt;&#x2F;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;&#x2F;code&gt;&lt;&#x2F;strong&gt; have
stable, widely-adopted &lt;code&gt;embedded-hal&lt;&#x2F;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:&#x2F;&#x2F;github.com&#x2F;OpenDevicePartnership&#x2F;tmp108&#x2F;tree&#x2F;main&#x2F;examples&quot;&gt;tmp108’s &lt;code&gt;examples&#x2F;&lt;&#x2F;code&gt;
folder&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;the-dev-loop-side-by-side&quot;&gt;The dev loop, side by side&lt;&#x2F;h2&gt;
&lt;p&gt;Here’s the same edit-and-test cycle from above, redrawn against this
program:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Edit a line of code.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;cargo run&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;Other things that just work, because it’s &lt;code&gt;std&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cargo test&lt;&#x2F;code&gt; runs against the real chip.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;cargo bench&lt;&#x2F;code&gt; measures real round-trip latency over USB.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;cargo flamegraph&lt;&#x2F;code&gt; and &lt;code&gt;perf&lt;&#x2F;code&gt; and &lt;code&gt;dtrace&lt;&#x2F;code&gt; and Instruments all
work, because it’s a normal native process.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;println!&lt;&#x2F;code&gt;, &lt;code&gt;dbg!&lt;&#x2F;code&gt;, &lt;code&gt;eprintln!&lt;&#x2F;code&gt;, &lt;code&gt;tracing&lt;&#x2F;code&gt;, &lt;code&gt;env_logger&lt;&#x2F;code&gt; — any
of them. Whatever you’d use in a normal Rust program.&lt;&#x2F;li&gt;
&lt;li&gt;The debugger you actually like — &lt;code&gt;rust-lldb&lt;&#x2F;code&gt;, &lt;code&gt;rust-gdb&lt;&#x2F;code&gt;, the
one built into your editor — attaches the way it always does.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The obvious question at this point is what the catch is. We’ll
get there.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-having-std-actually-unlocks&quot;&gt;What having &lt;code&gt;std&lt;&#x2F;code&gt; actually unlocks&lt;&#x2F;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;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Here is a small &lt;code&gt;ratatui&lt;&#x2F;code&gt; dashboard that reads the TMP108 four times
a second and plots a live sparkline of the last 60 samples
alongside running min &#x2F; avg &#x2F; max:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;balbi.sh&#x2F;posts&#x2F;writing-embedded-drivers-without-an-mcu&#x2F;dashboard.png&quot; alt=&quot;Live TMP108 dashboard rendered in the terminal: the top row shows current temperature, minimum, average, and maximum across the window; the bottom panel is a sparkline of the last 60 samples, making short-term drift and the warm-up curve visible at a glance.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The whole program is about 150 lines and uses three dependencies:
&lt;code&gt;pico-de-gallo-hal&lt;&#x2F;code&gt;, &lt;code&gt;tmp108&lt;&#x2F;code&gt;, and &lt;code&gt;ratatui&lt;&#x2F;code&gt;. The interesting bits
look like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;const&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; HISTORY&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; usize&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 60&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;const&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; SAMPLE_INTERVAL&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Duration&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Duration&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;from_millis&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;250&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; hal&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Hal&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; i2c&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; hal&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;i2c&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; tmp&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Tmp108&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;new_with_a0_gnd&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;i2c&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; samples&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; VecDeque&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;f32&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; VecDeque&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;with_capacity&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;HISTORY&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; next_sample&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Instant&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;now&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;loop&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Instant&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;now&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; next_sample&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;        let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; t&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; tmp&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;temperature&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;?&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        if&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; samples&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;len&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; HISTORY&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;            samples&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;pop_front&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;        samples&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;push_back&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;        next_sample&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt; SAMPLE_INTERVAL&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;    terminal&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;draw&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;|&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;frame&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;|&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; draw&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;frame&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;samples&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;?&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;    &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; ...event handling: quit on &amp;#39;q&amp;#39; or Esc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;draw&lt;&#x2F;code&gt; function splits the screen vertically into a stats row
and a sparkline:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; draw&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;frame&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Frame&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;_&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; samples&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;VecDeque&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;f32&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; chunks&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Layout&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;default&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;direction&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;Direction&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;Vertical&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;constraints&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;Constraint&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;Length&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt; Constraint&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;Min&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;split&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;frame&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;area&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;    frame&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;render_widget&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;stats&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;samples&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; chunks&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;    frame&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;render_widget&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;sparkline&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;samples&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; chunks&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…and &lt;code&gt;sparkline&lt;&#x2F;code&gt; rescales the float window into the integer bars
that &lt;code&gt;ratatui::Sparkline&lt;&#x2F;code&gt; wants, so even tiny drifts are visible:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; sparkline&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;samples&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;VecDeque&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;f32&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Sparkline&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;_&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; bars&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; if&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; samples&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;is_empty&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;        Vec&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; else&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;        let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; min&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; samples&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;iter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;copied&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;fold&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;f32&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;INFINITY&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt; f32&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;min&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;        let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; max&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; samples&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;iter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;copied&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;fold&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;f32&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other&quot;&gt;NEG_INFINITY&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt; f32&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;max&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;        let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; span&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;max&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; min&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;max&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;        samples&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;            .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;iter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;            .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;|&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; min&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; span&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 1000&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;round&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; u64&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;            .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;collect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;    Sparkline&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;default&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;data&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;bars&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;max&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;1000&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;style&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;Style&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;default&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;fg&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;Color&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;Yellow&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;block&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;Block&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;bordered&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;title&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; last 60 samples  (q to quit) &lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;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;&#x2F;code&gt; cleans up and exits.&lt;&#x2F;p&gt;
&lt;p&gt;Once you’re in this world, a lot of other things become “just write
the program”:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Data logging.&lt;&#x2F;strong&gt; A few lines of &lt;code&gt;serde&lt;&#x2F;code&gt; + &lt;code&gt;csv&lt;&#x2F;code&gt; and every sample
gets a wall-clock timestamp on disk. Post-process with whatever you
like — &lt;code&gt;polars&lt;&#x2F;code&gt;, &lt;code&gt;pandas&lt;&#x2F;code&gt;, &lt;code&gt;jq&lt;&#x2F;code&gt;, a quick shell pipeline:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt; mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; wtr&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt; csv&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;Writer&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;from_path&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;temps.csv&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;?&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;wtr&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;write_record&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;timestamp_ms&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;temperature_c&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;?&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; inside the loop:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;wtr&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;write_record&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;    chrono&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-namespace&quot;&gt;Utc&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;now&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;timestamp_millis&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;to_string&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;    format!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t:.4&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;?&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Bus tracing.&lt;&#x2F;strong&gt; Wrap the driver in a &lt;code&gt;tracing&lt;&#x2F;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;&#x2F;code&gt;, &lt;code&gt;tokio-console&lt;&#x2F;code&gt;, OpenTelemetry — pick your
poison.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Stress testing.&lt;&#x2F;strong&gt; Spin up a &lt;code&gt;tokio&lt;&#x2F;code&gt; runtime, share the bus through
a &lt;code&gt;Mutex&amp;lt;Bus&amp;gt;&lt;&#x2F;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;&#x2F;em&gt; do on the MCU, but it’s the kind of thing you’d never set up
just to find out.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;A&#x2F;B testing.&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Real CI.&lt;&#x2F;strong&gt; This is the one I’m most excited about. &lt;code&gt;cargo test&lt;&#x2F;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;&#x2F;pico-de-gallo&#x2F;driver&#x2F;testing.html&quot;&gt;testing
chapter&lt;&#x2F;a&gt; of the book.)&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Runnable examples in your crate.&lt;&#x2F;strong&gt; This one flips the framing.
So far we’ve talked about Pico de Gallo as something &lt;em&gt;consumers&lt;&#x2F;em&gt; of
a driver crate use. But it works just as well for the &lt;em&gt;author&lt;&#x2F;em&gt; of
the crate. Cargo lets you put a &lt;code&gt;examples&#x2F;&lt;&#x2F;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;&#x2F;code&gt; after
plugging in two cables.”&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;OpenDevicePartnership&#x2F;tmp108&#x2F;tree&#x2F;main&#x2F;examples&quot;&gt;&lt;code&gt;tmp108&lt;&#x2F;code&gt; crate’s
examples&lt;&#x2F;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;&#x2F;code&gt; trait demo — and every single one starts
with &lt;code&gt;let hal = Hal::new(); let i2c = hal.i2c();&lt;&#x2F;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;&#x2F;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;&#x2F;em&gt; the running program.&lt;&#x2F;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;&#x2F;code&gt; program, they all collapse into “use
the crate that already exists.”&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-catch&quot;&gt;The catch&lt;&#x2F;h2&gt;
&lt;p&gt;One might ask, what’s the catch? Here is the honest list.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;USB latency.&lt;&#x2F;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;&#x2F;code&gt;
pin and an interrupt-driven path for that, which Pico de Gallo also
supports via the GPIO &lt;code&gt;Wait&lt;&#x2F;code&gt; impl.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Timing is host-mediated.&lt;&#x2F;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;&#x2F;em&gt; the handler. For that you eventually need the real MCU.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Single master, single bus.&lt;&#x2F;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;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The driver still has to be &lt;code&gt;no_std&lt;&#x2F;code&gt;.&lt;&#x2F;strong&gt; Pico de Gallo lets you
exercise the &lt;em&gt;logic&lt;&#x2F;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;&#x2F;code&gt;”
step in CI to catch accidentally pulling in &lt;code&gt;std&lt;&#x2F;code&gt;. The &lt;code&gt;tmp108&lt;&#x2F;code&gt;
crate has &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;OpenDevicePartnership&#x2F;tmp108&#x2F;actions&#x2F;workflows&#x2F;nostd.yml&quot;&gt;exactly that
job&lt;&#x2F;a&gt;
running on every push for this reason.&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;&#x2F;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;&#x2F;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;&#x2F;pico-de-gallo&#x2F;&quot;&gt;book&lt;&#x2F;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:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;tmp108&quot;&gt;&lt;code&gt;tmp108&lt;&#x2F;code&gt; crate&lt;&#x2F;a&gt; used
in this post is on crates.io, and its
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;OpenDevicePartnership&#x2F;tmp108&#x2F;tree&#x2F;main&#x2F;examples&quot;&gt;&lt;code&gt;examples&#x2F;&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;
directory is full of &lt;code&gt;cargo run --example&lt;&#x2F;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;&#x2F;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;&#x2F;p&gt;
</content:encoded>
    </item>
    
    <item>
      <title>Hello, world</title>
      <link>https://balbi.sh/posts/hello-world/</link>
      <guid isPermaLink="true">https://balbi.sh/posts/hello-world/</guid>
      <pubDate>Wed, 10 Jun 2026 08:00:00 +0000</pubDate>
      <dc:creator>Felipe Balbi</dc:creator>
      <description>&lt;p&gt;This is the first post on the new version of &lt;a href=&quot;&#x2F;&quot;&gt;balbi.sh&lt;&#x2F;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:&#x2F;&#x2F;www.getzola.org&#x2F;&quot;&gt;Zola&lt;&#x2F;a&gt; and styled after
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;protesilaos&#x2F;ef-themes&quot;&gt;ef-melissa-light&lt;&#x2F;a&gt; — a warm,
legible Emacs theme by Protesilaos Stavrou. Body text is set in
&lt;strong&gt;Aporetic Serif&lt;&#x2F;strong&gt;, headings and UI in &lt;strong&gt;Aporetic Sans&lt;&#x2F;strong&gt;, and code in
&lt;strong&gt;Aporetic Sans Mono&lt;&#x2F;strong&gt; — all from the same author as the colour
palette.&lt;&#x2F;p&gt;</description>
      
      <content:encoded>&lt;p&gt;This is the first post on the new version of &lt;a href=&quot;&#x2F;&quot;&gt;balbi.sh&lt;&#x2F;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:&#x2F;&#x2F;www.getzola.org&#x2F;&quot;&gt;Zola&lt;&#x2F;a&gt; and styled after
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;protesilaos&#x2F;ef-themes&quot;&gt;ef-melissa-light&lt;&#x2F;a&gt; — a warm,
legible Emacs theme by Protesilaos Stavrou. Body text is set in
&lt;strong&gt;Aporetic Serif&lt;&#x2F;strong&gt;, headings and UI in &lt;strong&gt;Aporetic Sans&lt;&#x2F;strong&gt;, and code in
&lt;strong&gt;Aporetic Sans Mono&lt;&#x2F;strong&gt; — all from the same author as the colour
palette.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;h2 id=&quot;why-now&quot;&gt;Why now&lt;&#x2F;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;&#x2F;p&gt;
&lt;h2 id=&quot;what-to-expect&quot;&gt;What to expect&lt;&#x2F;h2&gt;
&lt;p&gt;Mostly:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Embedded Rust&lt;&#x2F;strong&gt; — Embassy, &lt;code&gt;embedded-hal&lt;&#x2F;code&gt;, async on tiny chips.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Bare-metal C&lt;&#x2F;strong&gt; — when Rust isn’t an option, which is more often
than I’d like.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Tooling&lt;&#x2F;strong&gt; — probe-rs, defmt, the linker, the assembler, the things
that quietly hold everything together.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Occasional rants&lt;&#x2F;strong&gt; about debugging hardware at 2am.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;If you want to follow along, there’s an &lt;a href=&quot;&#x2F;atom.xml&quot;&gt;Atom feed&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;code-by-the-way&quot;&gt;Code, by the way&lt;&#x2F;h2&gt;
&lt;p&gt;It looks like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span&gt;embassy_executor&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;task&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;async&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; fn&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; blinky&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; led&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt; Output&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;static&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    loop&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;        led&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;toggle&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-type&quot;&gt;        Timer&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;after_millis&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;500&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;await&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That’s all for now. More soon.&lt;&#x2F;p&gt;
</content:encoded>
    </item>
    
  </channel>
</rss>
