<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Byte Hamr Labs</title><link>https://bytehamr.com/</link><description>Build logs from Byte Hamr Labs: FPGA, retro computing, and hardware projects.</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Fri, 11 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://bytehamr.com/index.xml" rel="self" type="application/rss+xml"/><item><title>Aztec C 3.2b on one 800K disk</title><link>https://bytehamr.com/projects/apple-iie/logs/aztec-c-3-2b-on-one-800k-disk/</link><pubDate>Fri, 11 Sep 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/apple-iie/logs/aztec-c-3-2b-on-one-800k-disk/</guid><description>&lt;p&gt;I had the ProDOS release of Aztec C65 3.2b sitting around as eight 140K disk images. Swapping eight floppies to compile a file is nobody&amp;rsquo;s idea of fun, and I have a 3.5&amp;quot; drive on the IIe, so I wanted the whole thing on one 800K disk.&lt;/p&gt;
&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;AppleCommander&amp;rsquo;s &lt;code&gt;acx&lt;/code&gt; did the merging. Created an 800K ProDOS image named &lt;code&gt;/AZTEC&lt;/code&gt;, made &lt;code&gt;CC&lt;/code&gt;, &lt;code&gt;LN&lt;/code&gt; and &lt;code&gt;UTIL&lt;/code&gt; directories, and copied the tools across from each disk. Every &lt;code&gt;.LIB&lt;/code&gt; went into &lt;code&gt;LN/&lt;/code&gt; so the linker&amp;rsquo;s &lt;code&gt;CLIB&lt;/code&gt; variable finds them in one place. The shell&amp;rsquo;s &lt;code&gt;PROFILE&lt;/code&gt; assumed four separate volumes (&lt;code&gt;/system&lt;/code&gt;, &lt;code&gt;/cc&lt;/code&gt;, &lt;code&gt;/ln&lt;/code&gt;, &lt;code&gt;/util&lt;/code&gt;), so I rewrote it:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;set PATH=:/ram:/aztec:/aztec/cc:/aztec/ln:/aztec/util
set INCLUDE=/aztec/cc/include
set CLIB=/aztec/ln/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Two acx surprises. &lt;code&gt;create -f&lt;/code&gt; to copy boot blocks off the SYSTEM disk died with a null pointer, I think because there&amp;rsquo;s no BASIC.SYSTEM on it. And &lt;code&gt;write -b&lt;/code&gt; refused to write blocks 0 and 1 to the 800K image at all. Ended up reading the two boot blocks out of the SYSTEM disk and &lt;code&gt;dd&lt;/code&gt;-ing them into the first 1024 bytes of the &lt;code&gt;.po&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;The eight disks hold more than 800K, so the &lt;code&gt;.ARC&lt;/code&gt; library source archives and the DOS 3.3 target libs went on a second image. You don&amp;rsquo;t need either to compile.&lt;/p&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/apple-iie/apple2-boot.png" alt="First boot of the merged disk. Aztec C SHELL v1.99j, PROFILE banner, prompt." loading="lazy"&gt;
&lt;figcaption&gt;First boot of the merged disk. Aztec C SHELL v1.99j, PROFILE banner, prompt.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;It booted first try. Then I typed &lt;code&gt;cc exmpl.c&lt;/code&gt; and got this:&lt;/p&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/apple-iie/apple2-2.png" alt="cc ran, as ran, then the shell couldn&amp;#39;t reload itself. Any key just prints it again." loading="lazy"&gt;
&lt;figcaption&gt;cc ran, as ran, then the shell couldn&amp;rsquo;t reload itself. Any key just prints it again.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The Manx shell unloads itself to run a program and reloads &lt;code&gt;SHELL.SYSTEM&lt;/code&gt; afterward. The reload is a little native stub the shell parks at &lt;code&gt;$BD51&lt;/code&gt;, and it builds the pathname from a directory string the shell stashed at &lt;code&gt;$BC11&lt;/code&gt; plus the literal &lt;code&gt;SHELL.SYSTEM&lt;/code&gt;. That stub loops forever on failure, so a reboot is the only way out. Typing &lt;code&gt;set&lt;/code&gt; showed the problem:&lt;/p&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/apple-iie/apple2-6.png" alt="set output. HOME is /AZTEC//AZTEC/SHELL.SYSTEM, which ProDOS rejects." loading="lazy"&gt;
&lt;figcaption&gt;set output. HOME is /AZTEC//AZTEC/SHELL.SYSTEM, which ProDOS rejects.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The shell computes its home as the ProDOS prefix plus whatever is sitting at &lt;code&gt;$0280&lt;/code&gt;, the spot where the launcher leaves the name of the system file it just loaded. Manx assumed a bare filename there, which is what a 1986 Disk II boot gave them. On this machine &lt;code&gt;$0280&lt;/code&gt; had the full path, so the shell glued &lt;code&gt;/AZTEC/&lt;/code&gt; onto &lt;code&gt;/AZTEC/SHELL.SYSTEM&lt;/code&gt;, and the double slash is an invalid ProDOS pathname. Setting &lt;code&gt;HOME&lt;/code&gt; in PROFILE doesn&amp;rsquo;t help; the stub gets its copy during init, before PROFILE runs.&lt;/p&gt;
&lt;p&gt;The fix is a loader that boots first. &lt;code&gt;AZTEC.SYSTEM&lt;/code&gt; is 186 bytes of 6502: copy itself to &lt;code&gt;$0300&lt;/code&gt;, &lt;code&gt;SET_PREFIX&lt;/code&gt; to &lt;code&gt;/AZTEC/&lt;/code&gt;, &lt;code&gt;OPEN&lt;/code&gt; and &lt;code&gt;READ&lt;/code&gt; &lt;code&gt;SHELL.SYSTEM&lt;/code&gt; into &lt;code&gt;$2000&lt;/code&gt;, write a bare &lt;code&gt;SHELL.SYSTEM&lt;/code&gt; string to &lt;code&gt;$0280&lt;/code&gt;, jump. ProDOS boots the first &lt;code&gt;*.SYSTEM&lt;/code&gt; file in directory order, so I deleted &lt;code&gt;SHELL.SYSTEM&lt;/code&gt; and re-added it after the loader.&lt;/p&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/apple-iie/apple2-8.png" alt="With the loader in front. HOME is /AZTEC/ and cc comes back to the prompt." loading="lazy"&gt;
&lt;figcaption&gt;With the loader in front. HOME is /AZTEC/ and cc comes back to the prompt.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;cc&lt;/code&gt; calls &lt;code&gt;as&lt;/code&gt; on its own, so there&amp;rsquo;s no separate assemble step. &lt;code&gt;cc -A&lt;/code&gt; stops after codegen and leaves the &lt;code&gt;.asm&lt;/code&gt; for reading.&lt;/p&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/apple-iie/apple2-9.png" alt="Tail of the generated asm for hello world, then the link. 11,898 bytes total." loading="lazy"&gt;
&lt;figcaption&gt;Tail of the generated asm for hello world, then the link. 11,898 bytes total.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Eight disks total 1,116,160 bytes used. An 800K volume has 819,200.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AZTEC.po&lt;/code&gt;: 1411 blocks used, 188 free, about 94K for source and objects.&lt;/li&gt;
&lt;li&gt;Loader: 186 bytes.&lt;/li&gt;
&lt;li&gt;Hello world with &lt;code&gt;printf&lt;/code&gt;: code 11,274 bytes, data 552, uninitialised 68, total 11,898.&lt;/li&gt;
&lt;li&gt;Linking that one object against &lt;code&gt;c.lib&lt;/code&gt; (59K) off the floppy is slow enough to notice.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-broke-or-surprised-me"&gt;What broke or surprised me&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The shell&amp;rsquo;s builtins are lowercase only. &lt;code&gt;SET&lt;/code&gt; with caps lock on goes looking for a file called SET, fails, and drops into the same reload loop.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;3.2a&lt;/code&gt; in every signon banner is fine. The READ.ME says 3.2a was the last beta and 3.2b is the same binaries with the beta label removed.&lt;/li&gt;
&lt;li&gt;VED is arrow-key driven. &lt;code&gt;hjkl&lt;/code&gt; does nothing.&lt;/li&gt;
&lt;li&gt;11.9K for hello world. Aztec links whole library modules, and &lt;code&gt;printf&lt;/code&gt; drags in stdio, buffered files, the ProDOS I/O layer, the console driver and malloc. Every C call goes through helper routines and a software stack because the 6502 has nothing that looks like a C frame.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;Nothing on the C side. This was a one-off to see what the experience was like, and the answer is that Merlin is a better place to write for this machine. The disk stays useful for &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;diff&lt;/code&gt;, &lt;code&gt;hd&lt;/code&gt; and a working 800K ProDOS boot. The loader trick applies to any old system program that expects a bare name at &lt;code&gt;$0280&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>The instruction set, all 174 of them</title><link>https://bytehamr.com/projects/dino/logs/the-instruction-set-all-174-of-them/</link><pubDate>Thu, 10 Sep 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/dino/logs/the-instruction-set-all-174-of-them/</guid><description>&lt;p&gt;I&amp;rsquo;ve never written the instruction set down in one place. It lives in &lt;code&gt;microcode_gen.py&lt;/code&gt; as a table of control-word rows, and the assembler and the oracle both read it from there, so the ISA has been whatever that file said. This entry is the readable version. The opcodes, byte lengths and T-state counts below are dumped from that table, so if this page and the machine disagree the page is stale.&lt;/p&gt;
&lt;h2 id="the-shape-of-it"&gt;The shape of it&lt;/h2&gt;
&lt;p&gt;8-bit data, 16-bit addresses. Three registers: A is the accumulator and the ALU&amp;rsquo;s first operand, B is the ALU&amp;rsquo;s second operand, C is scratch. B:C doubles as an index pair with C as the low half. There&amp;rsquo;s a 16-bit stack pointer, a program counter, and an 8-LED output latch called OB, which is the only way anything gets out of the machine besides the serial card.&lt;/p&gt;
&lt;p&gt;Every instruction is a list of microcode rows in a 24-bit control word across three AT28C64Bs (U9, U15, U23). T0 is the fetch every instruction shares, then the rows run until one carries END. So a 2 T-state instruction is fetch plus one row.&lt;/p&gt;
&lt;p&gt;ISA size: 174 opcodes, 82 free. The high nibble of the opcode is its family, which is why I can read a byte off the bus and know roughly what it is.&lt;/p&gt;
&lt;h2 id="how-to-read-a-row"&gt;How to read a row&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hex&lt;/strong&gt; is the opcode byte.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;B&lt;/strong&gt; is total length in bytes, operands included. Multi-byte operands are low byte first.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;T&lt;/strong&gt; is the T-state count including the fetch.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flags&lt;/strong&gt;: &lt;code&gt;hold&lt;/code&gt; means the instruction doesn&amp;rsquo;t touch them. &lt;code&gt;CZVN&lt;/code&gt; means all four update. &lt;code&gt;ZN&lt;/code&gt; means it was a logic op: Z and N are valid, C is undefined because the &amp;lsquo;382 doesn&amp;rsquo;t specify carry out for logic functions and I&amp;rsquo;ve never scoped it. Don&amp;rsquo;t branch on C after AND, OR, XOR, CLR, SET or TST.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Notes&lt;/strong&gt; call out &lt;code&gt;clobbers B&lt;/code&gt; or &lt;code&gt;clobbers C&lt;/code&gt; where a register you didn&amp;rsquo;t name gets overwritten, &lt;code&gt;settle&lt;/code&gt; where one T-state is an idle pad, and &lt;code&gt;unrun&lt;/code&gt; where the microcode is burned but the instruction has never executed on the hardware.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Flags update on any instruction that uses the ALU and hold across everything else. Loads, moves, stores and stack traffic don&amp;rsquo;t touch them. A Z set by AND still stands at a JNZ two instructions later.&lt;/p&gt;
&lt;p&gt;The settle pads are on 23 instructions, added 2026-08-27 for a fault that turned out to be the ground return. Each one costs a T-state. Pulling them and re-running the soak is phase H work.&lt;/p&gt;
&lt;h2 id="addressing-modes"&gt;Addressing modes&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Syntax&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;How it works&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Implied&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CLR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;Operands named by the opcode.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Immediate&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LDAI n&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;One operand byte after the opcode.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Absolute&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LDA addr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;Two operand bytes, low first, loaded into MAR.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Indexed&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LDAX&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;Address is B:C. C is the low half. No carry from C into B.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SP-relative&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LDAS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Address is SP. Can&amp;rsquo;t share the stack with CALLs while doing this.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory-indirect&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LDAM ptr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;The pointer lives in memory. Operand names where the pointer is. Clobbers C, B survives.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Memory-indirect instructions are five bytes because the assembler emits the address twice, ptr and ptr+1. MAR loads only from the bus and has no increment, so the microcode reads the low byte through C, re-points MAR at the high byte, parks it in MDR and replays it.&lt;/p&gt;
&lt;h2 id="rules-you-find-out-the-hard-way"&gt;Rules you find out the hard way&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Set SP first.&lt;/strong&gt; The &amp;lsquo;169 counters have no clear, so SP is random at power-up. &lt;code&gt;LXISP&lt;/code&gt; before any push, pop, CALL or SP-relative access. Nothing warns you.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OB is never cleared.&lt;/strong&gt; The output latch has no reset and holds whatever the last program left. Write a known-wrong value first (&lt;code&gt;LDAI 0xFF; OUT&lt;/code&gt;) so a missing result shows up as missing instead of stale.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RET destroys C.&lt;/strong&gt; C holds half the return address while RET rebuilds it. Wrap it in &lt;code&gt;PUSHC&lt;/code&gt;/&lt;code&gt;POPC&lt;/code&gt; if it has to survive a call.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One branch polarity.&lt;/strong&gt; Every conditional is &amp;ldquo;taken if the flag is zero&amp;rdquo;. To branch on a flag being set, jump over a JMP. Six bytes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Carry is NOT-borrow.&lt;/strong&gt; SUB runs with carry-in set, so C=1 means A &amp;gt;= B unsigned and C=0 means A &amp;lt; B. &lt;code&gt;JNC&lt;/code&gt; after &lt;code&gt;CMP&lt;/code&gt; reads as &amp;ldquo;jump if less than&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C is the low half of B:C.&lt;/strong&gt; Always. A swapped pair is an off-by-256 that a write-then-read through the same pointer won&amp;rsquo;t show. Read the cell back by absolute address.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="0x0x-control"&gt;0x0x Control&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hex&lt;/th&gt;
&lt;th&gt;Mnemonic&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;T&lt;/th&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Flags&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;00&lt;/td&gt;
&lt;td&gt;NOP&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;nothing&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;0x00 is what blank RAM reads, so a PC that runs into it slides instead of stopping. First ran 2026-09-08.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;01&lt;/td&gt;
&lt;td&gt;RST&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;PC &amp;lt;- 0x0000&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;unrun. Clears the PC and nothing else.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="0x1x-immediate-loads"&gt;0x1x Immediate loads&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hex&lt;/th&gt;
&lt;th&gt;Mnemonic&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;T&lt;/th&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Flags&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;LDAI n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A &amp;lt;- n&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;LDBI n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;B &amp;lt;- n&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;The 0xCx forms do LDBI plus ALU in one instruction.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;LDCI n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;C &amp;lt;- n&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;Doesn&amp;rsquo;t survive a CALL.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;LXISP addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;SP &amp;lt;- addr&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;Required before any stack access.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;LXIL n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;SP low &amp;lt;- n&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;LXIH n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;SP high &amp;lt;- n&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="0x2x-memory-absolute-and-sp-relative"&gt;0x2x Memory, absolute and SP-relative&lt;/h2&gt;
&lt;p&gt;A peripheral card is memory. The I/O window is 0x4000-0x7FFF in 2K slots, so &lt;code&gt;LDA 0x4000&lt;/code&gt; reads the DIP switch.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hex&lt;/th&gt;
&lt;th&gt;Mnemonic&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;T&lt;/th&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Flags&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;LDA addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;A &amp;lt;- [addr]&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;STA addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[addr] &amp;lt;- A&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;LDB addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;B &amp;lt;- [addr]&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;LDC addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;C &amp;lt;- [addr]&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;STB addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[addr] &amp;lt;- B&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;STC addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[addr] &amp;lt;- C&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;LDAS&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;A &amp;lt;- [SP]&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;Read a stack slot without popping.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;STAS&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[SP] &amp;lt;- A&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;LDBS&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;B &amp;lt;- [SP]&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2A&lt;/td&gt;
&lt;td&gt;STBS&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[SP] &amp;lt;- B&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2B&lt;/td&gt;
&lt;td&gt;LDCS&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;C &amp;lt;- [SP]&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2C&lt;/td&gt;
&lt;td&gt;STCS&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[SP] &amp;lt;- C&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="0x3x-flow"&gt;0x3x Flow&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hex&lt;/th&gt;
&lt;th&gt;Mnemonic&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;T&lt;/th&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Flags&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;JMP addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;PC &amp;lt;- addr&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;JNZ addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;jump if Z = 0&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;CALL addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;push return, PC &amp;lt;- addr&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;Pushes PC+1, the address of its own operand bytes. RET steps over them.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;RET&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;pop PC, step over 2 bytes&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;clobbers C.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;JMPX&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;PC &amp;lt;- B:C&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;Computed jump.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;JMPSP&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;PC &amp;lt;- SP&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;JNC addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;jump if C = 0&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;After CMP: jump if A &amp;lt; B unsigned.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;td&gt;JZX&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;jump to B:C if Z = 0&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;td&gt;JCX&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;jump to B:C if C = 0&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3A&lt;/td&gt;
&lt;td&gt;JMPM ptr&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;PC &amp;lt;- [ptr]&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;clobbers C. Vector table in RAM.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3B&lt;/td&gt;
&lt;td&gt;JZM ptr&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;jump via [ptr] if Z = 0&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;clobbers C.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3C&lt;/td&gt;
&lt;td&gt;JCM ptr&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;jump via [ptr] if C = 0&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;clobbers C.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3D&lt;/td&gt;
&lt;td&gt;LDAM ptr&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;A &amp;lt;- [[ptr]]&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;clobbers C.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3E&lt;/td&gt;
&lt;td&gt;LDBM ptr&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;B &amp;lt;- [[ptr]]&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;clobbers C.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3F&lt;/td&gt;
&lt;td&gt;STAM ptr&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;[[ptr]] &amp;lt;- A&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;clobbers C.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="0x4x-alu"&gt;0x4x ALU&lt;/h2&gt;
&lt;p&gt;Two 74F382s with ripple carry. A and B feed the ALU from shadow latches that reload on every A or B load, so you never load them yourself. SHL, INR, DCR and NOT get to the ALU by staging a constant in B, so all four destroy it.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hex&lt;/th&gt;
&lt;th&gt;Mnemonic&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;T&lt;/th&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Flags&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;BIT&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A AND B, discarded&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;Mask test.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;ADD&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A &amp;lt;- A + B&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;SUB&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A &amp;lt;- A - B&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;AND&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A &amp;lt;- A AND B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;td&gt;OR&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A &amp;lt;- A OR B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;XOR&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A &amp;lt;- A XOR B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;CLR&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A &amp;lt;- 0x00&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;One byte, shorter than LDAI 0.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;SET&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A &amp;lt;- 0xFF&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;BSUB&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A &amp;lt;- B - A&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;td&gt;CMP&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A - B, discarded&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;Flags update even with no destination.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4A&lt;/td&gt;
&lt;td&gt;CMPB&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;B - A, discarded&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4B&lt;/td&gt;
&lt;td&gt;TST&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A OR A, discarded&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;Zero test on A alone.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4C&lt;/td&gt;
&lt;td&gt;SHL&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;A &amp;lt;- A + A&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;settle, clobbers B.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4D&lt;/td&gt;
&lt;td&gt;INR&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;A &amp;lt;- A + 1&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;settle, clobbers B. B staged with 0xFF, A - 0xFF.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4E&lt;/td&gt;
&lt;td&gt;DCR&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;A &amp;lt;- A - 1&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;settle, clobbers B. A + 0xFF.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4F&lt;/td&gt;
&lt;td&gt;NOT&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;A &amp;lt;- NOT A&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;settle, clobbers B. A XOR 0xFF.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="0x5x-output"&gt;0x5x Output&lt;/h2&gt;
&lt;p&gt;OB latches whatever is on the internal bus. Sourcing it from A was a microcode convention, so anything that can drive the bus can go to the display. Every variant except OUT and OUTI is unrun: the self-test reads OB, so it can&amp;rsquo;t judge an instruction whose only effect is writing OB.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hex&lt;/th&gt;
&lt;th&gt;Mnemonic&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;T&lt;/th&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Flags&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;td&gt;OUT&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;OB &amp;lt;- A&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;OUTB&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;OB &amp;lt;- B&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;td&gt;OUTC&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;OB &amp;lt;- C&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;OUTSPL&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;OB &amp;lt;- SP low&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;OUTSPH&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;OB &amp;lt;- SP high&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;56&lt;/td&gt;
&lt;td&gt;OUTPCL&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;OB &amp;lt;- PC low&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;td&gt;OUTPCH&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;OB &amp;lt;- PC high&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;td&gt;OUTI n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;OB &amp;lt;- n&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;OUTM addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;OB &amp;lt;- [addr]&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5A&lt;/td&gt;
&lt;td&gt;OUTMX&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;OB &amp;lt;- [B:C]&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5B&lt;/td&gt;
&lt;td&gt;OUTMS&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;OB &amp;lt;- [SP]&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="0x6x-stack-and-pointers"&gt;0x6x Stack and pointers&lt;/h2&gt;
&lt;p&gt;Empty-descending. SP points at the next free slot. Push stores then decrements, pop increments then loads. Every stack access copies SP into MAR first, which is where the extra two T-states go.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hex&lt;/th&gt;
&lt;th&gt;Mnemonic&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;T&lt;/th&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Flags&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;PUSHPCH&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;push PC high&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;61&lt;/td&gt;
&lt;td&gt;PUSHA&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;[SP] &amp;lt;- A, SP&amp;ndash;&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;POPA&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;SP++, A &amp;lt;- [SP]&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;63&lt;/td&gt;
&lt;td&gt;PUSHB&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;[SP] &amp;lt;- B, SP&amp;ndash;&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;POPB&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;SP++, B &amp;lt;- [SP]&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;PUSHC&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;[SP] &amp;lt;- C, SP&amp;ndash;&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;66&lt;/td&gt;
&lt;td&gt;POPC&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;SP++, C &amp;lt;- [SP]&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;67&lt;/td&gt;
&lt;td&gt;INXSP&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;SP &amp;lt;- SP + 1&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;The only 16-bit increment in the machine.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;68&lt;/td&gt;
&lt;td&gt;DCXSP&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;SP &amp;lt;- SP - 1&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;69&lt;/td&gt;
&lt;td&gt;SPHL&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;SP &amp;lt;- B:C&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6A&lt;/td&gt;
&lt;td&gt;HLSP&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;B:C &amp;lt;- SP&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6B&lt;/td&gt;
&lt;td&gt;PUSHSPL&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;push SP low&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6C&lt;/td&gt;
&lt;td&gt;POPSPL&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;pop SP low&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6D&lt;/td&gt;
&lt;td&gt;PUSHSPH&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;push SP high&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6E&lt;/td&gt;
&lt;td&gt;POPSPH&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;pop SP high&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6F&lt;/td&gt;
&lt;td&gt;PUSHPCL&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;push PC low&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="0x7x-indexed-through-bc"&gt;0x7x Indexed through B:C&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hex&lt;/th&gt;
&lt;th&gt;Mnemonic&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;T&lt;/th&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Flags&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;71&lt;/td&gt;
&lt;td&gt;LDAX&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;A &amp;lt;- [B:C]&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;STAX&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[B:C] &amp;lt;- A&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;73&lt;/td&gt;
&lt;td&gt;LDBX&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;B &amp;lt;- [B:C]&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;Overwrites the pointer&amp;rsquo;s high half.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;74&lt;/td&gt;
&lt;td&gt;STBX&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[B:C] &amp;lt;- B&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;LDCX&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;C &amp;lt;- [B:C]&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;Overwrites the pointer&amp;rsquo;s low half. Linked-list walk.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;76&lt;/td&gt;
&lt;td&gt;STCX&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[B:C] &amp;lt;- C&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;77&lt;/td&gt;
&lt;td&gt;MVIX n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;[B:C] &amp;lt;- n&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;Fill through the pointer without touching A.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;78&lt;/td&gt;
&lt;td&gt;STADDX&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[B:C] &amp;lt;- A + B&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;79&lt;/td&gt;
&lt;td&gt;STSUBX&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[B:C] &amp;lt;- A - B&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7A&lt;/td&gt;
&lt;td&gt;STBSUBX&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[B:C] &amp;lt;- B - A&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7B&lt;/td&gt;
&lt;td&gt;STANDX&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[B:C] &amp;lt;- A AND B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7C&lt;/td&gt;
&lt;td&gt;STORX&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[B:C] &amp;lt;- A OR B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7D&lt;/td&gt;
&lt;td&gt;STXORX&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[B:C] &amp;lt;- A XOR B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="0x8x-register-and-pointer-moves"&gt;0x8x Register and pointer moves&lt;/h2&gt;
&lt;p&gt;One row past the fetch. Cheapest instructions in the machine.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hex&lt;/th&gt;
&lt;th&gt;Mnemonic&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;T&lt;/th&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Flags&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;81&lt;/td&gt;
&lt;td&gt;MOVAB&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A &amp;lt;- B&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;MOVAC&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A &amp;lt;- C&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;C can&amp;rsquo;t be an ALU operand, so this is how C reaches the ALU.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;83&lt;/td&gt;
&lt;td&gt;MOVBA&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;B &amp;lt;- A&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;84&lt;/td&gt;
&lt;td&gt;MOVBC&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;B &amp;lt;- C&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;MOVCA&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;C &amp;lt;- A&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;86&lt;/td&gt;
&lt;td&gt;MOVCB&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;C &amp;lt;- B&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;87&lt;/td&gt;
&lt;td&gt;MOVASPL&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A &amp;lt;- SP low&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;MOVASPH&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A &amp;lt;- SP high&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;89&lt;/td&gt;
&lt;td&gt;MOVSPLA&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;SP low &amp;lt;- A&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8A&lt;/td&gt;
&lt;td&gt;MOVSPHA&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;SP high &amp;lt;- A&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8B&lt;/td&gt;
&lt;td&gt;MOVAPCL&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A &amp;lt;- PC low&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;Tapped at the counters&amp;rsquo; Q outputs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8C&lt;/td&gt;
&lt;td&gt;MOVAPCH&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A &amp;lt;- PC high&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="0x9x-reserved"&gt;0x9x Reserved&lt;/h2&gt;
&lt;p&gt;Held empty for opcodes that need hardware the machine doesn&amp;rsquo;t have yet: SHR, MOV A,FLAGS, ADC, SBB, and the branch families that need CW22/CW23 landed.&lt;/p&gt;
&lt;h2 id="0xax-pointer-save-and-restore-immediate-stores-computed-output"&gt;0xAx Pointer save and restore, immediate stores, computed output&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hex&lt;/th&gt;
&lt;th&gt;Mnemonic&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;T&lt;/th&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Flags&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A0&lt;/td&gt;
&lt;td&gt;LDSPL addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;SP low &amp;lt;- [addr]&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A1&lt;/td&gt;
&lt;td&gt;LDSPH addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;SP high &amp;lt;- [addr]&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A2&lt;/td&gt;
&lt;td&gt;STSPL addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[addr] &amp;lt;- SP low&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A3&lt;/td&gt;
&lt;td&gt;STSPH addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[addr] &amp;lt;- SP high&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A4&lt;/td&gt;
&lt;td&gt;STPCL addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[addr] &amp;lt;- PC low&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A5&lt;/td&gt;
&lt;td&gt;STPCH addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[addr] &amp;lt;- PC high&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A6&lt;/td&gt;
&lt;td&gt;MVI addr, n&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;[addr] &amp;lt;- n&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;Got a settle row on 2026-09-05 after PROG_test5 caught it writing a mix of the old byte and the new one.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A7&lt;/td&gt;
&lt;td&gt;MVIS n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;[SP] &amp;lt;- n&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A8&lt;/td&gt;
&lt;td&gt;OUTADD&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;OB &amp;lt;- A + B&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A9&lt;/td&gt;
&lt;td&gt;OUTSUB&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;OB &amp;lt;- A - B&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AA&lt;/td&gt;
&lt;td&gt;OUTBSUB&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;OB &amp;lt;- B - A&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AB&lt;/td&gt;
&lt;td&gt;OUTAND&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;OB &amp;lt;- A AND B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AC&lt;/td&gt;
&lt;td&gt;OUTOR&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;OB &amp;lt;- A OR B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AD&lt;/td&gt;
&lt;td&gt;OUTXOR&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;OB &amp;lt;- A XOR B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="0xbx-b-and-c-to-and-from-the-pointers"&gt;0xBx B and C to and from the pointers&lt;/h2&gt;
&lt;p&gt;With the ALU these let a routine do arithmetic on SP, or borrow SP as a second index register and hand it back.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hex&lt;/th&gt;
&lt;th&gt;Mnemonic&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;T&lt;/th&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Flags&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;B0&lt;/td&gt;
&lt;td&gt;MOVBSPL&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;B &amp;lt;- SP low&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B1&lt;/td&gt;
&lt;td&gt;MOVBSPH&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;B &amp;lt;- SP high&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B2&lt;/td&gt;
&lt;td&gt;MOVCSPL&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;C &amp;lt;- SP low&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B3&lt;/td&gt;
&lt;td&gt;MOVCSPH&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;C &amp;lt;- SP high&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B4&lt;/td&gt;
&lt;td&gt;MOVSPLB&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;SP low &amp;lt;- B&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B5&lt;/td&gt;
&lt;td&gt;MOVSPHB&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;SP high &amp;lt;- B&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B6&lt;/td&gt;
&lt;td&gt;MOVSPLC&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;SP low &amp;lt;- C&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B7&lt;/td&gt;
&lt;td&gt;MOVSPHC&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;SP high &amp;lt;- C&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B8&lt;/td&gt;
&lt;td&gt;MOVBPCL&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;B &amp;lt;- PC low&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B9&lt;/td&gt;
&lt;td&gt;MOVBPCH&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;B &amp;lt;- PC high&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BA&lt;/td&gt;
&lt;td&gt;MOVCPCL&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;C &amp;lt;- PC low&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BB&lt;/td&gt;
&lt;td&gt;MOVCPCH&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;C &amp;lt;- PC high&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;unrun&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="0xcx-alu-with-an-immediate"&gt;0xCx ALU with an immediate&lt;/h2&gt;
&lt;p&gt;Two bytes where LDBI plus the ALU op costs three. The constant comes in through B, so every one of these overwrites B unless B is the destination. Every row here carries a settle pad, which is why they&amp;rsquo;re 4 T-states and not 3.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hex&lt;/th&gt;
&lt;th&gt;Mnemonic&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;T&lt;/th&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Flags&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C0&lt;/td&gt;
&lt;td&gt;ADI n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;A &amp;lt;- A + n&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;settle, clobbers B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C1&lt;/td&gt;
&lt;td&gt;ADI_B n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;B &amp;lt;- A + n&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;settle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C2&lt;/td&gt;
&lt;td&gt;ADI_C n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;C &amp;lt;- A + n&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;settle, clobbers B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C3&lt;/td&gt;
&lt;td&gt;SUI n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;A &amp;lt;- A - n&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;settle, clobbers B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C4&lt;/td&gt;
&lt;td&gt;SUI_B n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;B &amp;lt;- A - n&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;settle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C5&lt;/td&gt;
&lt;td&gt;SUI_C n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;C &amp;lt;- A - n&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;settle, clobbers B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C6&lt;/td&gt;
&lt;td&gt;BSUI n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;A &amp;lt;- n - A&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;settle, clobbers B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C7&lt;/td&gt;
&lt;td&gt;BSUI_B n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;B &amp;lt;- n - A&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;settle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C8&lt;/td&gt;
&lt;td&gt;BSUI_C n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;C &amp;lt;- n - A&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;settle, clobbers B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C9&lt;/td&gt;
&lt;td&gt;ANI n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;A &amp;lt;- A AND n&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;settle, clobbers B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CA&lt;/td&gt;
&lt;td&gt;ANI_B n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;B &amp;lt;- A AND n&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;settle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CB&lt;/td&gt;
&lt;td&gt;ANI_C n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;C &amp;lt;- A AND n&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;settle, clobbers B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CC&lt;/td&gt;
&lt;td&gt;ORI n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;A &amp;lt;- A OR n&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;settle, clobbers B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CD&lt;/td&gt;
&lt;td&gt;ORI_B n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;B &amp;lt;- A OR n&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;settle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CE&lt;/td&gt;
&lt;td&gt;ORI_C n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;C &amp;lt;- A OR n&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;settle, clobbers B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CF&lt;/td&gt;
&lt;td&gt;XRI n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;A &amp;lt;- A XOR n&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;settle, clobbers B&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="0xdx-immediate-alu-continued-and-results-into-b-or-c"&gt;0xDx Immediate ALU continued, and results into B or C&lt;/h2&gt;
&lt;p&gt;Where the result lands decides what survives. Into B, it becomes the next op&amp;rsquo;s second operand. Into C, neither operand is touched.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hex&lt;/th&gt;
&lt;th&gt;Mnemonic&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;T&lt;/th&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Flags&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;D0&lt;/td&gt;
&lt;td&gt;XRI_B n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;B &amp;lt;- A XOR n&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;settle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D1&lt;/td&gt;
&lt;td&gt;XRI_C n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;C &amp;lt;- A XOR n&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;settle, clobbers B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D2&lt;/td&gt;
&lt;td&gt;CPI n&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;A - n, discarded&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;settle, clobbers B. &lt;code&gt;CPI n; JNC&lt;/code&gt; is compare-and-branch in four bytes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D3&lt;/td&gt;
&lt;td&gt;ADD_B&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;B &amp;lt;- A + B&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D4&lt;/td&gt;
&lt;td&gt;ADD_C&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;C &amp;lt;- A + B&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D5&lt;/td&gt;
&lt;td&gt;SUB_B&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;B &amp;lt;- A - B&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D6&lt;/td&gt;
&lt;td&gt;SUB_C&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;C &amp;lt;- A - B&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D7&lt;/td&gt;
&lt;td&gt;BSUB_B&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;B &amp;lt;- B - A&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D8&lt;/td&gt;
&lt;td&gt;BSUB_C&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;C &amp;lt;- B - A&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D9&lt;/td&gt;
&lt;td&gt;AND_B&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;B &amp;lt;- A AND B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DA&lt;/td&gt;
&lt;td&gt;AND_C&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;C &amp;lt;- A AND B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DB&lt;/td&gt;
&lt;td&gt;OR_B&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;B &amp;lt;- A OR B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DC&lt;/td&gt;
&lt;td&gt;OR_C&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;C &amp;lt;- A OR B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DD&lt;/td&gt;
&lt;td&gt;XOR_B&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;B &amp;lt;- A XOR B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DE&lt;/td&gt;
&lt;td&gt;XOR_C&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;C &amp;lt;- A XOR B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DF&lt;/td&gt;
&lt;td&gt;CPX&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A XOR B, discarded&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;Equality test, both operands intact.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="0xex-compute-and-store"&gt;0xEx Compute and store&lt;/h2&gt;
&lt;p&gt;A MAR load and an ALU row that writes RAM. The result never sits in a register.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hex&lt;/th&gt;
&lt;th&gt;Mnemonic&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;T&lt;/th&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Flags&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;E0&lt;/td&gt;
&lt;td&gt;STADD addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[addr] &amp;lt;- A + B&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E1&lt;/td&gt;
&lt;td&gt;STSUB addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[addr] &amp;lt;- A - B&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E2&lt;/td&gt;
&lt;td&gt;STBSUB addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[addr] &amp;lt;- B - A&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E3&lt;/td&gt;
&lt;td&gt;STAND addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[addr] &amp;lt;- A AND B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E4&lt;/td&gt;
&lt;td&gt;STOR addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[addr] &amp;lt;- A OR B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E5&lt;/td&gt;
&lt;td&gt;STXOR addr&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[addr] &amp;lt;- A XOR B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E6&lt;/td&gt;
&lt;td&gt;STADDS&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[SP] &amp;lt;- A + B&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E7&lt;/td&gt;
&lt;td&gt;STSUBS&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[SP] &amp;lt;- A - B&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E8&lt;/td&gt;
&lt;td&gt;STBSUBS&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[SP] &amp;lt;- B - A&lt;/td&gt;
&lt;td&gt;CZVN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E9&lt;/td&gt;
&lt;td&gt;STANDS&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[SP] &amp;lt;- A AND B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EA&lt;/td&gt;
&lt;td&gt;STORS&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[SP] &amp;lt;- A OR B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EB&lt;/td&gt;
&lt;td&gt;STXORS&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[SP] &amp;lt;- A XOR B&lt;/td&gt;
&lt;td&gt;ZN&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="0xfx-halt"&gt;0xFx Halt&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hex&lt;/th&gt;
&lt;th&gt;Mnemonic&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;T&lt;/th&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Flags&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FF&lt;/td&gt;
&lt;td&gt;HALT&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;T-counter stops&lt;/td&gt;
&lt;td&gt;hold&lt;/td&gt;
&lt;td&gt;0xFF so an erased EEPROM halts instead of running. Same byte fills unused microcode slots and parks the bus.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="opcode-map"&gt;Opcode map&lt;/h2&gt;
&lt;p&gt;256 slots. 174 assigned, 16 reserved (family 9), 66 free. Free slots are the gaps you can see in the tables above: 0x02-0x10, 0x17-0x20, 0x2D-0x30, 0x50, 0x5C-0x5F, 0x70, 0x7E-0x80, 0x8D-0x9F, 0xAE-0xAF, 0xBC-0xBF, 0xEC-0xFE.&lt;/p&gt;
&lt;h2 id="what-has-actually-run"&gt;What has actually run&lt;/h2&gt;
&lt;p&gt;The tables describe what&amp;rsquo;s burned. What has been witnessed on the hardware is a narrower claim.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Burned images in the repo: U9 0xC52B, U15 0xDCD1, U23 0xCB8E, CRC-16 pinned in the host tests so a reburn is deliberate. The U15 in the socket is 0x45F9, which adds an OUT settle row from 2026-09-08 that fixed nothing and comes back out on the next burn.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PROG_isa&lt;/code&gt; runs 147 subtests, one per instruction it can observe through OB, and reads 0xB4 at 1.024 MHz. &lt;code&gt;PROG_isasoak&lt;/code&gt; repeats them 64 times per run and read 0x00 on 50 runs in a row, 470,400 subtest executions.&lt;/li&gt;
&lt;li&gt;RET, OUT, OUTI and HALT have no subtest because the harness is built from them. Every coverage image since phase C runs all four. NOP first ran on 2026-09-08 in the OUT-before-RET chase. That&amp;rsquo;s 152 instructions witnessed.&lt;/li&gt;
&lt;li&gt;22 have never executed: RST, MOVAPCH, MOVBPCH, MOVCPCH, PUSHPCH, STPCL, STPCH, and the fifteen OUT variants (OUTB, OUTC, OUTSPL, OUTSPH, OUTPCL, OUTPCH, OUTM, OUTMX, OUTMS, OUTADD, OUTSUB, OUTBSUB, OUTAND, OUTOR, OUTXOR). The OUT ones can&amp;rsquo;t be self-tested because their only effect is the display. The rest just don&amp;rsquo;t have an image yet.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Phase H: pull the 23 settle pads and the OUT settle row, reburn U9 and U15, rerun &lt;code&gt;PROG_isasoak&lt;/code&gt;. If it stays at 0x00 the pads were never needed.&lt;/li&gt;
&lt;li&gt;Scope the &amp;lsquo;382 carry out after a logic op so the ZN rows can say what C actually does.&lt;/li&gt;
&lt;li&gt;Decide which of the fifteen OUT variants earn their slot. Something has to watch the LEDs to test them, so probably a camera or a second latch on the bus.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Cursors, measurements, and the write that flips the scope into Track</title><link>https://bytehamr.com/projects/sigscope/logs/cursors-measurements-and-the-write-that-flips-the-scope-into-track/</link><pubDate>Thu, 10 Sep 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/sigscope/logs/cursors-measurements-and-the-write-that-flips-the-scope-into-track/</guid><description>&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;v0.4 turns on the Cursors and Measure keys. Both cards follow the same rule as the rest of the panel: the app shows what the scope reports, and a click sends a request and waits for the next poll to confirm it.&lt;/p&gt;
&lt;p&gt;Cursors card: Mode Off/Manual/Track, Type X/Y/X-Y, and four knobs named like the scope&amp;rsquo;s own, X1 X2 Y1 Y2. The plot draws the cursors as dashed lines in the source channel&amp;rsquo;s colour and you can drag them. The readout chip in the corner is the scope&amp;rsquo;s own &lt;code&gt;CRVA?&lt;/code&gt; numbers (dX, 1/dX, X1, X2, and the Y trio), nothing computed on my side. The cursor source follows whichever channel is selected, because the scope has no query for it; the trace prefix on the &lt;code&gt;CRST&lt;/code&gt; write is what sets it.&lt;/p&gt;
&lt;p&gt;Measure card: the scope&amp;rsquo;s five custom slots, read every cycle with &lt;code&gt;PAVA? CUSTn&lt;/code&gt; (or &lt;code&gt;STATn&lt;/code&gt; when statistics are on, which also gives mean/min/max/std/count). An Add button opens a picker with the 28 single-channel parameters and the 10 delay types (&lt;code&gt;MEAD&lt;/code&gt;, channel pair). The live values also sit as a row of pills along the bottom of the plot.&lt;/p&gt;
&lt;p&gt;The scheduler got a fourth work kind, Measure, after the poll slice. Cursor positions are hot fields only while cursors are on.&lt;/p&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;One SCPI query is about 2.5 ms. The measure step costs 7 queries in the common case and 12 in the worst (statistics on, empty slots), so 20 to 30 ms per cycle.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CRST HREF&lt;/code&gt; reads back in seconds and &lt;code&gt;CRST VREF&lt;/code&gt; in volts, both absolute. A V write reads back as written minus 2 times the channel offset, checked at 0, 2 and 5 V. The app writes V + 2·ofst and the line lands where the scope&amp;rsquo;s screen shows it.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CRVA? HREL&lt;/code&gt; comes back as delta, 1/delta, X2, X1, in that order, with unit suffixes glued to the numbers.&lt;/li&gt;
&lt;li&gt;Live view in the captures ran 4.5 fps at 180 queries/s with two channels on, 7 Mpt memory, and five slots installed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--selftest&lt;/code&gt; gained a cursor step: X1 round trip within 1% of a division, Y1 round trip through the 2·ofst correction, and a &lt;code&gt;CRTY X-Y&lt;/code&gt; round trip. Still ends &lt;code&gt;SELFTEST OK&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-broke-or-surprised-me"&gt;What broke or surprised me&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PACL&lt;/code&gt; does nothing on this firmware. There is no SCPI way to remove a measurement. The slot list is a FIFO: a sixth &lt;code&gt;PACU&lt;/code&gt; drops slot 1 and shifts the rest up. The card says so and leaves an Add button on slot 5.&lt;/li&gt;
&lt;li&gt;Any &lt;code&gt;CRST HREF&lt;/code&gt; or &lt;code&gt;HDIF&lt;/code&gt; write puts the scope into Track mode. Any &lt;code&gt;VREF&lt;/code&gt; or &lt;code&gt;VDIF&lt;/code&gt; write sets the type to Y. Reads never change anything. I lost most of an afternoon on this: the scope kept ending up in Track after a live session and I blamed Auto Setup, a stale USB handle, then the scope&amp;rsquo;s own menu state, because my probes only ever wrote &lt;code&gt;CRTY&lt;/code&gt; and &lt;code&gt;VREF&lt;/code&gt;. The bisect that found it replayed the app&amp;rsquo;s traffic group by group and checked &lt;code&gt;CRMS?&lt;/code&gt; after each. The fix is two extra commands after every position write in Manual, &lt;code&gt;CRMS MANUAL&lt;/code&gt; and &lt;code&gt;CRTY &amp;lt;type&amp;gt;&lt;/code&gt;, coalesced by the scheduler so a knob drag doesn&amp;rsquo;t multiply them.&lt;/li&gt;
&lt;li&gt;The scope has a third cursor type, X-Y, all four cursors at once. &lt;code&gt;CRTY?&lt;/code&gt; returns &lt;code&gt;X-Y&lt;/code&gt;; &lt;code&gt;CRTY XY&lt;/code&gt; is ignored. I&amp;rsquo;d built the card with two knobs before Rico pointed at the scope&amp;rsquo;s own menu.&lt;/li&gt;
&lt;li&gt;Chaining commands with &lt;code&gt;;&lt;/code&gt; doesn&amp;rsquo;t work. The second command garbles the first one&amp;rsquo;s value.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QStringLiteral(&amp;quot;Add\xE2\x80\xA6&amp;quot;)&lt;/code&gt; renders as &lt;code&gt;Addâ€¦&lt;/code&gt;. Escaped UTF-8 bytes need &lt;code&gt;QString::fromUtf8&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Y cursors at the same volts as the trace&amp;rsquo;s low level are invisible: same colour, drawn on top. A dark halo under the dashed line is on the list.&lt;/li&gt;
&lt;li&gt;My scope MCP server holds the USB interface while it&amp;rsquo;s loaded, so the app fails with &amp;ldquo;claim interface 0 failed&amp;rdquo;. Only one process gets the scope.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Halo under the cursor lines so they survive sitting on a trace.&lt;/li&gt;
&lt;li&gt;Manual acceptance checklist for v0.4 is written but unticked; measurement values only got eyeballed live, not recorded.&lt;/li&gt;
&lt;li&gt;Deep capture export, Linux build, the rest of the menu keys.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/sigscope/2026-09-10-v0.4-live-xy.png" alt="v0.4 headless capture: Manual X-Y cursors on CH1, readout chip from CRVA?, five measurement pills under the plot." loading="lazy"&gt;
&lt;figcaption&gt;v0.4 headless capture: Manual X-Y cursors on CH1, readout chip from CRVA?, five measurement pills under the plot.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="later-the-same-day"&gt;Later the same day&lt;/h2&gt;
&lt;p&gt;The app has a name now, SigScope, and a public repo: &lt;a href="https://github.com/robertrico/sigscope"&gt;github.com/robertrico/sigscope&lt;/a&gt;. Rico picked the name after I offered a few. The bundle is &lt;code&gt;SigScope.app&lt;/code&gt;; the internal namespace stayed &lt;code&gt;osc&lt;/code&gt;. The README got the two v0.4 captures, a platforms table (macOS tested, Linux untested, Windows out), and a paragraph on the firmware quirks the app works around.&lt;/p&gt;</description></item><item><title>CI runs for the first time, and the wrapper had drifted off the core</title><link>https://bytehamr.com/projects/litex-8008-node/logs/ci-runs-for-the-first-time-and-the-wrapper-had-drifted-off-the-core/</link><pubDate>Thu, 10 Sep 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/litex-8008-node/logs/ci-runs-for-the-first-time-and-the-wrapper-had-drifted-off-the-core/</guid><description>&lt;p&gt;I sat down to write this project up for the site and read my own last commit. It said D and W over the LAN were returning 0x44. I remembered it working. Turned out I&amp;rsquo;d done the fix on the other machine and never pushed, so I pulled six commits from 2026-08-09 and one of them was a verification workflow that had never run. Pushing it ran it. Red.&lt;/p&gt;
&lt;p&gt;First failure was mine. The core repo&amp;rsquo;s FuseSoC generator finds GHDL through a &lt;code&gt;GHDL&lt;/code&gt; environment variable and falls back to &lt;code&gt;~/oss-cad-suite/bin/ghdl&lt;/code&gt;. My Makefile set that variable and never exported it, so it only ever worked on machines where the fallback path existed. One &lt;code&gt;export GHDL&lt;/code&gt; line.&lt;/p&gt;
&lt;p&gt;Second failure was the real one. &lt;code&gt;make convert&lt;/code&gt; builds the netlist from the core repo at HEAD, and the core repo moved on 2026-08-08. Commit c05c7c7 purged the &lt;code&gt;EXTERNAL_RAM&lt;/code&gt; generic, the &lt;code&gt;ram_ext_*&lt;/code&gt; bus and the 3-bit &lt;code&gt;int_vector&lt;/code&gt; from &lt;code&gt;b8008_top&lt;/code&gt;. My wrapper still declared all three. Nine port errors. It had worked locally for a month because the netlist on disk was built in August and nothing regenerated it.&lt;/p&gt;
&lt;p&gt;I went down a hole first. The bring-up commit says the DHCP lease needed liteeth at master 276c9e3, and this machine had it at the 2026.04 tag, so I tried pinning it forward. liteeth 28b4493 from June uses litex master&amp;rsquo;s new CSR aliases, &lt;code&gt;CSR.wr_stb&lt;/code&gt;, which litex 2026.04 doesn&amp;rsquo;t have. Elaboration dies in the MAC SRAM. Back to the tag. There&amp;rsquo;s a &lt;code&gt;make liteeth-pin LITEETH_REV=…&lt;/code&gt; knob now for a bench session, and &lt;code&gt;make litex-env&lt;/code&gt; never moves a checkout on its own.&lt;/p&gt;
&lt;p&gt;Then I pinned CI to the last core commit that still had the old ports, 70c7498, and cancelled the run when I realized that was freezing the wrapper on an August core. The core&amp;rsquo;s fine. The wrapper needed to follow it.&lt;/p&gt;
&lt;p&gt;The change is smaller than it sounds. &lt;code&gt;EXTERNAL_RAM=false&lt;/code&gt; already instantiated &lt;code&gt;ram_sync&lt;/code&gt; inside &lt;code&gt;b8008_top&lt;/code&gt;. The external mode just routed the same &lt;code&gt;latched_address&lt;/code&gt;, data, &lt;code&gt;rw_n&lt;/code&gt; and &lt;code&gt;cs_n&lt;/code&gt; out to the bus, where my Migen side fed a 16 KB Memory. &lt;code&gt;ram_sync.vhdl&lt;/code&gt; and &lt;code&gt;address_decoder.vhdl&lt;/code&gt; are byte-identical across the purge. The old jam byte was &lt;code&gt;&amp;quot;00&amp;quot; &amp;amp; int_vector &amp;amp; &amp;quot;101&amp;quot;&lt;/code&gt; with vector 000, which is 0x05. The new &lt;code&gt;int_instruction&lt;/code&gt; default is &lt;code&gt;00000101&lt;/code&gt;, also 0x05. ROM interface untouched. So the wrapper drops its &lt;code&gt;ram_*&lt;/code&gt; ports and the vector latch, the Migen side drops its RAM, and both boot testbenches drop their RAM models. The Verilator bench was also still wiring &lt;code&gt;ctl_*&lt;/code&gt; and &lt;code&gt;sts_*&lt;/code&gt; ports I&amp;rsquo;d deleted in August. S-CORE-3 got a dated amendment re-freezing the port list.&lt;/p&gt;
&lt;p&gt;Convert now stamps the netlist with core 342f8cd. The GHDL sim prints the banner through &amp;ldquo;8008 Mon&amp;rdquo;, the Verilator sim through &amp;ldquo;8008 &amp;ldquo;, and &lt;code&gt;make test&lt;/code&gt; is green, 4 C binaries, 105 host and 48 SoC tests.&lt;/p&gt;
&lt;p&gt;Then three more red runs, all one bug. On the runner, &lt;code&gt;litex_setup.py&lt;/code&gt; clones the vendored trees under &lt;code&gt;.venv/&lt;/code&gt;. On this Mac they&amp;rsquo;re at the repo root from July. &lt;code&gt;liteeth-pin&lt;/code&gt;, the C test include path and the fork-drift test all assumed root. They accept either now.&lt;/p&gt;
&lt;p&gt;Run 34549971806 went green at 2026-09-11 01:21 UTC. Five and a half minutes end to end on a fresh runner, of which the seven SoC elaborations take three. Convert takes 4 s. That&amp;rsquo;s the first green run this repo has had, and every failure on the way was something that only shows up on a machine that isn&amp;rsquo;t mine.&lt;/p&gt;
&lt;p&gt;What I have not done is put this on the board. The logic is the same but the 16 KB RAM used to be a Migen Memory and is now inferred by yosys from the GHDL netlist. That&amp;rsquo;s a different path to block RAM. &lt;code&gt;make check-synth&lt;/code&gt; counts DP16KD for exactly this reason, and the core repo&amp;rsquo;s own monitor build infers this same &lt;code&gt;ram_sync&lt;/code&gt; fine, but a green test suite is not a working machine. Next is a bitstream, &lt;code&gt;make selftest&lt;/code&gt;, and D and W by hand.&lt;/p&gt;</description></item><item><title>Why this site exists</title><link>https://bytehamr.com/logs/why-this-site-exists/</link><pubDate>Wed, 09 Sep 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/logs/why-this-site-exists/</guid><description>&lt;p&gt;Hardware projects generate notes: measurements, dead ends, part numbers, photos of a scope screen. Most of that ends up scattered across a lab notebook, a chat history, and a README nobody updates. This site is the fix. Every project gets one overview page, and every working session that produces something worth keeping becomes a dated log entry under it.&lt;/p&gt;
&lt;p&gt;A few rules keep it cheap to maintain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Static HTML built by &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt;, hosted on S3 behind CloudFront. No server, no database, nothing to patch.&lt;/li&gt;
&lt;li&gt;No JavaScript, no external fonts, no analytics. The page you see is the page that was written.&lt;/li&gt;
&lt;li&gt;Publishing is a &lt;code&gt;git push&lt;/code&gt;. A GitHub Actions workflow builds the site and syncs it to the bucket.&lt;/li&gt;
&lt;li&gt;A build log belongs to exactly one project. Logs get corrected in place when I learn better; git keeps the history.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This entry is the one exception: it belongs to the site itself, not to a project. Everything after it is project work.&lt;/p&gt;
&lt;p&gt;Projects currently listed: &lt;a href="https://bytehamr.com/projects/byte-hamr/"&gt;Byte Hamr&lt;/a&gt;, an ECP5-85K FPGA peripheral card for the Apple IIe, plus several others that are still stubs. The &lt;a href="https://bytehamr.com/projects/"&gt;projects&lt;/a&gt; page shows current status for each.&lt;/p&gt;</description></item><item><title>Talking to the SDS1204X-E over USB from a Mac</title><link>https://bytehamr.com/projects/sigscope/logs/usb-spikes-and-v0-1/</link><pubDate>Wed, 09 Sep 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/sigscope/logs/usb-spikes-and-v0-1/</guid><description>&lt;p&gt;Siglent doesn&amp;rsquo;t ship a Mac app for the SDS1204X-E and the Windows one isn&amp;rsquo;t much anyway. The scope has a USB device port that enumerates as USBTMC, so I wanted to see how far libusb alone would get me before committing to a real app.&lt;/p&gt;
&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;Plugged it in. macOS has no USBTMC driver, which turns out to be the good case: nothing grabs the interface, libusb claims it from user space, no kext. VID 0xF4EC, PID 0xEE38.&lt;/p&gt;
&lt;p&gt;First attempt with python-usbtmc died on the first control transfer. pyvisa-py got &lt;code&gt;*IDN?&lt;/code&gt; back. Then I wrote my own 80-line USBTMC framing in pyusb because pyvisa-py kept hanging on waveform reads, and that harness became the reference for the C++ transport later.&lt;/p&gt;
&lt;p&gt;The command set is the old LeCroy-style short form, not the &lt;code&gt;:TIMebase:SCALe&lt;/code&gt; tree. &lt;code&gt;C1:VDIV 1.3V&lt;/code&gt;, &lt;code&gt;TDIV 50NS&lt;/code&gt;, &lt;code&gt;TRSE EDGE,SR,C1,HT,OFF&lt;/code&gt;. &lt;code&gt;CHDR OFF&lt;/code&gt; first or every reply comes back with a header and units glued on.&lt;/p&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;2.5 ms per short query, median. 70 queries in 0.3 s.&lt;/li&gt;
&lt;li&gt;7 000 point waveform fetch: 81 ms. That&amp;rsquo;s 12 fps if you do nothing else.&lt;/li&gt;
&lt;li&gt;Full 7 M point record: 15.4 s. USB on this scope is full speed, 12 Mbit, about 500 KB/s real.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WFSU SP,5000,NP,1400&lt;/code&gt; gives 1 400 points spread across the whole 7 M record in 0.3 to 0.5 s. Sparsing only works if NP is also set, otherwise it&amp;rsquo;s ignored and you get the whole thing.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PNSU?&lt;/code&gt; returns the entire panel setup as 88 KB of XML. Takes 12.8 s every time, so it&amp;rsquo;s a connect-time thing only.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SCDP&lt;/code&gt; screenshot: 768 067 byte BMP in 1.3 s.&lt;/li&gt;
&lt;li&gt;60 s stress of 20 queries plus 2 waveforms per round: 377 rounds, zero failures.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The volts formula checked out against the scope&amp;rsquo;s own measurement to the last digit: code × VDIV / 25 − OFST, 25 codes per division. Rising edge landed on sample 3500 of 7000, so the trigger point is dead center.&lt;/p&gt;
&lt;h2 id="what-broke-or-surprised-me"&gt;What broke or surprised me&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Bare &lt;code&gt;SANU?&lt;/code&gt; hangs the link. &lt;code&gt;SANU? C1&lt;/code&gt; is fine. It&amp;rsquo;s on a denylist now.&lt;/li&gt;
&lt;li&gt;The scope rejects USBTMC INITIATE_CLEAR and INITIATE_ABORT, which is what most libraries send when a read times out. That&amp;rsquo;s why they hang. Recovery is a USB port reset plus draining whatever is still in the pipe, about 1.5 s.&lt;/li&gt;
&lt;li&gt;It pads bulk-in transfers and sometimes ends them early. You read until the header&amp;rsquo;s byte count is satisfied and ignore the packet boundaries.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MSIZ&lt;/code&gt; silently ignores 14K/140K/1.4M/14M when three or four channels are on. Only the 7K series is valid then.&lt;/li&gt;
&lt;li&gt;If you kill the app mid-transfer, the scope keeps streaming the rest of the record at the next program that opens it. My first C++ reader treated an all-zero header as &amp;ldquo;more coming&amp;rdquo; and looped forever. The Python harness never validated tags so it never noticed. Fixed with tag checking, a deadline on every read, and a drain on open.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;The app shell works: live 4-channel plot, run/stop/single/force, edge trigger panel, V/div and time/div, screenshot to the Desktop with Ctrl+S, and it follows the scope&amp;rsquo;s physical knobs within about a second by polling. It looks like a Qt example though. Next is making it look and work like the actual front panel.&lt;/p&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/sigscope/scope-screen.png" alt="The scope&amp;#39;s own screen, pulled over USB with SCDP. 768 KB BMP in 1.3 s." loading="lazy"&gt;
&lt;figcaption&gt;The scope&amp;rsquo;s own screen, pulled over USB with SCDP. 768 KB BMP in 1.3 s.&lt;/figcaption&gt;
&lt;/figure&gt;</description></item><item><title>Front panel, scope-style trigger, and persistence</title><link>https://bytehamr.com/projects/sigscope/logs/front-panel-and-persistence/</link><pubDate>Wed, 09 Sep 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/sigscope/logs/front-panel-and-persistence/</guid><description>&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;The first version worked but I couldn&amp;rsquo;t find Single, everything was a combo box, and turning a knob on the scope took over a second to show up. So version two copies the front panel. Four channel columns with a volts/div knob and an offset knob each, lit AC/DC/BW/INV buttons, a time/div knob, trigger source and slope as button rows, and a big Run/Stop. The trigger now works the scope&amp;rsquo;s way: pick Auto, Normal or Single, then press Run/Stop to go. Single re-arms a running scope instead of stopping it, which took one review to catch.&lt;/p&gt;
&lt;p&gt;The knobs are painted with QPainter, 20 detents over 300 degrees, a colored arc showing where you are in the 1-2-5 ladder. Mouse wheel is one detent, vertical drag is 12 px per detent.&lt;/p&gt;
&lt;p&gt;Auto Setup used to drag the whole link into recovery, so it moved into a Utility menu and now pauses the live feed, waits for the scope to answer again, does one full poll, and resumes. There&amp;rsquo;s a Pause/Live button for the same reason.&lt;/p&gt;
&lt;p&gt;Then persistence. A Display card with Off, 1s, 5s, 10s, 30s and infinite. It sends the scope&amp;rsquo;s own &lt;code&gt;PESU&lt;/code&gt; command so the scope screen matches, and the app keeps an accumulation image per channel, stamps each new sweep at low alpha, and multiplies the alpha down every frame for the timed modes. Live trace is drawn on top at full brightness. Then an intensity row because the persisted band was as bright as the live trace and you couldn&amp;rsquo;t tell them apart.&lt;/p&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/sigscope/2026-09-09-v0.2-live.png" alt="The front-panel layout connected to the scope, square wave on CH1 at 50 µs/div, 8.3 fps." loading="lazy"&gt;
&lt;figcaption&gt;The front-panel layout connected to the scope, square wave on CH1 at 50 µs/div, 8.3 fps.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/sigscope/2026-09-09-v0.3-live.png" alt="Infinite persistence with the trigger free-running. Every past sweep stays as the yellow band, the live sweep on top." loading="lazy"&gt;
&lt;figcaption&gt;Infinite persistence with the trigger free-running. Every past sweep stays as the yellow band, the live sweep on top.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Full state poll: 276 ms before, 163 ms after moving the hot fields (scales, offsets, trigger level, run state) into every poll slice.&lt;/li&gt;
&lt;li&gt;Knob on the scope to panel in the app: about 0.2 s now, was about 1.2 s.&lt;/li&gt;
&lt;li&gt;Live round of 18 hot queries plus one 1400-point waveform: 8.8 rounds/s, p50 114 ms.&lt;/li&gt;
&lt;li&gt;Deep memory no longer matters for the live view: 14 Mpt at 1400 decimated points fetches in 352 ms.&lt;/li&gt;
&lt;li&gt;Auto Setup settles in about 4 s.&lt;/li&gt;
&lt;li&gt;Persistence stamp alpha 0.15 per sweep, so full brightness after about 30 overlapping sweeps. Layers are about 3.5 MB each at a 1440-wide window.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-broke-or-surprised-me"&gt;What broke or surprised me&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;After &lt;code&gt;ASET&lt;/code&gt; the scope is silent for about 3 s. The first &lt;code&gt;SAST?&lt;/code&gt; times out at 2 s and its reply arrives late. My transport checks the USBTMC tag on every reply, so that late reply made every following read fail, 22 errors in a row until the 8 s budget ran out. The Python harness, which doesn&amp;rsquo;t check tags, never saw it. Fix was to drain stale output inside the settle loop after any error.&lt;/li&gt;
&lt;li&gt;8-bit premultiplied alpha never reaches zero when you multiply it down by a factor near 1. Pixels get stuck at alpha 4. The fade pass now zeroes anything under 8 every eighth frame.&lt;/li&gt;
&lt;li&gt;A negative frame delta (clock jitter) would have made the fade factor exceed 1, produced an invalid QColor, and wiped the whole layer. Caught in review before it hit hardware.&lt;/li&gt;
&lt;li&gt;A stably triggered square wave shows no persistence at all, every sweep lands on the last one. Same on the real scope. You have to let the trigger free-run to see the smear.&lt;/li&gt;
&lt;li&gt;Screenshot to the desktop with Ctrl+S still works, and the scope&amp;rsquo;s own dense persistence comes through that path.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;Cursors and measurements. Both menu keys are already on the panel, disabled. The SCPI for them is documented and I&amp;rsquo;ve already read &lt;code&gt;PAVA?&lt;/code&gt; values off the scope in the first day&amp;rsquo;s spikes.&lt;/p&gt;</description></item><item><title>OUT before a RAM fetch halts; the breadboard is done</title><link>https://bytehamr.com/projects/dino/logs/out-before-a-ram-fetch-halts-the-breadboard-is-done/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/dino/logs/out-before-a-ram-fetch-halts-the-breadboard-is-done/</guid><description>&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;RAM programs were stopping. &lt;code&gt;life.asm&lt;/code&gt; printed row 0, OB read 0x01, and the machine froze with no response to keys. &lt;code&gt;bigxfer&lt;/code&gt; at 2233 bytes has gone in and streamed itself back clean several times, sometimes back to back, and then a table bump or a wire out of line stops it (one run: monitor sum 0x69 against host 0x29, OB 0xC2, monitor unresponsive). Every earlier RAM program had done five firsts at once, so I wrote a staircase, &lt;code&gt;asm/ram/step1..8&lt;/code&gt;, one first per step, with the Python oracle as the answer key.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;step2&lt;/code&gt; is &lt;code&gt;LDAI 0x22; OUT; RET&lt;/code&gt;. It OUTs 0x22 and freezes. Fetch, OUT, HALT, RET and CALL from RAM all work on their own; only OUT immediately before another instruction fetched from RAM halts. Data-independent: four OUT values all froze, and OUT of 0x34 with REG_A already 0x34, no bus transition, still froze. One instruction between them cures it (&lt;code&gt;step2a&lt;/code&gt;). One NOP cures it (&lt;code&gt;step2g&lt;/code&gt;, OB 0x22, prompt back). That was NOP&amp;rsquo;s first execution on silicon.&lt;/p&gt;
&lt;p&gt;First model: OUT was one row driving MDR from REG_A and strobing the output latch in the same T-state that carried END, so the next fetch began before the bus released. Fix: a SETTLE row that drives nothing before END. Reburned U15, CRC 0xDCD1 to 0x45F9, one chip.&lt;/p&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;With the settle row burned, &lt;code&gt;step2&lt;/code&gt; still halts, OB 0x22. The settle fixed nothing.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;step2&lt;/code&gt; halts at 1.024 MHz and above, returns at 500 kHz.&lt;/li&gt;
&lt;li&gt;Half-window 488 ns fails, 1000 ns passes, so the real first-fetch-after-OUT settle is somewhere in 488 ns to 1000 ns.&lt;/li&gt;
&lt;li&gt;Datasheet budget to IR is 382 ns worst case and closes at 488 ns. The missing 100 ns to 600 ns is not in the datasheet path.&lt;/li&gt;
&lt;li&gt;Machine draw on the bench supply ammeter: 1.25 A to 1.34 A at 5 V.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-broke-or-surprised-me"&gt;What broke or surprised me&lt;/h2&gt;
&lt;p&gt;Idle T-states between OUT and the RET fetch do nothing; only a real intervening fetch cures it. So the bottleneck is the RET fetch&amp;rsquo;s own single T-state, whose setup deadline is the clock edge, and states before it cannot lengthen it. The extra delay is breadboard bus capacitance (100+ pF against the 45 pF a 74LS245 tPD is specced into) plus the REG_A to RAM buffer cross-driver handoff that only an OUT-then-fetch sees. A fetch after a fetch is the same buffer both times, no contention. A driving settle does not help either: OUT cannot know whether the next fetch is ROM or RAM, and a RAM-driving last row makes the ROM case a bus fight.&lt;/p&gt;
&lt;p&gt;An earlier note had predicted slower is worse from a transparent-IR-dwell model. The bench said slower is better. That model is retracted, and so is the settle row: it costs a T-state and should be reverted on the next burn.&lt;/p&gt;
&lt;p&gt;Zero-burn workaround: never put OUT immediately before RET in RAM code.&lt;/p&gt;
&lt;p&gt;Stepping back: every error since the ISA went green has been noise. Ground offsets, a ringing clock stub, a wrong capacitor, a reset wire in the wrong bundle, bus capacitance, and now a transfer that a bump can stop. None of it has been the logic.&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;The breadboard is at the end of what it can do. Decision: one 2-layer mainboard, the phase E slots as its only connectors, and power designed first. Wrote &lt;code&gt;POWER.md&lt;/code&gt; with the record of every power or ground fault chased as logic since 2026-08-24, eight entries, the supply options (old ATX on hand, PicoPSU, plain 5 V brick) and the bench checks that pick between them. Which supply is an open item I have not dug into.&lt;/p&gt;</description></item><item><title>Monitor loads hex over serial, and RAM is proven whole</title><link>https://bytehamr.com/projects/dino/logs/monitor-loads-hex-over-serial-and-ram-is-proven-whole/</link><pubDate>Mon, 07 Sep 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/dino/logs/monitor-loads-hex-over-serial-and-ram-is-proven-whole/</guid><description>&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;Two new monitor commands in ROM. &lt;code&gt;L addr,len&lt;/code&gt; takes hex digits off the wire, writes them to RAM and prints a running 8-bit sum so the host can check the transfer. &lt;code&gt;G addr&lt;/code&gt; does a CALL into RAM, so a loaded program ends with RET and lands back on the prompt. No microcode burn; it is all program ROM.&lt;/p&gt;
&lt;p&gt;On the host, &lt;code&gt;dinoload.py&lt;/code&gt; assembles a &lt;code&gt;.asm&lt;/code&gt;, sends &lt;code&gt;L&lt;/code&gt;, streams the bytes and compares the monitor&amp;rsquo;s sum against its own. The first version burst the whole line and the machine dropped characters. Four fixes in a day: flush the port and retry the sync CR, type one character at a time and wait for its echo before the next, keep bytes that arrive past the delimiter, and show the session live with echoes, the sum line and a byte counter. A per-character echo check means a wrong echo names the position.&lt;/p&gt;
&lt;p&gt;Then RAM. Every bench-green image so far had kept its cells and stack inside 0x8000-0x80FF, so RAM above that was formally open. Wrote data and fetched instructions across the range.&lt;/p&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Paced typing, one character per echo: no drops. Burst: drops. So it&amp;rsquo;s the machine.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bigxfer&lt;/code&gt;, an early RAM program that CALLs into the monitor&amp;rsquo;s ROM subroutines and RETs back into RAM, printed 261 bytes to the terminal. CALL from RAM into ROM and RET from ROM into RAM both work.&lt;/li&gt;
&lt;li&gt;RAM 0x8000-0xFFFF: data lands and reads back, instructions fetch, across the whole 32K.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-broke-or-surprised-me"&gt;What broke or surprised me&lt;/h2&gt;
&lt;p&gt;The loader was sensitive to everything. It worked sometimes, which meant noise, and the noise was on the DINO side: the serial card is the only device in the machine that counts strobes or has an asynchronous reset, so so a serial-only fault with the ISA self-test clean has to be a strobe or a reset.&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;Next is loading something real: &lt;code&gt;life.asm&lt;/code&gt;, Rule 30 on the terminal, seeded from the DIP switch, with the rule table patchable from the monitor prompt. Then a large transfer both directions to see whether the wire has settled.&lt;/p&gt;</description></item><item><title>LEDs on a RAM byte, DIPs unplugged, and a make target for the console</title><link>https://bytehamr.com/projects/intel-8008-vhdl/logs/leds-on-a-ram-byte-dips-unplugged-and-a-make-target-for-the-console/</link><pubDate>Thu, 03 Sep 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/intel-8008-vhdl/logs/leds-on-a-ram-byte-dips-unplugged-and-a-make-target-for-the-console/</guid><description>&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;b8008_top gains a LED_SHADOW_ADDR generic and a ram_byte_led output, a shadow of one RAM byte the same way the boot vector shadows byte 0. Both board tops put it on LEDs 7 to 1, bit n to LED n, 1 is on. LED0 (D25) stays the CPU-running light. In the monitor build the byte is 0x3FFF, so &lt;code&gt;W 3FFF,FE&lt;/code&gt; lights all seven. In the BASIC build it&amp;rsquo;s 0x00FF, reached with MON, then &lt;code&gt;W 00FF,FE&lt;/code&gt;, then &lt;code&gt;G 1FB6&lt;/code&gt;. Port 8 is still latched by the core and no longer shown.&lt;/p&gt;
&lt;p&gt;A cylon_ram sample sweeps the seven LEDs through the byte, G 2100, any key exits.&lt;/p&gt;
&lt;h2 id="what-broke"&gt;What broke&lt;/h2&gt;
&lt;p&gt;The DIP bank. On this board ON reads as 0. With the old wiring, my all-OFF bank selected the fetch-capture LED mode, and any flip of switch 6 or 7 halted the CPU through the READY hold or the interrupt input. So sw(1) through sw(7) are no-connects in both tops now: no capture modes, no switch interrupts, READY tied high, post-bootstrap break off. Only DIP 1, reset, remains.&lt;/p&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;p&gt;Two new regressions: monitor_led_tb at 900 ms of simulated time, and basic_led_tb at 2.1 s covering a full boot, MON, W, G 1FB6 and LIST. Timing reports refreshed.&lt;/p&gt;
&lt;p&gt;The serial side: &lt;code&gt;make monitor&lt;/code&gt; opens minicom, &lt;code&gt;make kill-monitor&lt;/code&gt; closes it, and &lt;code&gt;make send-hex HEX=name&lt;/code&gt; resolves against the samples directory. kill-monitor waits for minicom to finish its hangup and then settles 2 s before the port is reopened. At 0.5 s the first bytes of a send came through corrupted. Validated on the board.&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;No plan for the core. This is the state the Byte Hamr card&amp;rsquo;s 8008 and the Apple II toolchain build on.&lt;/p&gt;</description></item><item><title>Both machines booting</title><link>https://bytehamr.com/projects/ibm-5160/logs/both-machines-booting/</link><pubDate>Sat, 29 Aug 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/ibm-5160/logs/both-machines-booting/</guid><description>&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;The other good ST-251 into the 5160 on its own controller card. Different card from the 5271&amp;rsquo;s, so a different format, but the same process through DEBUG. FDISK, FORMAT, DOS 5.&lt;/p&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;p&gt;Both machines boot from A: and from C:. On the 5271 I loaded DOS and typed around with the Inboard in. If the Inboard didn&amp;rsquo;t work, the machine wouldn&amp;rsquo;t.&lt;/p&gt;
&lt;h2 id="what-broke-or-surprised-me"&gt;What broke or surprised me&lt;/h2&gt;
&lt;p&gt;Nothing.&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;The Inboard&amp;rsquo;s DOS utility. The alignment tool. Eventually the 3270 side.&lt;/p&gt;</description></item><item><title>Dynamic mode format</title><link>https://bytehamr.com/projects/ibm-5160/logs/dynamic-mode-format/</link><pubDate>Sun, 23 Aug 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/ibm-5160/logs/dynamic-mode-format/</guid><description>&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;A known-good ST-251 this time. Back into &lt;code&gt;G=C800:5&lt;/code&gt;, dynamic configuration, &lt;code&gt;820 6 821 821 11 7&lt;/code&gt;. That&amp;rsquo;s 820 cylinders, 6 heads, reduced write current and precomp starting at cylinder 821, 11-bit error burst, step rate select 7. Interleave 5 first, then reformatted at 3. Then FDISK, then FORMAT C: /S, then DOS 5. All with the Inboard 386 in.&lt;/p&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;p&gt;FDISK showed the full drive. DOS 5 installed and the 5271 boots from C:.&lt;/p&gt;
&lt;h2 id="what-broke-or-surprised-me"&gt;What broke or surprised me&lt;/h2&gt;
&lt;p&gt;Nothing this time.&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;Same on the 5160.&lt;/p&gt;</description></item><item><title>Tandon alignment</title><link>https://bytehamr.com/projects/ibm-5160/logs/tandon-alignment/</link><pubDate>Sat, 22 Aug 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/ibm-5160/logs/tandon-alignment/</guid><description>&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;Swapped the two full-height Tandons between machines until the problem followed one drive. Then followed the classic-computers.org.nz TM100 procedure: scope on the analog test points on the drive&amp;rsquo;s logic board, head parked on a track and reading continuously, adjust the head cam.&lt;/p&gt;
&lt;p&gt;To park the head I wrote a small program in DEBUG with &lt;code&gt;a 100&lt;/code&gt; that seeks to a track and reads it in a loop. I made the track number a variable so I could move between tracks without leaving the session.&lt;/p&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;p&gt;Signal amplitude at the test points on the Siglent, on the tracks the procedure calls for. The numbers came out near what the website says they should be.&lt;/p&gt;
&lt;h2 id="what-broke-or-surprised-me"&gt;What broke or surprised me&lt;/h2&gt;
&lt;p&gt;The DEBUG loop only tells me the head is reading. It can&amp;rsquo;t tell me which track is actually under the head. I wrote a spec for a proper DOS tool that uses the FDC Read ID command for that and sweeps every track for read reliability.&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;The aligned drive reads and writes disks from the other drive and from OEM disks. Now the hard drives.&lt;/p&gt;</description></item><item><title>Floppy controller tantalum</title><link>https://bytehamr.com/projects/ibm-5160/logs/floppy-controller-tantalum/</link><pubDate>Sun, 16 Aug 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/ibm-5160/logs/floppy-controller-tantalum/</guid><description>&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;In the morning the floppy read fine. By afternoon &lt;code&gt;DIR&lt;/code&gt; worked sometimes. By evening freshly formatted disks wouldn&amp;rsquo;t boot. By night the machine went straight to Cassette BASIC with no non-system disk message, meaning the BIOS never read sector one.&lt;/p&gt;
&lt;p&gt;I chased the drive: head contamination, belt, spindle speed, alignment, a capstan on the stepper shaft that looked like it might have slipped. I also found one disk that had been sitting on a MacBook lid and was partly erased by the magnets, which muddied things further.&lt;/p&gt;
&lt;p&gt;Then I pulled the floppy controller card and put the meter across the +5 V edge pins.&lt;/p&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;p&gt;Continuity across +5 V and ground on the controller. I didn&amp;rsquo;t want to desolder every cap to measure them, so I found the shorted tantalum with continuity checks on the board, replaced it, and the floppy read again.&lt;/p&gt;
&lt;h2 id="what-broke-or-surprised-me"&gt;What broke or surprised me&lt;/h2&gt;
&lt;p&gt;A tantalum failing progressively looks like several unrelated things getting worse over a few hours. When multiple subsystems degrade together, look for one common factor first. Also, on a tantalum the stripe marks positive, which is backwards from an aluminum electrolytic.&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;Figure out why disks written on one machine won&amp;rsquo;t read on the other.&lt;/p&gt;</description></item><item><title>A dead ST-251</title><link>https://bytehamr.com/projects/ibm-5160/logs/a-dead-st-251/</link><pubDate>Sat, 15 Aug 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/ibm-5160/logs/a-dead-st-251/</guid><description>&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;Identified the 5271&amp;rsquo;s controller from the silkscreen: Western Digital WD1002A-WX1, assembly 60-600003-02, BIOS ROM 62-000094-030, the Super BIOS. Ran the on-card formatter from DEBUG with &lt;code&gt;G=C800:5&lt;/code&gt;. Tried SpeedStor 6.0.3 too, but DEBUG against the card&amp;rsquo;s own formatter worked best. Swapped drives, cables and cards in and out for a while.&lt;/p&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;p&gt;The first format completed and reported 612 cylinders and 4 heads, about 21 MB. The ST-251 is 820 cylinders, 6 heads, 17 sectors, about 40 MB. That&amp;rsquo;s the card&amp;rsquo;s fixed mode: geometry comes from a drive table selected by the S1 jumpers, and the table stops at 663 cylinders.&lt;/p&gt;
&lt;p&gt;In dynamic mode with the right geometry the format ran for hours and never finished. The drive swept full stroke about once a second the whole time.&lt;/p&gt;
&lt;h2 id="what-broke-or-surprised-me"&gt;What broke or surprised me&lt;/h2&gt;
&lt;p&gt;The drive was dead. The same process worked fine on two other ST-251s. Of the four, two are good.&lt;/p&gt;
&lt;p&gt;Also, SpeedStor&amp;rsquo;s MediaAnalysis scans cylinder 0 head 0, which is where the WD card keeps its dynamic geometry, and wipes it.&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;Floppies before hard drives. The 5271&amp;rsquo;s floppy had started acting up.&lt;/p&gt;</description></item><item><title>D and W check out by hand, and the network stack gets the core repo's test rigor</title><link>https://bytehamr.com/projects/litex-8008-node/logs/d-and-w-check-out-by-hand-and-the-network-stack-gets-the-core-repo-s-test-rigor/</link><pubDate>Sun, 09 Aug 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/litex-8008-node/logs/d-and-w-check-out-by-hand-and-the-network-stack-gets-the-core-repo-s-test-rigor/</guid><description>&lt;p&gt;Day after bring-up, on the other machine. The README&amp;rsquo;s status paragraph got a truth pass and now says memory read and write through the monitor is verified by hand over the LAN. That closes the 0x44 thing from the day before. None of the day&amp;rsquo;s commits names a fix for it, so I&amp;rsquo;m treating it as a bring-up artifact rather than a bug that got patched.&lt;/p&gt;
&lt;p&gt;SPEC.md section 12 got a dated amendment instead of a rewrite. The hardware Etherbone wire contract is superseded by the software server, with the CommUDP dialect written down, a structural bound that a reply never exceeds the request length plus 16 bytes, and the broadcast-request, unicast-reply rule with the reason it exists. Section 13 gained the ARP keepalive requirement and a requirement that the RX path accept both limited and subnet broadcast, which is why udp.c is a fork. VPLAN grew 14 rows for the software transport and now counts 44 of 133 passing.&lt;/p&gt;
&lt;p&gt;The bulk of the day was tests. The firmware C compiles on the host against a mocked ethmac, a 4-slot model with a scripted auto-responder. eb8008.c got 26 directed tests plus a 2149-case adversarial sweep over truncated and oversized records, which settled a suspected reply-buffer overflow by showing a read record always consumes at least as many bytes as its reply produces. udp.c got 46 tests covering the subnet-broadcast fix, min-frame padding, source MAC capture, ARP-free replies, and ICMP both directions. The serve glue came out of main.c into eb_serve.c so it could be tested too, 15 tests through the real receive, serve and transmit path including NAT-rewritten ports. dhcp8008.c got adversarial parser cases. A coverage gate fails below 100 percent of lines on all four files. main.c is the one file left without a harness.&lt;/p&gt;
&lt;p&gt;On the host side, a golden differential encodes every request with litex&amp;rsquo;s own EtherbonePacket and decodes the C server&amp;rsquo;s replies through a pipe harness, plus 50 seeded random records against a Python reference model. A fork-drift test pins the sha256 of the vendored udp.c base and checks each load-bearing change is still there. And a conftest guard makes any UDP sendto off the loopback raise, because the broadcast-probe change had three tests emitting probes onto the bench LAN. Host pytest is 105 and the SoC tier is 48, the latter elaborating all three ethernet configurations through the real CLI.&lt;/p&gt;
&lt;p&gt;Mutation pass. Seven bugs planted one at a time, each required to fail its test, then reverted. The subnet-broadcast fix dropped, TX padding dropped, correlation id zeroed, port mirroring hardcoded, MAC-capture reply replaced with broadcast, and 8 bytes of slack either way in both bounds checks. The two bounds mutants survived the first time. The sweep was checking the reply-size contract and a canary but not what the parser did to the bus with truncated input. It now checks that a record section executes only if every one of its bytes arrived, against per-cut expectations across all 2149 cases, and that kills both.&lt;/p&gt;
&lt;p&gt;The old Verilator CSR bench is deleted. It drove the Wishbone RAM window and the run/stop control, both removed by the spec, and the structural test asserts the RAM window is gone, so the bench couldn&amp;rsquo;t even elaborate.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;make selftest&lt;/code&gt; is new. Five checks over the live board with no arguments, using the same discovery and bridge as &lt;code&gt;make login&lt;/code&gt;. Identifier read, console banner, an H command, a scratch write and readback with three patterns, and a 255-word burst read. The bridge startup window went from 5 s to 15 s because a board mid-DHCP can take that long to answer a probe. The board was powered down before it got a live run, so the code path is verified up to discovery only.&lt;/p&gt;
&lt;p&gt;Last, a GitHub Actions workflow mirroring the core repo&amp;rsquo;s. It bootstraps the gitignored LiteX environment on the pinned tag, installs oss-cad-suite, checks out intel-8008-vhdl for the core, and runs the C tests with the coverage gate, both pytest tiers and the SoC elaboration on every push.&lt;/p&gt;
&lt;p&gt;Next is TODO.md, which is the plan to get all 133 rows to pass or waived with cocotb, SymbiYosys and EQY, and a &lt;code&gt;b8008net load&lt;/code&gt; tool that paces a hex file against the monitor&amp;rsquo;s echo.&lt;/p&gt;</description></item><item><title>Spec first, and the gateware is wrong in 12 places</title><link>https://bytehamr.com/projects/litex-8008-node/logs/spec-first-12-divergences/</link><pubDate>Sat, 08 Aug 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/litex-8008-node/logs/spec-first-12-divergences/</guid><description>&lt;p&gt;I wrote SPEC.md and docs/VPLAN.md before correcting any RTL, and declared the spec authoritative. Every normative sentence has an ID like S-RX-3, every plan row cites one, and the plan bans the words &amp;ldquo;correctly&amp;rdquo;, &amp;ldquo;properly&amp;rdquo; and &amp;ldquo;as expected&amp;rdquo; from its pass conditions. Then I walked the existing gateware against it and wrote down where they disagreed. There were 12.&lt;/p&gt;
&lt;p&gt;The worst ones. The &lt;code&gt;cd_b8008&lt;/code&gt; reset synchronizer was gated only on the PLL lock, with no power-on reset or button in the fan-in and no ordering against &lt;code&gt;cd_sys&lt;/code&gt;, so the console logic and the core could come out of reset in either order. The console read register was a destructive pop, and LiteX&amp;rsquo;s &lt;code&gt;CommUDP&lt;/code&gt; retries a read that times out, so a dropped reply packet ate a byte silently. The RX FIFO dropped bytes on the floor when full instead of stalling the 8008. A TX write while full was discarded without a flag. And five retired control inputs, run/stop, step, interrupt request and vector, were left undriven at the netlist boundary going into the CPU&amp;rsquo;s run and interrupt logic.&lt;/p&gt;
&lt;p&gt;The fixes came in as their own commits. &lt;code&gt;ConsoleBridge&lt;/code&gt; got extracted from the integration module as a pure Migen block. &lt;code&gt;console_rx&lt;/code&gt; became a non-destructive read of data, valid and level in one word, with &lt;code&gt;console_rx_pop&lt;/code&gt; as a separate write. Backpressure went in with hysteresis at 4032 and 3968 bytes, gating the core&amp;rsquo;s &lt;code&gt;run_enable&lt;/code&gt; hold. A sticky &lt;code&gt;console_err&lt;/code&gt; register with three bits, where a set that coincides with a clear resolves in favor of set. TX writes while full are rejected and flagged. The &lt;code&gt;cd_b8008&lt;/code&gt; reset is now synchronized off the PLL lock and the board reset together, and ordered after the console logic&amp;rsquo;s reset. The dead controls, the hardcoded &lt;code&gt;triggered&lt;/code&gt; status and the Wishbone RAM window are gone from the CSR map and from the VHDL entity itself.&lt;/p&gt;
&lt;p&gt;SPEC.md was wrong twice. S-RST-3 said no reset reached &lt;code&gt;cd_b8008&lt;/code&gt; at all, and it did, it was just mis-gated. S-CDC-1 miscounted the crossings. Both corrections are noted in the document where they happened instead of being quietly edited.&lt;/p&gt;
&lt;p&gt;Then the first real bitstream build, yosys through ecppack. It succeeded, and the post-PnR report printed a critical path report for a cross-domain path from the etherbone clock to the b8008 clock. S-CLK-3 wants those two declared as separate clock groups. LiteX&amp;rsquo;s &lt;code&gt;add_false_path_constraints()&lt;/code&gt; turns out to be inert on the ECP5 Trellis toolchain and never reaches the generated &lt;code&gt;.lpf&lt;/code&gt; or nextpnr. I found no mechanism to declare the groups, so D-12 stays open.&lt;/p&gt;
&lt;p&gt;A final review pass found six plan rows in the test suite tagged against the wrong assertion, and one row, RX-6, had no genuine test behind its tag and went back to unimplemented. The count went from 32 to 31.&lt;/p&gt;
&lt;p&gt;Where it landed. 11 of 12 divergences resolved. 31 of 119 verification rows pass, 88 unimplemented, 11 imported from the core repo and never re-run here. The host package has 86 tests passing. &lt;code&gt;make login&lt;/code&gt; exists as a console client with discovery by cache, then DNS, then a subnet sweep. Nothing had run on the board yet when this closed.&lt;/p&gt;</description></item><item><title>Every module machine-checked, and the checkers find three bugs the board never showed</title><link>https://bytehamr.com/projects/intel-8008-vhdl/logs/every-module-machine-checked-and-the-checkers-find-three-bugs-the-board-never-showed/</link><pubDate>Sat, 08 Aug 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/intel-8008-vhdl/logs/every-module-machine-checked-and-the-checkers-find-three-bugs-the-board-never-showed/</guid><description>&lt;h2 id="before-this"&gt;Before this&lt;/h2&gt;
&lt;p&gt;On 2026-07-29 the carry look-ahead block got wired in as the ALU&amp;rsquo;s adder. It had compiled and had a testbench and nothing instantiated it. The toolchain was inferring the adder from numeric_std and the look-ahead was decorative. It now has a 9-bit carry-out and a sum output, SUB, SBB and CMP feed it two&amp;rsquo;s complement and invert the carry into the 8008 borrow flag, and an exhaustive testbench sweeps all 656,384 arithmetic cases against a reference model.&lt;/p&gt;
&lt;h2 id="what-i-tried-08-07"&gt;What I tried, 08-07&lt;/h2&gt;
&lt;p&gt;A synth-plugin target using the ghdl-yosys plugin, and a netlist-vhdl target that runs each module through Yosys write_vhdl so the round-tripped netlist can be checked against the RTL. Then, module by module: stack_pointer got PSL properties proven by k-induction under SBY plus a miter against its netlist, and a cocotb random walk that runs against both. state_timing_generator got a full property suite, 21 arcs and the status table, proven by k-induction. machine_cycle_control got bmc and cover. condition_flags k-induction. The ALU&amp;rsquo;s write_vhdl round trip was proven equivalent by an SBY miter. Combinational modules went through EQY. The instruction decoder got an exhaustive 256-opcode sweep against a Python model written from the datasheet, and that found issue #4.&lt;/p&gt;
&lt;p&gt;write_vhdl splits vector flops, which breaks EQY&amp;rsquo;s partition matching, so sequential modules use SBY miters instead. GitHub Actions runs all of it on push.&lt;/p&gt;
&lt;h2 id="what-i-tried-08-08"&gt;What I tried, 08-08&lt;/h2&gt;
&lt;p&gt;register_file, stack_memory, instruction_register, temp_registers, interrupt_ready_ff and ahl_pointer all got properties and miters. memory_io_control got a 21-scenario cocotb suite. The assembly regression suite joined CI with the AS assembler built from pinned source, which exposed that NUL bytes in the sim log had been blinding GNU grep. ANA, XRA and ORA joined the ALU sweep, now 1,049,600 cases. A READY/WAIT stress test, interrupt jams with NOP, HLT and 3-byte instructions and during WAIT, 14-bit PC wrap, a 48-combination conditional matrix, and composition proofs across the cycle-control and timing-generator cluster.&lt;/p&gt;
&lt;p&gt;The verification plan was audited row by row against the spec stack. All 15 open spec questions were ratified and isa.json realigned with the PDFs. Orphan modules and dead fabric were deleted.&lt;/p&gt;
&lt;h2 id="what-broke"&gt;What broke&lt;/h2&gt;
&lt;p&gt;Three RTL bugs, none of which the board had ever shown.&lt;/p&gt;
&lt;p&gt;The external cycle code on D6 and D7 was transposed relative to the datasheet&amp;rsquo;s table, and during H:L data cycles the raw H byte rode the bus where the code belongs. Nothing on the Versa decodes those bits, so silicon never noticed. A period-correct external controller would have. The new cocotb bus-protocol monitor caught it on its first run: 265 violations before the fix, 0 after, across 341 T1 and 341 T2 checks.&lt;/p&gt;
&lt;p&gt;The decoder flagged CPr with opcode 0xFF as memory-indirect.&lt;/p&gt;
&lt;p&gt;INP wasn&amp;rsquo;t driving the condition flip-flops onto the bus at PCC T4.&lt;/p&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;p&gt;Every checker was mutation-tested by planting a bug and confirming it fails. The differential fuzzer generates seeded random legal programs (forward-only jumps, leaf-only calls, terminating by construction) and runs them whole-system on both the RTL and netlist cores under three oracles: the bus monitor, a per-instruction datasheet timing check, and an rtl-vs-netlist trace diff. 20 seeds on both cores, all traces identical, zero violations. A dropped ALU inverter planted in the netlist was caught by the trace diff on 2 of 20 seeds.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan rows&lt;/th&gt;
&lt;th&gt;102&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Formal&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exhaustive&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Directed&lt;/td&gt;
&lt;td&gt;66&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Incidental&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Constraint&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gap&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Regression suite is 37 programs.&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;Nothing on the verification side. The README scorecard shows every module with something machine-checked behind it.&lt;/p&gt;</description></item><item><title>Ethernet bring-up: hardware Etherbone is dead on silicon, so Etherbone goes in software over broadcast</title><link>https://bytehamr.com/projects/litex-8008-node/logs/ethernet-bring-up-software-etherbone/</link><pubDate>Sat, 08 Aug 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/litex-8008-node/logs/ethernet-bring-up-software-etherbone/</guid><description>&lt;p&gt;Same day as the spec work, the bitstream went on the Versa for the first time.&lt;/p&gt;
&lt;p&gt;Timing came first. At the stock 75 MHz the sys and etherbone domain closed at about 66 MHz. &lt;code&gt;SYS_CLK_FREQ&lt;/code&gt; is 60e6 in the Makefile now and it closes with margin. The b8008 domain at 25 MHz was never the problem.&lt;/p&gt;
&lt;p&gt;With no network I needed a console, so &lt;code&gt;--debug-uart&lt;/code&gt; puts the LiteX UART on X3, FPGA transmit on B19 and receive on B12, into a 3.3 V FTDI. The firmware grew bring-up instrumentation to print there at boot. An MDIO scan finds the Marvell 88E1512 at address 0. Its MSCR2 reads 0x1076, meaning the PHY adds the RGMII delays internally in both directions, so the FPGA-side delays have to be 0 and 0. MAC RX and TX counters, and a PHY internal loopback self-test that passed 3 of 3.&lt;/p&gt;
&lt;p&gt;The CPU path through the plain ethmac worked. Gigabit link, loopback clean, and a DHCP lease from the LAN. That needed LiteEth vendored at master 276c9e3. The pinned 2026.04 release mangled the preamble on transmit frames in PHY loopback.&lt;/p&gt;
&lt;p&gt;The hybrid path is the part where LiteEth&amp;rsquo;s hardware does Etherbone, ARP and ICMP alongside the CPU&amp;rsquo;s MAC, and it&amp;rsquo;s what the spec assumed. On silicon it never answered. I tried LiteEth 2026.04 and master, data width 8 and 32, static IP and the dynamic mux, the stock &lt;code&gt;add_etherbone&lt;/code&gt; and a local reconstruction of it. Every permutation dead. The upstream &lt;code&gt;test/test_mac_hybrid.py&lt;/code&gt; passes in sim. I couldn&amp;rsquo;t find a public report of hybrid working at gigabit, and my guess is a rate or clock-crossing defect in the hardware path that doesn&amp;rsquo;t show at 100 Mbit. The bisect flags, &lt;code&gt;--stock-hybrid&lt;/code&gt; and &lt;code&gt;--eb-static-ip&lt;/code&gt;, stay in the tree, and &lt;code&gt;--ethmac-only&lt;/code&gt; is the production config.&lt;/p&gt;
&lt;p&gt;So Etherbone moved into firmware. &lt;code&gt;eb8008.c&lt;/code&gt; is a software Etherbone server on the VexRiscv speaking the litex CommUDP dialect, probe plus read records answered as writes with the request&amp;rsquo;s base return address as the correlation id, 32-bit big-endian. I validated it host-side against litex&amp;rsquo;s own encoder and decoder before flashing.&lt;/p&gt;
&lt;p&gt;Then the network on the bench got in the way. The mesh router, an MR60, drops or NATs unicast from a WiFi client to the wired LAN, and it negative-caches an unanswered ARP and never asks again. Broadcast gets through. So &lt;code&gt;udp.c&lt;/code&gt; is now a local fork of libliteeth&amp;rsquo;s. Upstream has a bug where the destination-equals-my-IP filter in process_frame makes the broadcast callback path unreachable. The fork fixes that and adds /24 subnet-broadcast acceptance, minimum-frame padding on transmit, a gratuitous ARP announce, a forced gateway ARP refresh, and &lt;code&gt;udp_set_peer&lt;/code&gt; so replies skip ARP. The serve loop takes a DHCP lease, then answers Etherbone on both the unicast and broadcast receive paths. Replies go unicast straight to the requester&amp;rsquo;s captured MAC and mirror the request&amp;rsquo;s ports.&lt;/p&gt;
&lt;p&gt;One more firmware trap. The generated &lt;code&gt;regions.ld&lt;/code&gt; gets rewritten by the builder with ROM shrunk to whatever the previous firmware measured, so any build that grows fails with &amp;ldquo;region rom overflowed&amp;rdquo;. &lt;code&gt;linker.ld&lt;/code&gt; has fixed ROM and SRAM ceilings now.&lt;/p&gt;
&lt;p&gt;Host side, &lt;code&gt;eb_server.py&lt;/code&gt; replaces &lt;code&gt;litex_server --udp&lt;/code&gt;. It broadcasts requests to the subnet, accepts unicast replies, and ignores the socket&amp;rsquo;s own broadcast echo. &lt;code&gt;discovery.py&lt;/code&gt; tries one broadcast probe before the cache, DNS and sweep. &lt;code&gt;board.py&lt;/code&gt; spawns this bridge instead of the stock server.&lt;/p&gt;
&lt;p&gt;Verified on the board: discovery finds it, a RemoteClient reads the SoC identifier, a CSR write reads back, and &lt;code&gt;make login&lt;/code&gt; lands at the 8008 monitor prompt over the LAN.&lt;/p&gt;
&lt;p&gt;What broke next. The monitor&amp;rsquo;s D and W commands return a constant 0x44 and writes don&amp;rsquo;t stick. This is the first time the b8008 console path has run on silicon and I haven&amp;rsquo;t looked at it yet.&lt;/p&gt;</description></item><item><title>5271 first power</title><link>https://bytehamr.com/projects/ibm-5160/logs/5271-first-power/</link><pubDate>Sat, 08 Aug 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/ibm-5160/logs/5271-first-power/</guid><description>&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;Inspected the 5271&amp;rsquo;s PSU. It looked fine and stayed original. Then I swapped video cards around between the two machines to get the 5160 live on the 5153 with the best of what I had. It took a few tries. An EGA card in CGA mode is what finally worked on that monitor. With the 5160 settled, the 5271 got a VGA card and came up next, with the Inboard 386 still installed.&lt;/p&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;p&gt;Both machines POST. The 5271 shows ERROR at boot with no code and continues.&lt;/p&gt;
&lt;h2 id="what-broke-or-surprised-me"&gt;What broke or surprised me&lt;/h2&gt;
&lt;p&gt;The 5153 is pickier than I expected about which EGA card it will show a picture from.&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;Hard drives.&lt;/p&gt;</description></item><item><title>Model F refurb</title><link>https://bytehamr.com/projects/ibm-5160/logs/model-f-refurb/</link><pubDate>Sat, 01 Aug 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/ibm-5160/logs/model-f-refurb/</guid><description>&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;Complete disassembly on both boards. Every key pulled and cleaned, the steel frame cleaned, then an electric duster at high RPM through everything. Loud.&lt;/p&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;p&gt;Nothing. It&amp;rsquo;s a passive device. I cleaned and tested every key.&lt;/p&gt;
&lt;h2 id="what-broke-or-surprised-me"&gt;What broke or surprised me&lt;/h2&gt;
&lt;p&gt;Nothing broke. Both work fabulously. I now understand what the eBay listings mean by &amp;ldquo;clicky.&amp;rdquo;&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;Get the 5271 up.&lt;/p&gt;</description></item><item><title>5160 PSU and first power</title><link>https://bytehamr.com/projects/ibm-5160/logs/5160-psu-and-first-power/</link><pubDate>Sat, 25 Jul 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/ibm-5160/logs/5160-psu-and-first-power/</guid><description>&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;Opened the 5160&amp;rsquo;s IBM 130 W supply. The RIFA cap had cracks in it, so I replaced it and the large ceramic next to it before applying power. Inspected every tantalum on the motherboard and the cards. Then the listen test with no video card, then a video card and the 5153.&lt;/p&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;p&gt;One long and one short beep with no video, which is the board complaining correctly. With video, a memory count and a picture.&lt;/p&gt;
&lt;h2 id="what-broke-or-surprised-me"&gt;What broke or surprised me&lt;/h2&gt;
&lt;p&gt;A lot of dust. It took some shaking off, and I needed the floppy and a keyboard working before it went anywhere useful. Both of those got their own sessions.&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;Keyboards.&lt;/p&gt;</description></item><item><title>Pickup in Chehalis</title><link>https://bytehamr.com/projects/ibm-5160/logs/pickup-in-chehalis/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/ibm-5160/logs/pickup-in-chehalis/</guid><description>&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;Met Dan in Chehalis after about four months of emails. He&amp;rsquo;d said what he had and I took as much of it as I could fit.&lt;/p&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;p&gt;Two machines, a 5160 and a 5271 with an Inboard 386 in it, an IBM 5153, two Model F keyboards, a 122-key 3270 keyboard, four ST-251s, and a box of ISA cards including the 3270 display adapter and host connect card. All Tektronix surplus, stored since the 1990s.&lt;/p&gt;
&lt;h2 id="what-broke-or-surprised-me"&gt;What broke or surprised me&lt;/h2&gt;
&lt;p&gt;Nothing had been powered on. The 3270 keyboard controller card wasn&amp;rsquo;t in the lot.&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;Inspect the power supplies before anything gets plugged in.&lt;/p&gt;</description></item><item><title>Carved out of the core repo, and the LiteX chain builds again</title><link>https://bytehamr.com/projects/litex-8008-node/logs/carved-out-of-the-core-repo-and-the-litex-chain-builds-again/</link><pubDate>Fri, 10 Jul 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/litex-8008-node/logs/carved-out-of-the-core-repo-and-the-litex-chain-builds-again/</guid><description>&lt;p&gt;The Ethernet monitor design spec went into the core repo on 2026-07-09, and the next day I pulled &lt;code&gt;projects/b8008_net&lt;/code&gt; out of &lt;code&gt;intel-8008-vhdl&lt;/code&gt; at commit 311df3f into a fresh repo called &lt;code&gt;remote_8008&lt;/code&gt;. Tracked files only. The vendored LiteX trees and build products get recreated by &lt;code&gt;make litex-env&lt;/code&gt;, which pins LiteX 2026.04 into a local venv. The top-level Python moved into &lt;code&gt;soc/&lt;/code&gt;, and &lt;code&gt;rom_4kx8_bram.vhdl&lt;/code&gt; came across from the monitor project.&lt;/p&gt;
&lt;p&gt;The core itself doesn&amp;rsquo;t get copied. It comes in as the FuseSoC core &lt;code&gt;greygiant:retro:b8008&lt;/code&gt; through its ghdl_synth_verilog generator, with &lt;code&gt;CORE_DIR&lt;/code&gt; pointing at the core checkout. This is the same arrangement the Byte Hamr card uses, so the 8008 has one source of truth and two consumers.&lt;/p&gt;
&lt;p&gt;Two things broke on the move. &lt;code&gt;soc/versa_soc.py&lt;/code&gt; computed the netlist path relative to its own directory instead of the repo-root &lt;code&gt;build/&lt;/code&gt;, so &lt;code&gt;make build&lt;/code&gt; couldn&amp;rsquo;t find what &lt;code&gt;make convert&lt;/code&gt; had just produced. The same fix had already gone into the bench script. And &lt;code&gt;host/tests/test_selftest.py&lt;/code&gt; still looked for &lt;code&gt;host_selftest.py&lt;/code&gt; one level above &lt;code&gt;host/&lt;/code&gt;, where it sat in the monorepo layout.&lt;/p&gt;
&lt;p&gt;The third thing wasn&amp;rsquo;t the repo. Every RISC-V compile, BIOS and firmware alike, died with &amp;ldquo;internal compiler error: Abort trap: 6&amp;rdquo;. &lt;code&gt;otool -L&lt;/code&gt; on &lt;code&gt;cc1&lt;/code&gt; showed it couldn&amp;rsquo;t load &lt;code&gt;libisl.23.dylib&lt;/code&gt; or &lt;code&gt;libmpc.3.dylib&lt;/code&gt;. &lt;code&gt;brew install isl mpfr&lt;/code&gt; fixed it. Homebrew&amp;rsquo;s riscv-gnu-toolchain had lost its runtime deps and was reporting it as a compiler bug.&lt;/p&gt;
&lt;p&gt;A fresh checkout has an ordering problem too. &lt;code&gt;make bootstrap-headers&lt;/code&gt; has to run before &lt;code&gt;make firmware&lt;/code&gt;, and it can&amp;rsquo;t be an automatic prerequisite without a circular edge between &lt;code&gt;build&lt;/code&gt; and &lt;code&gt;firmware&lt;/code&gt;. It&amp;rsquo;s in the README and a Makefile comment.&lt;/p&gt;
&lt;p&gt;After that, &lt;code&gt;make sim-core&lt;/code&gt;, &lt;code&gt;make sim-netlist&lt;/code&gt;, &lt;code&gt;make sim-bench&lt;/code&gt; and &lt;code&gt;make build&lt;/code&gt; all pass in the new layout. Nothing has been loaded on the board from this repo yet.&lt;/p&gt;</description></item><item><title>An Intel 8008 in the card, and a whole 8008 toolchain that lives on the Apple IIe</title><link>https://bytehamr.com/projects/byte-hamr/logs/an-intel-8008-in-the-card-and-a-whole-8008-toolchain-that-lives-on-the-apple-iie/</link><pubDate>Wed, 08 Jul 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/byte-hamr/logs/an-intel-8008-in-the-card-and-a-whole-8008-toolchain-that-lives-on-the-apple-iie/</guid><description>&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;The 8008 core is the cycle-exact one from my &lt;a href="https://bytehamr.com/projects/intel-8008-vhdl/"&gt;Intel 8008 VHDL&lt;/a&gt; project. GHDL synthesizes it to a Verilog netlist and Yosys takes that unmodified, so the Byte Hamr build never needs VHDL support. On 2026-07-07 I dropped it into &lt;code&gt;gateware/rev2/b8008_hamr/&lt;/code&gt; using the SDRAM monitor&amp;rsquo;s bus idiom: the bootstrap FSM that jams RST 0 at T1I, ported verbatim from the core&amp;rsquo;s monitor top; 64-deep TX and RX FIFOs on IN 1 and OUT 9 with USART snapshot-and-pop semantics; an OUT 31 checkpoint latch; reset and go controls; live PC registers; ID $B8 at &lt;code&gt;$C0CF&lt;/code&gt;. The 256-byte slot ROM is the whole glass terminal in 122 bytes, so &lt;code&gt;PR#4&lt;/code&gt; drops you at the 8008 monitor prompt with no OS in the loop. Ctrl-Q exits, Ctrl-R reboots the 8008 alone.&lt;/p&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/byte-hamr/b8008_monitor.png" alt="PR#4 from the BASIC prompt drops into the slot ROM terminal and the 8008 monitor answers. H lists the whole command set: dump, write, load hex, go." loading="lazy"&gt;
&lt;figcaption&gt;PR#4 from the BASIC prompt drops into the slot ROM terminal and the 8008 monitor answers. H lists the whole command set: dump, write, load hex, go.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I wanted to write the 8008 code on the Apple itself. The first leg was MAC8008, the 8008 instruction set as Merlin macros generated from the ISA JSON, with semicolon two-operand arguments like &lt;code&gt;MOV A;B&lt;/code&gt;. B8RUN reads a ProDOS BIN whose aux type is the ORG, turns it into Intel hex and streams it through the FIFO to the monitor&amp;rsquo;s L command. B8CMP byte-compares two files through the MLI. The second leg, 07-08, was ASM8, a native 8008 assembler written in 6502 that takes true 8008 syntax, &lt;code&gt;MOV A,B&lt;/code&gt; and &lt;code&gt;MVI C,41h&lt;/code&gt;, with a table generated from the same ISA JSON. A8 wraps it: an 80-column full-screen editor, ^A assembles in memory and jumps the cursor to an error line, ^G streams the object into the card and drops into the monitor, ^Q comes back to the editor with the buffer intact. On 07-10 the vendored netlist went away and FuseSoC generates it at build time from the core repo.&lt;/p&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/byte-hamr/a8_assembled.png" alt="A8 after ^A on HELLO8R.ASM, the verbatim upstream ASL source in 8008new syntax. The status row reports OK $01C9 AT $2040: 457 bytes of object at the ORG, the same size as the ASL reference. ^G streams it into the card." loading="lazy"&gt;
&lt;figcaption&gt;A8 after ^A on HELLO8R.ASM, the verbatim upstream ASL source in 8008new syntax. The status row reports OK $01C9 AT $2040: 457 bytes of object at the ORG, the same size as the ASL reference. ^G streams it into the card.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/byte-hamr/a8_load.png" alt="^G from A8, then the run. The editor streams Intel hex into the 8008 monitor&amp;#39;s L command, one dot per record, the monitor answers OK, and A8 hands the screen over as a TTY with the G address printed. It stops there on purpose. I typed G 2040, the 8008 printed HI and 0123456789 B8008-OK, and the monitor came back." loading="lazy"&gt;
&lt;figcaption&gt;^G from A8, then the run. The editor streams Intel hex into the 8008 monitor&amp;rsquo;s L command, one dot per record, the monitor answers OK, and A8 hands the screen over as a TTY with the G address printed. It stops there on purpose. I typed G 2040, the 8008 printed HI and 0123456789 B8008-OK, and the monitor came back.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Core in the ECP5: 1453 LUTs, 433 flip-flops, 10 DP16KD, Fmax 74 MHz to 95 MHz at a 25 MHz clock.&lt;/li&gt;
&lt;li&gt;First flash: PR#4, monitor banner, live prompt. H, D, W, G all work. A program hand-keyed with W at &lt;code&gt;$2000&lt;/code&gt; printed 8008 and restarted the monitor.&lt;/li&gt;
&lt;li&gt;ECHO8, 37 bytes, written in Merlin Pro on the IIe with MAC8008: the object dumped with D matched the Merlin32 build byte for byte. Loaded with B8RUN, ran, echoed keys, ESC returned to the monitor.&lt;/li&gt;
&lt;li&gt;MAC8008 against the ASL golden hex: 6 of 6 upstream samples byte-identical, hello_8008_ram at 88 of 88 bytes.&lt;/li&gt;
&lt;li&gt;ASM8 acceptance: HELLO8R.ASM, the verbatim upstream source, assembled on the IIe and B8CMP against the 457-byte ASL reference object reported PASS. The Python model of the same spec had already passed 6 of 6 samples on the Mac, so the bench only tested the 6502 implementation.&lt;/li&gt;
&lt;li&gt;A8: ^A on the same file reports OK $01C9 AT $2040, and the object it writes passes the same B8CMP gate. I then wrote COUNT8 from scratch in the editor, assembled it and ran it on the card.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-broke-or-surprised-me"&gt;What broke or surprised me&lt;/h2&gt;
&lt;p&gt;B8RUN&amp;rsquo;s first bench run went wild. The FIFO put routine clobbered X and Y, the record loop ran off the end, and a 64K read sweep walked straight through the &lt;code&gt;$C0xx&lt;/code&gt; soft switches: garbled screen, and the slot 6 drive motors came on in write mode. I checked the floppies afterwards. The driver preserves X and Y now, and I found five more 6502-side bugs the same session, none needing a reflash: the MLI prefix is empty at boot so raw MLI calls fail with $40 until you set it; an error printer that destroyed the code before printing it; lowercase input that ProDOS rejects; an unattended load that overflowed the 64-byte TX FIFO and dropped the monitor&amp;rsquo;s OK, fixed by draining TX to the screen per record.&lt;/p&gt;
&lt;p&gt;Merlin Pro on the IIe loaded the whole 200-line macro file and defined every macro, then threw Operand too long on the generator&amp;rsquo;s 72-column header comment. Merlin Pro enforces line width and Merlin32 does not. Every generated source is clamped to 40 columns now. Real Merlin also scopes local labels between globals, so self-modifying loops use global labels only, and &lt;code&gt;#','&lt;/code&gt; will not parse, so it is &lt;code&gt;#$2C&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The monitor&amp;rsquo;s W command chokes on trailing text. My first freeze was annotations typed in with the listing, which the monitor faithfully wrote as garbage into a $00 HLT wall. A program that waits silently for input also reads as hung; the first ECHO8 run got Ctrl-R&amp;rsquo;d during its own input wait.&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;SCELBAL as a second personality, a direct load and readback port for RAM snapshots, and merging the block device so the card boots itself. None of it started.&lt;/p&gt;</description></item><item><title>Cycle-exact T-states, then SCELBAL, then booting straight into it</title><link>https://bytehamr.com/projects/intel-8008-vhdl/logs/cycle-exact-t-states-then-scelbal-then-booting-straight-into-it/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/intel-8008-vhdl/logs/cycle-exact-t-states-then-scelbal-then-booting-straight-into-it/</guid><description>&lt;h2 id="cycle-exact"&gt;Cycle-exact&lt;/h2&gt;
&lt;p&gt;The ISA table in docs/isa.json always said which T-slots each machine cycle skips. The RTL ran every cycle a fat five states. Now cycles end where the table says: fetch cycles of multi-cycle instructions end at T3 (except LMr, which does its register write at T4), middle cycles of 3-cycle instructions end at T3, a not-taken conditional RET is 3 states, and not-taken conditional jumps and calls drop their last two states, 11 down to 9. The new cycle_done path goes T3 or T4 back to T1 mid-instruction without the interrupt check, again per Figure 2.&lt;/p&gt;
&lt;p&gt;The first cut skidded. Fetch-cycle decisions have to evaluate in the second half of T3, because at T3 entry the instruction register still holds the previous opcode and the decoder flags are stale. A new regression runs one instruction per timing class, counts simulated states between fetch markers, and diffs against the table.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Timing classes cycle-exact&lt;/td&gt;
&lt;td&gt;27/27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regression suite&lt;/td&gt;
&lt;td&gt;28/28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interrupt suite&lt;/td&gt;
&lt;td&gt;10/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bitstream timing at 25 MHz&lt;/td&gt;
&lt;td&gt;93 MHz clk / 114 MHz&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Pi visibly prints faster on the thin cycles.&lt;/p&gt;
&lt;h2 id="scelbal-from-ram"&gt;SCELBAL from RAM&lt;/h2&gt;
&lt;p&gt;RAM grew to 12 KB, 0x1000 to 0x3FFF, to fit a 7.3 KB interpreter under the monitor. The ledger for Jim Loos&amp;rsquo;s build came to six changes: the variable-page EQUs, the program-space bounds, one ORG, four deleted init lines, the two USART I/O shims (only A and B touched, output preserves A with the MSB restored, per SCELBAL&amp;rsquo;s documented contract), and CRLF in the banner. I added an auto-SCR at cold entry so G 2000 would land in initialized BASIC, then reverted it the same evening. Jim&amp;rsquo;s manual SCR stays.&lt;/p&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/intel-8008-vhdl/scelbal.png" alt="First SCELBAL run, RAM-resident, loaded through the monitor and started with G 2000. FOR/NEXT with PRINT I&amp;#43;1." loading="lazy"&gt;
&lt;figcaption&gt;First SCELBAL run, RAM-resident, loaded through the monitor and started with G 2000. FOR/NEXT with PRINT I+1.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Loading it exposed weak spots in my send_hex script, which now fails loudly on silently dropped records, counts the EOF record&amp;rsquo;s OK correctly, and refuses to send unless it sees the monitor prompt first.&lt;/p&gt;
&lt;h2 id="the-tiny-os"&gt;The tiny OS&lt;/h2&gt;
&lt;p&gt;Loading BASIC through a monitor every boot is a workflow. Booting into it feels like a computer. New build, new memory map: RAM 4 KB at 0x0000, 12 KB ROM at 0x1000 holding the monitor and SCELBAL, and three bytes of block-RAM initialization at address zero, &lt;code&gt;JMP 1800h&lt;/code&gt;. The memory map became generics on b8008_top and the ROM address widened to 14 bits so both builds are the same top.&lt;/p&gt;
&lt;p&gt;MON lives in an unused zero-filled slack region of SCELBAL&amp;rsquo;s keyword lookup table, every original offset preserved, and jumps to the monitor. G 1FB6 warm-enters BASIC&amp;rsquo;s executive without re-initializing, so the program survives the trip. The boot vector was verified in the synthesized netlist: ram[0..2] is 44 00 18.&lt;/p&gt;
&lt;p&gt;Relocating my own monitor exposed a hardcoded page-low-byte in its command buffer, the same class of bug as calc&amp;rsquo;s FININP the day before.&lt;/p&gt;
&lt;p&gt;The full ceremony passed in a testbench over the real RTL with a real UART: power-on to banner with nothing typed, SCR and a program and RUN, MON to the monitor prompt, W and D alive there, G 1FB6 and LIST intact. Then on the board the same evening.&lt;/p&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/intel-8008-vhdl/tiny_os_scelbal.png" alt="Boot-to-BASIC build on the ECP5. SCELBAL from ROM, a FOR/NEXT loop, MON dropping to the monitor to dump the tokenized program at 0100, and G 1FB6 back into BASIC with the program still there." loading="lazy"&gt;
&lt;figcaption&gt;Boot-to-BASIC build on the ECP5. SCELBAL from ROM, a FOR/NEXT loop, MON dropping to the monitor to dump the tokenized program at 0100, and G 1FB6 back into BASIC with the program still there.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;Prove the thing beyond my own tests. The self-test passing means the CPU agrees with my reading of the datasheet, which is circular. The period software helped. Formal is the next step.&lt;/p&gt;</description></item><item><title>Load anything over serial, and 1974 finds five CPU bugs the self-test missed</title><link>https://bytehamr.com/projects/intel-8008-vhdl/logs/load-anything-over-serial-and-1974-finds-five-cpu-bugs-the-self-test-missed/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/intel-8008-vhdl/logs/load-anything-over-serial-and-1974-finds-five-cpu-bugs-the-self-test-missed/</guid><description>&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;L streams Intel HEX from the UART into RAM, with checksum, resync at the next colon, a &amp;lsquo;.&amp;rsquo; or &amp;lsquo;?&amp;rsquo; per record and an OK or ERR verdict. G runs an address through a JMP trampoline at 0x3F80, since the 8008 has no indirect jump. Monitor scratch moved to the 0x3F00 page so a payload owns 0x2000 to 0x3EFF. RST 1 through 7 ROM vectors forward to 8-byte RAM slots at 0x3FC0 plus n times 8, and an uninstalled slot reads 0x00, which is HLT. Senders have to pace: the USART holds one RX byte and the echo path costs about 4 ms per character.&lt;/p&gt;
&lt;p&gt;Then five programs from Mike Willegal&amp;rsquo;s SCELBI apps page. The recipe for each: relocate into 0x2000 to 0x3EFF, swap the bit-banged serial for OUT 9 and IN 1, exit with &lt;code&gt;jmp 0&lt;/code&gt; instead of HLT. Everything else stays the author&amp;rsquo;s bytes, and each change is in the commit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mandelbrot.&lt;/strong&gt; &lt;code&gt;org 2040h&lt;/code&gt; so the page-zero data offsets stay valid on page 0x20. That&amp;rsquo;s the whole change.&lt;/p&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/intel-8008-vhdl/mandlbrot.png" alt="SCELBI Mandelbrot renderer loaded over serial and started with G 2040. Only change from the original was the ORG." loading="lazy"&gt;
&lt;figcaption&gt;SCELBI Mandelbrot renderer loaded over serial and started with G 2040. Only change from the original was the ORG.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Pi.&lt;/strong&gt; Page-zero variables moved out of what is now ROM, and its RST 7 handler reaches the output routine through a forwarded RAM vector slot. The port also surfaced a bug in the original: the 2013-era bit-bang cout ends its delay loop with B at 0, and prbcd keeps its BCD digit pair in B across that call, so every second digit prints as &amp;lsquo;0&amp;rsquo; on any hardware wired that way. My USART version paces on A and leaves B alone.&lt;/p&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/intel-8008-vhdl/pi.png" alt="Pi digit generator. 49 of 50 digits correct. The 50th is guard-byte truncation in the original algorithm." loading="lazy"&gt;
&lt;figcaption&gt;Pi digit generator. 49 of 50 digits correct. The 50th is guard-byte truncation in the original algorithm.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;HEXPAWN (1973).&lt;/strong&gt; Learns by modifying its own move lists, which is why it has to run from RAM. Page literals bumped by 0x20. These programs compare against MSB-set ASCII, and the monitor&amp;rsquo;s ready byte is already MSB plus character, so input needed nothing. It beat me repeatedly and got better at it.&lt;/p&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/intel-8008-vhdl/hexspawn.png" alt="HEXPAWN from 1973, running from RAM at 2100 because it modifies its own move lists as it learns." loading="lazy"&gt;
&lt;figcaption&gt;HEXPAWN from 1973, running from RAM at 2100 because it modifies its own move lists as it learns.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Calc (1974).&lt;/strong&gt; SCELBI&amp;rsquo;s 23-bit floating point package with self-modifying result dispatch. Moving its work area from page 0 to 0x2000 found a bug in the original FININP: one site sets the destination page with &lt;code&gt;xra a&lt;/code&gt; then &lt;code&gt;mov d,a&lt;/code&gt;, hardcoding page zero instead of loading it from H. The converted mantissa was being written into monitor ROM, the write died silently, and every result printed +0.000000E+00. The fix had to be &lt;code&gt;mov d,h&lt;/code&gt;, same byte count, because the self-modifying code around it depends on intra-page addresses staying put.&lt;/p&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/intel-8008-vhdl/calc.png" alt="SCELBI floating-point calculator, 1974. 12.2 x 5.11 = 62.34202 through a 23-bit FP package, after the FININP page fix." loading="lazy"&gt;
&lt;figcaption&gt;SCELBI floating-point calculator, 1974. 12.2 x 5.11 = 62.34202 through a 23-bit FP package, after the FININP page fix.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Shooting Stars (Byte, May 1976).&lt;/strong&gt; Talks through RST 6 and RST 7. A small boot shim installs forwarding stubs in the RAM vector slots and jumps to the unmodified game. It wants DEL (0x7F) for rubout, and echoes a backslash per deleted character, because there&amp;rsquo;s no cursor addressing in 1976.&lt;/p&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/intel-8008-vhdl/stars.png" alt="Shooting Stars from Byte, May 1976. Talks through RST 6 and RST 7 vectors that a boot shim forwards to the monitor." loading="lazy"&gt;
&lt;figcaption&gt;Shooting Stars from Byte, May 1976. Talks through RST 6 and RST 7 vectors that a boot shim forwards to the monitor.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="what-broke"&gt;What broke&lt;/h2&gt;
&lt;p&gt;Five things in the CPU, each found by one of those programs, each fixed in one module and re-proven on the board.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;INR and DCR must preserve carry. The FP package does multi-byte arithmetic with increment-driven loops between carry-chained adds, and my ALU clobbered carry on every increment.&lt;/li&gt;
&lt;li&gt;Rotates write carry only. Z, S and P must be preserved.&lt;/li&gt;
&lt;li&gt;A real WAIT state. READY parks the CPU between T2 and T3. My first attempt was a PC freeze on the side, and it double-fetched.&lt;/li&gt;
&lt;li&gt;Interrupts at instruction boundaries only. Figure 2 of the User&amp;rsquo;s Manual reaches the INTERRUPTED? decision only when execution is complete. Mine could hijack a multi-cycle instruction between its own machine cycles. An interrupt storm of RST 7s into a spinning taken-jump loop crashed on silicon until this, with the post-handler resume landing one byte past the jump target.&lt;/li&gt;
&lt;li&gt;The PC lives in the address stack. The real chip has no separate program counter. The PC is whichever of the eight 14-bit stack registers SP points at. CALL is SP moving on and the old slot keeps the return address, with no copy. RET is SP moving back. I rebuilt it that way and converted fetch to post-increment. The &lt;code&gt;pc_was_loaded&lt;/code&gt; flag and the pre-computed return addresses deleted themselves. The stack wrap now emerges from the structure. One consequence: the bootstrap RST-0 jam consumes one level for good, so programs get six safe nesting levels, same as real silicon booted the same way.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="what-else-went-in"&gt;What else went in&lt;/h2&gt;
&lt;p&gt;The self-test grew to 46 with rotate flag preservation and INR/DCR carry cases. A parameterized 8 KB RAM behind an address decoder. The USART&amp;rsquo;s RX read became an atomic snapshot-and-pop after a race. Front-panel switches: sw(6) holds READY low to freeze the CPU in WAIT, sw(5) fires an interrupt with sw(7) picking RST 5 or RST 7, both with hltwake and intstorm test programs. A dead-board regression from a vector-mux race and inverted switch polarity got fixed the same day.&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;Cycle-exact T-states, then SCELBAL.&lt;/p&gt;</description></item><item><title>The monitor finally talks, and the flaky boot was the level shifters</title><link>https://bytehamr.com/projects/intel-8008-vhdl/logs/the-monitor-finally-talks-and-the-flaky-boot-was-the-level-shifters/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/intel-8008-vhdl/logs/the-monitor-finally-talks-and-the-flaky-boot-was-the-level-shifters/</guid><description>&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;Fetch-capture instrumentation in the FPGA: latch every T3&amp;rsquo;s address and data onto the LEDs, and read them out when the CPU wedges. A black-box recorder that holds the killing instruction. Then a standalone rom_diag firmware that hex-dumps the CPU&amp;rsquo;s own view of its first 256 ROM bytes plus a RAM write and readback pattern, forever, with no receive path and no parsing. That proved the CPU, BRAM and UART transmit chain clean on their own.&lt;/p&gt;
&lt;h2 id="what-broke"&gt;What broke&lt;/h2&gt;
&lt;p&gt;Several independent things, which is why it took months.&lt;/p&gt;
&lt;p&gt;The HW-221 breakouts are TXS0108E auto-direction shifters. I was running the 5 V EEPROM&amp;rsquo;s outputs into a bank whose VCCB rail was at 3.3 V. That&amp;rsquo;s an absolute-maximum violation on every read. Those parts wedge into a bad state until power-cycled, which is why boot reliability changed when I power-cycled the shifter board and nothing else. Auto-direction one-shot shifters were the wrong part for a memory bus regardless.&lt;/p&gt;
&lt;p&gt;The bootstrap FSM was clocked on the derived phi2 signal with an async reset, so every path into it was unconstrained. Glitches re-jammed RST 0 (spontaneous restart to the banner) or re-fired the post-bootstrap hardware break (freeze). It now advances on a phi2 rising-edge enable inside the 25 MHz domain. No clock crossing remains.&lt;/p&gt;
&lt;p&gt;The D command printed every low nibble as 0. send_hex_byte parked its byte in B, and char_delay counts B down to zero. Saved in C instead.&lt;/p&gt;
&lt;p&gt;Weeks of &amp;ldquo;memory corruption&amp;rdquo; in the monitor turned out to be my backspace keystrokes going into the command buffer as raw 0x08 bytes. The hex parser stopped at them and mangled addresses. The CPU was never wrong about that one.&lt;/p&gt;
&lt;h2 id="what-changed"&gt;What changed&lt;/h2&gt;
&lt;p&gt;ROM went back inside the FPGA. The Yosys ROM problem from January got solved properly this time: a synchronous-read ROM that Yosys infers as DP16KD block RAM, with the one-clock latency invisible to the 8008. Firmware updates now go through ecpbram, which patches ROM contents into the post-PnR config. Unused ROM space is seeded with random bytes so ecpbram has a unique pattern to match, and it fails loudly if the pattern is missing, so the old silent corruption can&amp;rsquo;t come back silently.&lt;/p&gt;
&lt;p&gt;The data RAM also went from async-read distributed LUTRAM with &amp;lsquo;Z&amp;rsquo; outputs to synchronous block RAM. That LUTRAM was the design&amp;rsquo;s critical path.&lt;/p&gt;
&lt;p&gt;Auto-start: a synthetic run press 2 ms after reset release, so the board boots hands-free. A W command for writing bytes. A boot testbench over the full top with a PLL stub and UART decoder, power-on to banner byte.&lt;/p&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Thing&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Firmware iteration&lt;/td&gt;
&lt;td&gt;4 min resynthesis&lt;/td&gt;
&lt;td&gt;0.66 s ecpbram&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fmax&lt;/td&gt;
&lt;td&gt;46 MHz&lt;/td&gt;
&lt;td&gt;116 MHz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T-state&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;~4.4 us (real chip 4 us at 500 kHz)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ISA self-test on silicon&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;42/42&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The self-test ROM runs on the FPGA and reports per test over serial: every ALU op in register, immediate and memory form, carry and borrow chains, all rotates, sign, parity, zero and carry branches, conditional CALL and RET both ways, 6-deep call nesting, a RAM pattern walk. The only bring-up failure was a wrong expected value in my own test. The CPU summed 0x11 times 28 to 0x1DC and the test wanted 0x1BC.&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;L to load Intel HEX over serial and G to run it. First payload is the 1974 SCELBI Mandelbrot from Mike Willegal&amp;rsquo;s page.&lt;/p&gt;</description></item><item><title>Conway's Multiverse: a soft 6502 on the card runs eight Life universes while the IIe watches</title><link>https://bytehamr.com/projects/byte-hamr/logs/conway-s-multiverse-a-soft-6502-on-the-card-runs-eight-life-universes-while-the-iie-watches/</link><pubDate>Wed, 10 Jun 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/byte-hamr/logs/conway-s-multiverse-a-soft-6502-on-the-card-runs-eight-life-universes-while-the-iie-watches/</guid><description>&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;The PicoPort&amp;rsquo;s PIO is driven by a small internal RISC-V, and that planted the question of whether the Byte Hamr could run its own processor next to the host. The ECP5 had the room. A 6502 made sense for this machine, so I put Arlet Ottens&amp;rsquo; verilog-6502 in with its own block RAM and a &lt;code&gt;$E000&lt;/code&gt; write window that posts one SDRAM write through a two-client priority arbiter, monitor first, coprocessor second. Everything is one 25 MHz domain, no CDC.&lt;/p&gt;
&lt;p&gt;C0 on 2026-06-08 was the steel thread: the coprocessor runs LDA #$42, STA $E000 from its own BRAM, the byte lands in SDRAM, and from the monitor &lt;code&gt;B 000&lt;/code&gt; then &lt;code&gt;R 0040&lt;/code&gt; prints 0040: 42. C1 the same day was a resident, hardware-protected kernel the host registers tasks into. C2 was a cooperative scheduler: two Merlin tasks race, the host reads 0102, retune the budgets and it reads 0201. C3 added a preemptive tick timer and an IRQ into the Arlet core, and a third task with no yields still gets sliced. C3.1 made the GO trigger a nonzero write so a stale count could not re-arm across runs. C-flash on 06-09 saves the task registry to flash and restores it on boot, with a magic-last header so a torn write fails safe. C4 the same day was async skill dispatch: a control-plane register block, a dispatcher inside the ISR, a slot mailbox ABI so a skill is reentrant, and CPLIB on the host with CP_CALL, POLL, WAIT and RESULT. Then a two-step &lt;code&gt;$E004&lt;/code&gt; to &lt;code&gt;$E008&lt;/code&gt; auto-increment read window, so the coprocessor can read SDRAM as well as write it. The demo needs that.&lt;/p&gt;
&lt;p&gt;The demo is Conway&amp;rsquo;s Life, eight of them. LIFE8 is one registered skill that loops over eight independent universes in SDRAM forever, bit-packed cells, a three-row sliding window so each row is read once, wrap-around edges. The IIe picks a universe with the 0 to 7 keys, draws it, and otherwise does nothing. Switch away, come back, it has moved on.&lt;/p&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/byte-hamr/multiverse_hero.png" alt="Conway&amp;#39;s Multiverse. Eight Life universes evolving in the card&amp;#39;s SDRAM on the soft 6502; the Apple IIe is only the viewer." loading="lazy"&gt;
&lt;figcaption&gt;Conway&amp;rsquo;s Multiverse. Eight Life universes evolving in the card&amp;rsquo;s SDRAM on the soft 6502; the Apple IIe is only the viewer.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;C0 build: about 2% of LUTs, 4 EBR, logic Fmax around 72 MHz against the 25 MHz clock.&lt;/li&gt;
&lt;li&gt;Double Hi-Res grid, 560 by 192, about 107,000 cells: a few seconds per generation per universe on a 25 MHz 6502 doing one cell&amp;rsquo;s neighbors at a time.&lt;/li&gt;
&lt;li&gt;Lo-Res grid, 40 by 48, about 56 times fewer cells: near real time with fat blocks. Both ship.&lt;/li&gt;
&lt;li&gt;Gliders hold shape and wrap the torus. In simulation one crossing a packed-byte boundary lands one over and one down every four generations. The R-pentomino on channel 3 settles into beehives, blocks, blinkers and beacons.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Lo-Res version, since it is the one you can actually watch:&lt;/p&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/byte-hamr/gliders.png" alt="Gliders in flight, wrapping around the torus edges." loading="lazy"&gt;
&lt;figcaption&gt;Gliders in flight, wrapping around the torus edges.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/byte-hamr/rpentomino-ash.png" alt="Channel 3 after the R-pentomino settled: beehives, blocks, blinkers and beacons." loading="lazy"&gt;
&lt;figcaption&gt;Channel 3 after the R-pentomino settled: beehives, blocks, blinkers and beacons.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/byte-hamr/soup-oscillators.png" alt="Soup that has organized. The box-and-bars structure is a period-2 oscillator, with beehives and a stray glider around it." loading="lazy"&gt;
&lt;figcaption&gt;Soup that has organized. The box-and-bars structure is a period-2 oscillator, with beehives and a stray glider around it.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img src="https://bytehamr.com/img/byte-hamr/soup-growing.png" alt="A different random seed mid-evolution, structures still resolving." loading="lazy"&gt;
&lt;figcaption&gt;A different random seed mid-evolution, structures still resolving.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="what-broke-or-surprised-me"&gt;What broke or surprised me&lt;/h2&gt;
&lt;p&gt;Yosys will not infer a DP16KD with two write ports, so the kernel BRAM has one shared write port and a count-last rule. The Arlet core needed a registered synchronous data-in and a gate on SDRAM ready. A cooperative task has to end with JMP DONE and not RTS. Re-arming the count register is a level, so wait for DONE before re-arming or the kernel hangs.&lt;/p&gt;
&lt;p&gt;The Gosper glider gun I seeded on channel 0 collapsed. On a torus the gliders come all the way round and hit the gun. Guns need an open field.&lt;/p&gt;
&lt;p&gt;Lo-Res and Double Hi-Res are different memory formats. DHGR packs 7 pixels per byte so the render was a copy; Lo-Res is two stacked 4-bit color blocks per byte, so the renderer expands each cell bit into a $0 or $F nibble. And the double buffer that kept DHGR clean tears at Lo-Res speed, because the core laps the host inside one frame. The fix is a generation-stamped snapshot: copy the grid, check the generation counter did not change, retry if it did. Cloning the engine also broke on the row stride, which was a hand-written MUL80 and had to become MUL5 for 5-byte rows.&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;Other things that run on the card without the host asking. A live database the IIe queries, or a game where the day passes and crops grow whether or not you are looking. That became the farm.&lt;/p&gt;</description></item><item><title>Rev 2 cannot bus-master, so the hypervisor saves by shadowing and restores through the 6502</title><link>https://bytehamr.com/projects/byte-hamr/logs/rev-2-cannot-bus-master-so-the-hypervisor-saves-by-shadowing-and-restores-through-the-6502/</link><pubDate>Sat, 06 Jun 2026 00:00:00 +0000</pubDate><guid>https://bytehamr.com/projects/byte-hamr/logs/rev-2-cannot-bus-master-so-the-hypervisor-saves-by-shadowing-and-restores-through-the-6502/</guid><description>&lt;h2 id="what-i-tried"&gt;What I tried&lt;/h2&gt;
&lt;p&gt;The idea after the SDRAM monitor was a snapshot hypervisor: treat a whole booted machine, main 64K plus aux 64K plus registers and soft switches, as a guest that lives in one SDRAM context bank. Boot a disk, hit a hyperkey, the card traps the 6502 with NMI and INH, pages the guest out to SDRAM, pages the supervisor back in. The obvious fast path is DMA: pull the DMA line, halt the 6502, and let the card copy RAM to SDRAM itself.&lt;/p&gt;
&lt;p&gt;On 2026-06-05 I went through the KiCad netlist to wire that up and found there is nothing to wire. The three address-bus level shifters, U8 for A0 to A6, U11 for A7 to A13 and U13 for A14 and A15, have DIR tied to +5 V and OE to ground. They pass slot to FPGA and nothing else. R/W is input-only at the FPGA on D10. The card can pull DMA low and stop the 6502, and after that no one can drive an address. The data bus through U12 is reversible and NMI, INH and RES are FPGA outputs, so the trap works, but nothing can drive an address.&lt;/p&gt;
&lt;p&gt;So the mechanism changed. Save is continuous write-through shadowing: the card snoops every qualified CPU write, PHI0 falling with R/W low in a RAM region, and mirrors the byte into the active context&amp;rsquo;s SDRAM bank as it happens. The 6502 only drives the bus in phase 0, video and refresh are phase 1 reads, read-modify-write dummy writes self-correct because the last write wins, and the &lt;code&gt;STA abs,X&lt;/code&gt; dummy cycle is a read, so that condition is enough. A soft-switch tracker supplies the main-or-aux bank bit per cycle, since RAMWRT and friends are not on the address bus. Restore is the 6502 pulling bytes out of the &lt;code&gt;$C0Cx&lt;/code&gt; port with hardware auto-increment and storing them, with 256-byte dirty-page tracking so only clobbered pages get copied.&lt;/p&gt;
&lt;h2 id="what-i-measured"&gt;What I measured&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;SDRAM at 100 MHz against a roughly 1 MHz write stream: about 100:1, so the shadow keeps up with no buffering.&lt;/li&gt;
&lt;li&gt;Estimated switch cost: 3.3 s for a naive full copy, 1.7 s with write-through save, 0.3 s target with dirty-page restore. A Rev 3 with DMA would be 50 ms to 100 ms.&lt;/li&gt;
&lt;li&gt;06-06, restore on the bench: POKE 8192,17 into context 0, switch context, scribble 238 over the same RAM, BRUN SDMREST, PEEK(8192) reads 17. First 4 KB slice, &lt;code&gt;$2000&lt;/code&gt; to &lt;code&gt;$2FFF&lt;/code&gt; main, software only, no gateware change.&lt;/li&gt;
&lt;li&gt;06-07, trap unit 5c-2b: NMI vector hardwired to a handler in the &lt;code&gt;$C800&lt;/code&gt; ROM with INH forcing the fetch, captures a clean frame even with the vector page trashed and INTCXROM set.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-broke-or-surprised-me"&gt;What broke or surprised me&lt;/h2&gt;
&lt;p&gt;The SYNC line on B11 looked useful for the write snoop and is not. It marks opcode fetches, which never coincide with a write. It is parked as a future trace primitive.&lt;/p&gt;
&lt;p&gt;BRUN needs ProDOS booted. Bare Applesoft gives ?SYNTAX ERROR because there is no DOS to hook the command.&lt;/p&gt;
&lt;p&gt;Time-slicing the one host 6502 through this machinery costs seconds per switch, too slow to use it as a helper. A second CPU inside the FPGA gives real concurrency with no switch at all. That became the coprocessor, and the hypervisor stopped at 5c-2b on branch &lt;code&gt;obscurus-sdram-monitor&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;Rev 3 needs DIR and OE on U8, U11 and U13 routed to the FPGA plus an R/W output. I am not going to bodge three transceivers on a Rev 2 board. When Rev 3 happens, DMA drops in behind the same trap and vault logic as a speedup.&lt;/p&gt;</description></item></channel></rss>