Skip to content

BACKLOG — unbuilt work, open questions, verification still to do (2026-09-20)

Gathered from the card/folder READMEs and the old notes so that pending work is visible in one place. Built state: docs/system/MACHINE.md. Which revisions exist and were fabricated: hardware/FABRICATED.md.

Hardware — designed, not built

  • Memory card: the FORCE-ROM race fix (review M1, 2026-09-30) - a 470 Ω pull-down on ADDR15. Undriven, A15 floats high, so IC12 is clocked ~40 ns before the real address arrives. v1.3 in the machine: fit 470 Ω from IC10 pin 4 to IC10 pin 7 on the solder side (docs/cards/memory.md 4.1 has the why, the value and the check); read the bus tester's RN4 value first (pull-ups on the address lines: 10 kΩ is fine, 1 kΩ is not). v2.0: done - R15 (470 Ω, beside the bus connector, pin 1 on the ADDR15 track; hardware/cards/memory/kicad/v2.0 README). Then, optional and separate: drop the -VMA-in-every-step hack from the microcode generator (a reload) so the chip selects are qualified by -VMA again.
  • Ports P2-P7 are probably free for another card (noted 2026-09-24). The I/O card's 74LS138 (IC5, strapped to P0-P7) decodes all eight, but only P0 (control) and P1 (data) are used, each picked by a jumper on the IO-ADDR / DATA-ADDR headers; -IO-SEL2..7 go only to those headers and drive nothing. So a card decoding P2-P7 itself should not clash, as long as neither header's jumper selects that port. To verify on the board before relying on it: nothing else on the I/O card loads or drives -IO-SEL2..7, and the fitted jumpers are on P0/P1 (docs/cards/io.md 3.1).
  • Blank V3.2 (hardware/bus/blank-card/eagle/v3.2): derived 2026-09-20 from V3.1 with the Bus V3.2 names on C3–C6. Open it in Eagle/Fusion, re-save, use as the template for every new card.
  • Bus Tester V3.1 (hardware/cards/bus-tester/eagle/v3.1, 2020-07): latches drive the bus, soft bus-enable/reset, bypass caps; routed, CAM run, never ordered. Decide: build it, or keep the 2016 v1.1 for good.
  • Video card v1.1 (hardware/cards/video/kicad/v1.1, the KiCad master since 2026-09-21; Fusion abandoned): DONE in the design — one 5 V rail (+5V folded into VCC, joining track added; proof 116/116). TO DO — move the 6845 RS from A0 to A1 (hardware/cards/video/docs/fix-6845-register-select.md; bench job first), pull-ups on the 7416 outputs; then order. On the built v1.0: fit the 6845 with the RS bench fix, then the ROM's VR0C 12 / VR0C read-back proves the register path (docs/cards/video.md section 8, step 6).
  • Sequencer logic v2.2: a "CPU off" switch (hardware/cards/sequencer-logic). Today the card's outputs can never be silenced while the memory card is enabled: -BUS-EN is generated on the card itself (IC36 74LS04 from the sequencer's READY line) and is also the output enable of all nine pipeline 74LS374s and the two microcode-address 74LS244s; -RESET only clears the instruction register and step counter, so in reset the pipeline still drives every control line (word instruction 0 step 0: -VMA asserted, R0 selected as address source, the rest driven inactive). The bus tester therefore cannot load RAM with the logic card fitted (found 2026-09-21; every switch on the card is in use and the -BUS-EN copper cannot be split with one cut). Change: (1) route the 374/244 output enables through a new switch or jumper, RUN = follow -BUS-EN, OFF = pulled high - AND the 17 lines the 2026-09-21 review found driven regardless of -BUS-EN (IC4/IC5/IC18 select buffers, -REG-LD-LO/HI, -RESET, OUT); (2) make the READY-to-BUS-EN driver open-collector (or jumperable) so the bus tester can own -BUS-EN without shorting the LS04. With that, the tester loads RAM with everything plugged in. Until then: ROM monitor over the UART, or unplug the logic card.
  • IO V1.2 ideas (IO notes): directional data-bus buffer driven by -IO-RD; 74138 IC5 pin 5 tied to -BUS-EN.
  • Index Registers: notes ask whether bus direction should follow -RD-SEL (the same question as the IO buffer).
  • Memory v1.3 notes: "should TMP registers move to the ALU", hard-jumper a boot-loader enable, 4K-block EEPROM select.
  • Sequencer logic notes: expose ucode-count-reset / instruction number for an external debugger; HALT LED.
  • Memory card: the unconnected jumper wire on IC7 pin 4 — purpose not remembered (2026-09-20); trace it on the board or remove it.

Firmware — written, not burned / loaded

  • ROM 2026-09-25: the video unit (not burned) — firmware/rom/shipped/rom.bin (MD5 3ebc6789...): the $D000 probe at reset, the screen driver, CHAROUT mirroring (VIDMIR $0FF1, off at reset), the V command, the video entry at $FFBC (docs/programming/MONITOR.md section 11). Burn it (Visual Minipro, 28C64) when ready, then check the banner says ROM 2026-09-25 and VIDEO CARD FOUND, and run the bring-up table in docs/cards/video.md section 8. The old chip stays compatible (Y1/OS's video reports "no video driver").
  • Video auto-start: VIDAUTO EQU 1 in firmware/monitor/monitor.asm once the card is debugged (6845 fitted with the RS-to-A1 fix, E one-shot and 7416 pull-ups sorted, a picture from V I): reset then programs the CRTC, clears the screen and turns mirroring on. Rebuild, tools/verify_firmware.py, burn.
  • Video: settle the CRTC timing — the ROM uses $D800/$D802 (confirmed 2026-09-25: RAM $D000-$D7FF, 6845 $D800-$DFFF) and a CRTC table assuming a 10 MHz dot clock, 5-dot characters, 80 x 24 (vcrtab); read the crystal, confirm with tests/video/hold_address.py, adjust with the monitor's VR by hand, then in the table. The character EPROM's order is assumed 2513-style (ASCII bits 0-5); read the 2732 to confirm.
  • Sequencer microcode: five changes waiting for one EEPROM (firmware/microcode/ucode-generator2/test.hex; cache = what the EEPROM holds, the 2026-09-23 image): (a) LDZ/STZ/ADDIW/SHL16 (2026-09-24, 32 new records $80-$8F, $C0-$CF), (b) the undefined opcodes $A5, $AE, $F8-$FA get HALT's record (2026-09-29, H-4), (c) the three-step fetch prologue (2026-09-29, docs/system/MICROCODE.md 5.6: every record changes, instructions 2-3 steps shorter), (d) M-1: every step that counts a register is written without -MEM-RD (2026-09-29, the same section; 116 steps, one bit each), (e) 152 idle steps removed (2026-09-29, 5.7 there). Load in two stages so a failure points at its change:
  • make -C firmware/microcode/ucode-generator2 prologue6, then python3 tools/ucode_send.py --all --hex firmware/microcode/ucode-generator2/build/p6/test.hex (at the machine: press START), reset, python3 tests/bench/run.py --port /dev/cu.usbserial-AB0MVHSQ: (a) + (b) with the old prologue. isa checks the four instructions byte by byte (the machine must print exactly expected/isa.uc.out), xisa is compiled C using them (y1cc --xisa: the page register R6, a recursive frame in the page, ADDIW, SHL16, R6 reloaded after the ROM's charout).
  • python3 tools/ucode_send.py --all (the tree's image, + (c), (d) and (e)), START, reset, the bench again: every test must print the same; then the monitor, BASIC, Y1/OS from CF and a kermit transfer (its timeouts were recalibrated for (c): os/kermit_io.asm PPS 6528 - with the old microcode they are 25% long, harmless). If stage 2 fails where stage 1 passed, stage 1's image is the way back. When xisa passes, make --xisa y1cc's default (below, "C compiler").
  • Monitor + BASIC 8afde21 (2021-09, firmware/*/candidates/2021-09-8afde21): charavail BIOS vector ($FFEC), BASIC ON/OFF statements, break into a running program. Needs a hardware test, then burn and update rom/shipped.
  • monnew-2025 (firmware/monitor/monnew-2025): small D/M/B monitor; assembled, never run on the machine.
  • firmware/abi/ — the BIOS/port/variable map still has to be written from the two .asm headers.

Design review 2026-09-21 (reports: hardware/DESIGN-REVIEW.md, hardware/DESIGN-REVIEW-NOTES-datapath.md,

hardware/DESIGN-REVIEW-NOTES-control-io.md, docs/isa/MICROCODE-REVIEW.md, docs/isa/MICROCODE-REVIEW-NOTES.md) Items below were traced to nets/pins or to test.hex and spot-checked; the reports give the evidence and a bench check each. - (done 2026-09-29 in the tree, NOT YET LOADED: HIGH, microcode: undefined opcodes were all-zero words ($80-$8F, $A5, $AD, $AE, $C0-$CF, $F8-$FA; BRUR and the four xisa instructions filled all but five). An all-zero word asserts every active-low line (-MEM-RD and -MEM-WR together, every register strobe) for 61 steps. The generator now gives $A5, $AE, $F8-$FA the HALT record (docs/system/MICROCODE.md 5.4, tests/ucemu/undefined.py); it reaches the EEPROM with the next tools/ucode_send.py --all, the same reload as the xisa instructions.) - HIGH, microcode: PUSHR writes both stack bytes while the register card and TMP1 both drive the data bus (read strobes never cleared); BRZ/BRNZ/BR16Z/BR16NZ keep -AC-RD on while -BRANCH-RD loads the PC; BR16Z/NZ cannot work (BDATA8-15 are pull-ups under -AC-RD). None exercised by ledcount; bench order in the notes: IC11 scope check, then BRZ, then PUSHR. - HIGH, logic card: 17 bus lines driven regardless of -BUS-EN (REG-RD-ID/REG-LD-ID/ADDR-REG-ID via IC4/IC5/IC18 enabled by the operand-select pipeline bits; -REG-LD-LO/HI, -RESET, OUT from plain gate outputs). The v2.2 "CPU off" switch must cover these too, and bus-driver on the tester fights them whenever the logic card is fitted. - HIGH, BOM: step counters and all 16 register counters are 74LS192 (BCD) in schematic/board/BOM (sequencer-logic IC33/34, register x16); the machine runs 32-step binary microcode, so 74LS193 must be fitted. (2026-09-23: the board photos in media/ read SN74HC193N at IC33/IC34 and on the register counters, and 74HC parts through much of the sequencer, ALU and register cards, so the machine is right and the DESIGN FILES are wrong; docs/cards/sequencer-logic.md / register.md. Still to do: confirm the markings on the second register card, then fix schematic/BOM.) - HIGH (masked), memory v1.3: FORCE-ROM race - IC12 (74LS74) is clocked by ADDR15·-VMA·-BUS-EN while the register card puts the address up ~40 ns after -VMA and the address bus floats high between cycles; masked since 2020 by asserting -VMA in EVERY microcode step (main.c:103,115 "Hack prevent ROM mapping from triggering"), which removes -VMA from all memory chip selects. - HIGH (untested), video v1.1: 6845 E clock derived from C1/R1 discharged by a 7416 open-collector output with no pull-up; unreliable at run speed. Goes with the RS-to-A1 change and the 7416 pull-ups before a CRTC is fitted. - MED: 28C64 -WE is raw -MEM-WR (any store during FORCE-ROM writes the EEPROM; the monitor is safe only because its first instruction jumps above $8000; memory v2.0 has the write-protect jumper JP3, 2026-09-29); reset does not reload the pipeline (stale word on the bus during reset, with FORCE-ROM active); no power-on reset anywhere (FORCE-ROM, counters, carry undefined until the button); register card IC34 is a CD4077 driven by LS levels; count strobe = OR(-Rx-RDSEL, -REG-UP) counts a deselected register on a REG-RD-ID change; the register card drives $FFFF onto DATA0-15 on every increment step, overlapping -MEM-RD (327 steps); one-step memory windows before leading-edge latches (LDTI, LDIVR, LDT, BRANCH-LD, INT-LD) are the first to fail at a faster clock; JP2 on the logic card would hold the sequencer card in reset; video character clock is a ~50 ns runt; TMP registers latch on the leading edge (microcode must present the source a step early - it does). - LOW / speed: (done 2026-09-29 in the generator, NOT YET LOADED: the fetch prologue is 3 steps, docs/system/MICROCODE.md 5.6; compiled code ~18% fewer clocks on the emulator.) (Also done, the same day: M-1 in the 116 operand-fetch increment steps, section 5.6 there, and 152 of the 259 idle steps, section 5.7, ~3% more.) Still open: hold steps that are not idle (5.7 "Not done"), -IO-ADDR-LD (L-2); the rest of the ~30% tabulated per opcode in the microcode notes; emulator mismatches listed there (BRVR, JSRUR byte order, carry on SUB/shifts, R0-load suppression). - Timing-diagram model (tools/ucode_wavedrom.py) corrections from the review: IR/operand/branch/TMP/ACC latch on the LEADING edge of their strobe; one step = two clocks; steps 0-2 run with the previous opcode in the IR; -REG-RD-LO/HI are byte lanes. To fold into the generator when the diagrams are next regenerated.

Software

  • BASIC OUTP port,value / INP port,var (open since 2026-09-23): both keywords tokenise, but exe_outp_stmt and exe_inp_stmt in firmware/basic/basic.asm only eat the keyword - no port access, so BASIC cannot drive the LEDs or read the switches (POKE cannot: they are I/O ports, P0 select + P1 data). The port is in the opcode (OUTA Pn $60+n, INP Pn $90+n), so a run-time port needs OUTA/INP Pn + RET built in RAM and called, or a 16-entry jump table. INP like PEEK addr,var (no functions in expressions). ~230 bytes free in the BASIC half of the ROM. Test on both emulators (ucemu -L shows LED writes, -i/-I the switches), patched_files entry for basic.asm, then burn - ideally together with the next ROM burn. Example: OUTP 0,1 (select switches/LEDs), OUTP 1,170 (LEDs = $AA), INP 1,S.
  • Every C tool now has a plain Makefile (2026-09-20); the NetBeans projects are kept but no longer needed to build. The three tools still carry the old tree's relative include paths, satisfied by tools/layout_links.py symlinks; fixing the includes would let the links go.
  • Test-vector generator uses the 2016 signal names (tests/bus-tester-scripts/Gen Test Vectors/): it emits the 2016 register-card codes (REG-FUNC-LD, REG-BRD-LD-ID, WDATA/RDATAL) which are not in the 2020 bus table, where the card select became REG-LD-ID2..3 / ADDR-REG-ID0..3; the fix converter only maps BUS-WR. Rewrite it against the current signal table before generating vectors for the 2020 Index Register cards.
  • Replace the Processing command sender with a Python host (2026-09-20). embedded/command-sender/command_sender_8 builds again under Processing 4.5.6 (2026-09-20 fixes) but is a dead end. tools/busdrv.py already speaks the bus tester's CMD:OPERAND# / >> protocol; still needed is the script interpreter from the sketch: // comments, :label + GOTO, LET, FOR/NEXT, DUMP start end, WAIT, DUMPVARS/DUMPLABELS, and CMD:OP#EXPECTED!VAR return-value matching and capture (hex in the scripts, decimal on the wire). Retire the sketch to deprecated/ once the Python version runs the scripts in tests/bus-tester-scripts/ on the bench.
  • (done 2026-09-21: embedded/sequencer-card/sequencer4 reads the copy back before READY and refuses on a mismatch; copy 16 s
  • verify 29 s instead of 156 s; sequencer3 deprecated)
  • (done 2026-09-23: the monitor's : Intel-hex loader and tools/monload.py — records answered ./?/!, $1000-$DFFF only, read-back verify, ESC abandons; host tool paces 3 ms/char and waits per record, --go, --listen, --term; tested on both emulators and over a pty (tests/monload/); burned 2026-09-23 and the monitor boots on the machine. The E-command scheme planned here was dropped.)
  • Port of the P8X work (OS, monitor, BASIC, compilers) onto YACC1 — the reason this repo exists; not started.

Verification still to do

  • (done 2026-09-21: both ATmegas now run tree builds, verified by reading the flash back; the previous binaries are in embedded/*/readback/)
  • (done 2026-09-20: tools/verify_embedded.py, 12 sketches compile against the vendored libraries)
  • (done 2026-09-24: the tree's eagle/v1.3 board turned out NOT to be the ordered one (now eagle/deprecated/v1.3-do-not-use/); the Fusion 360 export of the built card is hardware/cards/memory/eagle/v1.3/ (filed first as v1.3-fusion-export-2026-09-24), proven hole for hole and track for track against the 2025 gerbers (JLCPCB order 2000765A) by tools/verify_fab_vs_brd.py; it adds IC15, the 74245 enable from the chip selects.)
  • (answered 2026-09-20: jumper boards not fitted/obsolete, EEPROM adaptor fitted, two register cards, RN2 = 1k; bus tester firmware settled 2026-09-21 by reading the flash out; sequencer likewise = Sequencer3) — no (confirm) marks left.

Tree / docs

  • (done 2026-09-20: every .rtf outside archive has a Markdown twin; tools/rtf_to_md.py)
  • Theory-of-operation write-ups per card (docs/cards/), architecture / memory map / microcode format (docs/system/).
  • KiCad conversion of the remaining cards with the memory-card toolchain (tools/kicad/); Eagle then frozen.
  • git init (no LFS), first commit, GitHub repo; decide whether archive/ (190 MB) is committed or kept as a separate repo.
  • Move off NetBeans (README decision 7).

Disk operating system (plan docs/system/OS-PLAN.md, decisions 2026-09-22)

  • (done 2026-09-22 evening: phase 1 — CF model in both emulators, ROM driver + O boot + vectors, p8xfs/img2bin; phase 2 read-only — os/y1os.c shell with dir/cd/pwd/cat/load/run and /BIN programs, tests/os/ sessions on both emulators.)
  • (designed 2026-09-23: the CF card v1.0 — hardware/cards/cf/kicad/v1.0/ generated from cf_netlist.py: 5 ICs (74LS138/32/175/08/245), 40-pin IDE header for a CF-to-IDE adapter, schematic and board both proven equal to the netlist, DRC 0 errors / 0 unconnected, gerbers ready; theory in docs/cards/cf.md; never ordered. Card-preparation procedure written: docs/procedures/CF-CARD.md. A move of the interface onto an I/O card v2.0 on ports P4/P5 was designed 2026-09-23 and dropped 2026-09-24 (6eeb259, 65851b0).) 2026-09-25: memory card v2.0 FINISHED FOR FABRICATION, NOT ORDERED (hardware/cards/memory/kicad/v2.0, README "The v2.0 board"): standoff option E (chosen 2026-09-25) - the CF-to-IDE adapter (HX-2118P: 60 x 44 mm, 2 M3 holes 52 mm apart at the header end, no pin 20) on two 15 mm M3 standoffs over the CF chips at the free top edge, J2 parallel to X1 with a short straight ribbon, the ROM uncovered in the top row of the memory column. 2026-09-29: + JP3, the ROM write-protect jumper (design review M2; 1-2 WRITE, 2-3 PROTECT), added locally beside the ROM, the rest of the route unchanged. 2026-09-30: + R15, the 470 Ω ADDR15 pull-down (design review M1, the FORCE-ROM race), added locally beside the bus connector (pad 1 on the ADDR15 track, which was split there; no via). The 1:1 print changed again (JP3, then R15): reprint it before ordering. memory-v2.0.kicad_pcb: 0 unrouted, 72 through vias, DRC 0 copper violations / 0 unconnected, planes one piece each, netlist proof MATCH, silkscreen tidied (adapter outline, standoff holes H1/H2, "CF CARD INSERTS HERE", J3 / JP2 / LED labels); gerbers + NPTH/PTH drill zip, renders, placement PDF, BOM (+ standoffs, screws, washers, ribbon), JLCPCB order note (as order 2000765A), 1:1 print. Open before ordering: the ribbon plug's pin 20 (open, or pull J2's pin 20). Resolved 2026-09-26: the adapter's power pads are a floppy pinout in J3's order, so the J3 cable is straight through (a harness built by hand). Resolved 2026-09-25: the card-cage slot in front of the memory card stays empty, so the ~35-44 mm stack does not matter. Before that (2026-09-24 evening): five standoff placements A-E trial-routed with 1:1 check prints (records in the folder). Earlier the same day: memory card v2.0 ROUTED, fab files ready, NOT ORDERED: hardware/cards/memory/kicad/v2.0 (built memory card + CF on P8/P9, schematic proven) did not fit with the built card's copper kept, so the whole card was laid out again (same circuit, outline, X1, 4-layer GND/VCC planes; 0.25 mm tracks / 0.2 mm clearance / 0.8-0.4 mm vias). Option B was chosen (TMP registers at the bus connector, CF column at the top edge, J2 centred); memory-v2.0.kicad_pcb is routed (0 unrouted, 43 through vias, DRC 0 copper violations), silkscreen tidied, gerbers
  • drill zip, renders, placement PDF, BOM and the JLCPCB order note (4 layers, 1.6 mm, as order 2000765A) are in the folder. Before ordering, the open items in its README: J2 pin-1/key vs the chosen adapter, J3 pinout vs the adapter's power cable, socket heights under a TAODAN overhang, which backplane slot (TAODAN needs ~75 mm free). C20-C23 (four spare 100 nF with no IC): removed (2026-09-24), board and fab files re-made. Earlier plan: design the CF interface onto the memory card (more room, chips spaced far apart), kept I/O-mapped on P8/P9 with the CF card v1.0 circuit (own 74LS138 enabled by IO-ADDR3, 74LS32, 74LS175, 74LS08, 74LS245, 40-pin IDE header); the memory card's IO-ADDR0-3, -IO-RD and -IO-WR pins are on its connector but unwired today (docs/cards/memory.md). The CF-to-IDE adapter is undecided: a SinLoon CF-IDE on a ribbon/standoffs, or the TAODAN CF-IDE40 V2.0 plugged straight onto a male header (docs/cards/cf.md section 0). The ROM in the machine (ROM 2026-09-23) already drives P8/P9, so no burn is needed. After building: the bring-up steps in docs/cards/cf.md section 7 (empty adapter = CF ERROR, the P8 latch on the IDE header's DA pins, then O with a card prepared by tools/cfcard.py from os/disk.img).
  • (done 2026-09-23: write support — save/del/ren/mkdir/rmdir in the shell, files written at the free pointer and registered as p8xfs.py does, verified from the host in tests/os/run.py (fsck, ls, get); the file API — a 22-entry syscall table at $0F14 (SYSARG/SYSRES at $0F06..), y1cc's sys()/funcaddr() builtins, os/lib_fs.c wrappers (fopen/fread/fgetc/fclose/fcreate/fwrite/fputc/fdelete/fmkdir/frmdir/opendir/readdir/fresolve/fentry/ getcwd/chdir/frename/conin/constat), four handles with their own buffers; CONIN through a new ROM vector UARTINNE $FFFC (no echo; ROM rebuilt, still unburned); ARGBUF 128 bytes; first commands on the API: CAT2 (retired the same day for the ported cat), WC, LS, CP; OS image 12,183 bytes = 24 sectors; os/README.md.)
  • (done 2026-09-23: PACK — /BIN/PACK (os/commands/pack.c, 5,397 bytes + 9.3K tables; a program, the OS image did not grow): iterative tree walk into a record table, sort by start LBA, layout check, each extent moved down sector by sector, entries rewritten where their directory is now, '.'/'..' fixed; refused under </>/>>/|; keeps the current directory (GETCWD/CHDIR); pack -v shows each move and its steps. Reset-safe (second version the same day): an extent whose hole is smaller than itself moves in two steps through a scratch area above the free pointer (raised over it before the first move), so every entry always points at a complete copy; tests/os/run.py --cuts 60 cuts pack off at 120 points in two fragmented volumes, every phase hit, and after each cut fsck passes, every file is byte-identical and a rerun completes (120/120; a one-step-only build fails 16 of 40). The OS re-reads the free pointer after every program (read_free()), zeroes its state through main's BSS clear only, save closes its handle after a failed write (a leaked write handle was the one open file STDIO could not show), and take_entry() counts sectors without the 16-bit wrap of (e_len + 511) / 512 (65,025..65,535-byte files were 1 sector), and load checks the size against the program area without the wrap of e_load + e_secs * 512 (a 64K file passed the check and overwrote all of memory). p8xfs.py fsck checks '.'. tests/os/pack.session with host checks (no dead sector, every pristine file byte-identical, the next file at the new free pointer); os/man/pack. OS image 14,619 bytes, image + data 16,043.)
  • PACK reset window (found 2026-09-23 by run.py --cuts 60, 1 of 120): in a two-step move of a DIRECTORY, a cut after the copy down but before the entry's final rewrite (631>630 via 687: ceC) leaves a child's .. (/PK/SUB/..) pointing at the scratch copy (687) while its parent's entry already says 630: fsck fails until the next pack's repair pass fixes it (no file is lost; the rerun passes). The same cut fails identically with the C OS and the assembly OS (pack does its own raw sector I/O); earlier runs passed only because no cut point landed in that ~20,000-instruction window, and the man-page/README edits of the same day moved the layout. Either rewrite the '..' of the subdirectories before the parent's entry moves off the scratch copy, or let fsck accept a '..' that points at an identical copy; os/README.md's "a '..' that lags one step" says the second is the design.
  • PACK, what is left: the "no room on the card" refusal for the scratch area rests on a real card rejecting an LBA past its end (the emulators' CF model reads zeros there and grows the image), so try it on the CF card once it exists; a nearly full card cannot pack a big file that sits behind a small hole (the scratch area needs its size above the free pointer). Tombstones are not squeezed out of directories (the OS reuses them, so nothing is lost). 800 files and directories at most; every sector past the first hole is copied, one at a time, two-step moves twice.
  • Y1/OS still to write: FORMAT and FSCK on the target, seek, more than one write handle (needs allocation away from the single free pointer; it would let cp/touch/save/mkdir/vi :w work inside a > or a pipe).
  • (done 2026-09-23: the P8X commands, waves 0-2 of os/PORT-PLAN.md — shared libs os/lib_*.c (stdin, rdline, glob/globx with an iterative gmatch, regex with a backtrack stack, walk = a recursion-free tree walker on one directory handle, apath, more = the pager, num, err); /BIN pwd help dep dump examine man cat wc head tail more sort uniq sed awk cmp diff md touch del mv tree find dir grep cp (cat2 retired); man pages in os/man/ -> /MAN, Markdown docs -> /DOCS, sample data /FRUIT.TXT /FRUIT2.TXT; the shell runs /BIN/NAME before a built-in of the same name; tests/os/wave1.session, wave2.session with host-side p8xfs checks. Status per command in PORT-PLAN section 2.)
  • Wave 3 of the port: (done 2026-09-25: asm, the on-target assembler for the RC/asm dialect, table generated from yacc1.def - os/README.md "asm", tests/asm), (done 2026-09-29: disasm, os/commands/disasm.c, 6,107 bytes: disasm [-s] FILE [START [COUNT]] / -m ADDR [COUNT], lines in RC/asm's dialect that reassemble byte for byte, unknown bytes as DB; its table os/dis_optab.c generated from yacc1.def by tools/gen_y1_distab.py, which runs the assembler generator's Translate() backwards and checks every opcode against software/opcodes.h; tests/disasm/run.py round-trips 382 programs/sources/ROM/synthetic files through RC/asm, tests/os/disasm.session reassembles its output with /BIN/ASM under Y1/OS - os/README.md "disasm"); vi done 2026-09-23. Then BASIC as /BIN/BASIC.
  • disasm follow-ups (2026-09-29, none needed for it to work): (1) labels - a second pass that names every branch/JSR target inside the range (L5003:) and prints BR L5003, so -s output reads like source and can be edited and moved (today it reassembles only at the same address); (2) not yet run on the machine (no CF card yet): nothing in it is machine-specific beyond peek and the syscalls every command uses; (3) seen in passing: tests/compiler/passes.py lists os/commands/help.c (STRPOOL: its strings), os/commands/kermit.c and tests/os/rdn.c (NODES_MAX) as not fitting the native passes' tables, besides y1cc.c - help.c already before disasm's line was added; the docs name only y1cc.c, and none of these is built natively today.
  • /BIN/ASM follow-ups (2026-09-25): (1) speed - (done 2026-09-26: /BIN/ASM in YACC1 assembly, os/commands-asm/asm.asm, identical to asm.c - now /BIN/ASMC, the specification - on the whole tests/asm corpus and in the self-host's fixed point; 4.3-5.3x fewer instructions, 5.2x in clocks: cat 12.8M -> 2.75M, cc8's 235K 129.7M -> 26.4M; a native build's assembling 217M -> 46.5M, 9% of it; the self-host's stage 21 h 17 min -> 15 h 47 min at 1 MHz; the label table 17,088 -> 20,292 bytes; docs/programming/ASSEMBLER.md section 10). Left in asm.asm, a few percent each (tests/native/profile.py): the label hash over the whole name (the last four characters and the length chain as well), 512 chains (-1.7 records a lookup, 512 bytes of table), hashing the mnemonic in the getln loop, unrolling gl_lp; the ROM's sector reads are 12% and fixed. Keep asm.c and asm.asm in step: a change of behaviour goes into both, tests/asm/run.py compares them; (2) sources over 64K - (done 2026-09-25: 24-bit positions); (3) what RC/asm accepts and asm refuses (MACRO, PUBLIC/EXTERN/LIB, '/' beyond 16 bits, EQU values beyond 16 bits) - nothing in the tree uses them; (4) asm inside a > or a pipe needs the OS's second write handle ("Y1/OS still to write", above).
  • RC/asm (host) aborts on a line whose text after the label or the leading blanks, up to the comment, is 100 characters or more (trim()/parse() copy it into char tmp[100], and macOS's fortified strcpy traps; found 2026-09-25 writing tests/asm/src/quirks.asm); y1cc's lines are shorter, and /BIN/ASM takes 254. A tools/patched_files.txt fix to the host, with make -C software/assembler check and tests/asm after it. The assembly OS has 18 sectors of headroom in the 16K reserve (LBA 1..32) and 7,711 bytes free below its RAM (2026-09-23, v0.2).
  • (done 2026-09-23: redirection and pipes — cmd [< in] [> out | >> out] [| cmd ...], up to 4 commands, clauses after the arguments, quotes protect | < >; y1cc --os makes putchar/puts the new syscall CONOUT (19) and getchar CONIN, KEYIN (20) is always the keyboard (pager, vi, dump, examine), STDIO (21) says what is redirected (the pager stops paging into a file); pipes run stage by stage through /PIPE0.TMP and /PIPE1.TMP, deleted after the line; >> appends in place when the file is the last one written, else copy-then-extend; CREATE now replaces a same-named file at CLOSE (the new entry over the old slot); eputs() and the shell's errors go to the raw console; the four handle buffers moved to $0400-$0BFF to fit (OS image 14,673 bytes = 29 sectors, image + data 16,097 of 16K); tests/os/redirect.session, pipe.session with host checks; os/man/shell.)
  • Redirection and pipes, what is left: no 2> (errors always go to the screen); stages run one after the other, not concurrently; a redirect clause must follow the arguments (echo > F hi is a syntax error); a write that fails part-way (disk full, 64K) drops bytes silently. (The space problem is gone with the assembly OS: 7,711 bytes free, below.)
  • (done 2026-09-23: the OS in assembly, v0.2 — os/y1os.asm (hand-written, 3,415 lines) replaces the C OS as the default: 7,137 bytes = 14 sectors instead of 14,619 = 29; its RAM at fixed aligned addresses $4A00-$4F0F, so $2BE1-$49FF (7,711 bytes) is free; messages in os/strings.txt -> numeric DB lines by os/mkstrings.py. Same behaviour and ABI: every tests/os session passes on both emulators with either OS (run.py compares the banner without its version; transcripts changed only in the banner), --cuts 60 120/120 (119/120 after the day's doc edits moved the disk layout: the PACK reset window below, identical with the C OS), and a C-vs-asm differential run (same disk and keystrokes, transcript and every data sector compared) agreed on the sessions plus the built-ins /BIN hides, load limits, redirect/pipe edge cases and a syscall torture program. Instructions to exit, C -> asm (interpreter; ucemu steps the same ratio): basic 1.53M -> 0.99M, api 1.80M -> 1.11M, write 2.84M -> 1.95M, wave1 5.04M -> 3.20M, wave2 5.68M -> 3.63M, redirect 1.88M -> 1.25M, pipe 9.57M -> 4.91M, vi 1.11M -> 0.84M, pack 29.97M -> 15.78M (1.3-1.9x). make -C os OS=c builds the C OS, still the specification.)
  • (done 2026-09-25: SYSTAB 22 -> 32 entries — the OS fills SYSTAB2 at $4FC0-$4FFF (32 entries) and copies its first 22 to SYSTAB at $0F14, so every program compiled before (and tests/compiler/syscall.c, the bench image syscall.img) keeps working unchanged; y1cc's sys() reads SYSTAB for a constant 0..21 (output unchanged) and SYSTAB2 for 22..31; a computed number still indexes SYSTAB (0..21). os/README.md "Two tables", tests/compiler/syscall2.c, sysbig.c, tests/os/systab.session. Left: a computed number over 21.)
  • Y1/OS behaviours kept by the assembly rewrite (y1os.c's, the transcripts are the contract; fix both together): CLOSE of a directory handle returns 3 (its mode), of a read handle 1, not "1" as documented. A trailing slash after a file name resolves (fopen("README.TXT/")). CHDIR with a component over 12 characters matches the entry named by its first 12 and puts the whole typed name in the path (cd /D1/AAAAAAAAAAAAXYZ). DELETE returns 1 when the tombstone write fails. ren A B C names the file "B C".
  • (done 2026-09-23, both OSes: PUTC/WRITE through anything but the open write handle refused — handle 0 with no write open used to write $0200 and then LBA 0, the boot block; load/run of an empty file refused with "bad load address or size"; tests/os/badhandle.session + badh.c, host checks fsck, boot block, pristine files. The asm image is 7,151 bytes.)
  • (done 2026-09-27: the "READN gives stale data" report (from the /BIN/ASM work, 2026-09-26) - not READN: the READN trial of asm.asm (READN(fh, SDATA, 512) in gs_rd) kept the skip it needs after a SEEK with READ (GL_SKIP: READ starts at the sector's start, READN at the position), so after every INCLUDE it lost the outer file's next bytes (tests/asm QUIRKS.ASM, first run or not); READ is what asm.asm uses. The hunt found a real bug next to it, in y1os.asm only: since 6a42b9f (2026-09-25, 24-bit positions) fs_getc's sector-boundary compare read the handle's start LBA instead of its buffered sector (a missing ANDI 0F0H), so GETC and READN re-read a sector at nearly every boundary and returned the buffer's stale bytes for the sector whose number equals the file's start LBA - only possible for a file longer than its start LBA in sectors, which no disk made from os/disk.img can hold (the free pointer is past 1,100 on 2,048 sectors), so no test or native compile ever met it. Fixed; READN's exact contract documented (os/README.md, docs/programming/OS.md, man fs); tests/os/rdn.session (rdn.c: every READN count and byte against the contract in the mixed patterns) and rdnlow.session (a fresh volume, a 40K file at LBA 37: failed at byte 18,944 before the fix), both kernels, both emulators.)
  • software/emulator (instruction level) treats a lower-case q on the console as end of input (mygetchar(), an old quit key): a command line containing q (uniq, sed s/q/x/) is cut there. Sessions use UNIQ and Q until it is fixed; the microcode emulator has no such quirk.
  • Y1/OS details found while writing the man pages (2026-09-23, not fixed): path_push stops extending the textual current path past 62 characters while cd still descends, so the prompt/GETCWD (and every command's abspath) point at the wrong directory that deep; the built-in rmdir says "not an empty directory" for every failure. (The load_file whole-sector overrun past $CFFF was fixed the same day: the check now uses the sector count.)
  • vi redraws the whole current line on every keystroke in insert mode (<ESC>[r;1H + line + <ESC>[K): fine on the emulators, ~80 bytes per key at 9600 baud on the machine; redraw only from the cursor, or just echo the character when appending at the end of a line.
  • Phase 4: video card v2 (6845 on ports PA/PB, 2K RAM) + PS/2 keyboard behind the console vectors.
  • kermit (/BIN/KERMIT, 2026-09-26; os/README.md "kermit", docs/procedures/KERMIT.md) - done on both emulators against tools/y1kermit.py (tests/kermit), open:
  • On the machine with C-Kermit: not run yet. To verify there: that the XR16C550's FIFO works as kermit uses it (FCR = 7 at the start, 0 at the end; if it does not, packets over ~13 characters overrun at 38400: try kermit -r -l 20, then a lower line speed), the timeouts at the real clock (poll counts calibrated for 1 MHz), C-Kermit 9.0.302 with tools/y1.ksc in all three modes, and autodownload.
  • Not implemented (optional in the protocol; each would cost code in a program already at 27K of the 32K area): long packets (E-Kermit's F_LP: up to 4096 characters a packet, fewer ACK round trips; with the six-step fetch prologue a receive burst that long would overrun the 16-byte FIFO at 279 clocks a character against the line's 260; with the three-step one of 2026-09-29 the loop takes 217 and keeps up, so receiving becomes possible once the microcode is reloaded - the packet buffer is the remaining limit), sliding windows (F_SSW; the same), streaming, locking shifts, RESEND/recovery of an interrupted transfer, file dates in the attribute packet (Y1/OS has no dates), and the server's REMOTE commands (DIR, CD, DELETE, TYPE, SPACE: each a G subcommand; now "Unimplemented server command").
  • A timer would make the timeouts real seconds at any clock (today a count of UART polls).
  • The instruction-level emulator's port 2 still turns q into end of input (item above); the UART model added for kermit does not, so the two console paths of that emulator now differ in that one byte.
  • (done 2026-09-22: yacc1.def P8=9 typo -> P8=8.)

C compiler (y1cc, 2026-09-22)

software/compiler/y1cc.py compiles p8cc's C subset to YACC1 assembly (static frames, R3 accumulator, see its README); 12 test programs pass on the emulator (make cc-test), 9 of them checked against the host C compiler as an oracle. - (done 2026-09-24: software/compiler/c/y1cc.c, the C twin — 3,122 lines in the C89 + y1cc subset, the same assembly as y1cc.py on the whole corpus including itself, tests/compiler/twin.py in make check; compiled by y1cc.py it is an 82,345-byte image, 2.5x the 32K program area.) The road to a native compiler, in order: 1. (done 2026-09-24: the multi-pass compiler — y1cc.c split into nine programs software/compiler/c/cc1_lex.c .. cc9_final.c (lexer; parser; declarations; call graph; labels and frames; statements; expression analysis; expression code; the text), chained by y1ccp on the Mac: the same assembly as y1cc.py on the whole corpus (twin.py --chain, --chain16: 126 programs + 4 errors, the passes compiling themselves among them) and on random programs (twinfuzz.py --chain, seeds 1-6 x 500, the 60 error programs and 20 with two errors found in different passes, where the first in y1cc.py's order must win). Every pass compiled by y1cc.py and assembled fits $5000-$CFFF with its Y1/OS tables and its measured stack, the tightest cc9 with 244 bytes to spare; with those tables the chain compiles the whole corpus but y1cc.c (tests/compiler/passes.py, in make check). software/compiler/README.md, "The multi-pass compiler".) 2. (done 2026-09-25: a stack for the native compiler - y1cc --stack ADDR (the chosen design) in y1cc.py, y1cc.c and the passes: main saves the caller's SP on its own stack at ADDR and puts it back at every return; the passes are built with --stack 0xCFFF (make -C os passes), and tests/native/run.py runs them under Y1/OS with the emulator's program watch (emulator -S): every pass stays above its data (measured 2026-09-24 in passes.py: 84-1,202 bytes on the corpus, cc2 about 144 more per level of parentheses, cc8 62 per level of operators).) 3. (done 2026-09-25: Y1/OS for the compiler - files over 64K (24-bit positions and lengths in both kernels, tests/os/big.session), SYSTAB2 (32 entries), the syscalls EXIT, EXEC and SEEK (tests/os/exec.session), /BIN/CC chaining the passes with EXEC, the lexer's #include stack over three real handles (c/target_inc.c), /LIB/Y1CCRT.TXT and /LIB/Y1LIB.C on the disk. The passes are built with --xisa since: with the chaining and the include stack cc1, cc6 and cc9 no longer fit 32K without it.) 4. (done 2026-09-25: the passes run under Y1/OS - one by one and chained by cc, on the emulators; tests/native/run.py: 27 programs compiled by cc, assembled by /BIN/ASM and run under Y1/OS, the tests, four /BIN commands and the compiler's own pass 4 among them, byte-identical to the host toolchain; about 32 us an instruction at 1 MHz, so hello 3.7 minutes, fib 18, cat.c an hour, pass 4 2.3 hours - software/compiler/ README.md "Native".) Follow-ups: - (done 2026-09-25: speed - the passes' I/O buffered through the new syscall READN and a WRITE that copies a sector's rest at once (both kernels, tests/os/big.session), and the hot spots tests/native/profile.py (the emulator's -P PC histogram) showed: cc9 made the code text in all three section readings, parsed each line three times, divided twice a digit; cc1's lookahead array, operator search and per-character hash; cc2's token calls; cc4's matrix multiplies. Compiles 2.7x faster (all 27 of tests/native: 1,269M -> 468M instructions), compile + assemble 2.2x (13.3 h -> 6.2 h at 1 MHz); cat.c 57 -> 24 minutes, pass 4 2.2 h -> 57 min. software/compiler/README.md "Native".) Left, largest first: /BIN/ASM is now 30% of compile + assemble (getln, a third of it, ~50 instructions a source byte; hash, asmcmd, same); cc9's text building (mn_arg, Ls, bcat: ~20% of a compile) - (the assembler: done 2026-09-26, asm.asm, 9% of compile + assemble now); the ROM loading each pass and main's BSS clear (~2M a compile, half of hello's time); cc2's parser; cc7's and cc8's records a byte at a time. The passes' room shrank for it: cc1 314 bytes free, cc9 463 (passes.py), and /BIN/ASM's symbol pool grew to 17,088 bytes (cc8's labels need 16,925; the assembler fills 32,755 of 32,768; asm.asm's table is 20,292 since 2026-09-26). - The passes need the 2026-09-24 microcode (--xisa) since the chaining and the lexer's include stack: without it cc1, cc6 and cc9 are 26-985 bytes over 32K. So the machine needs the EEPROM reload and tests/bench (the --xisa item below) before it can compile, besides the CF interface Y1/OS itself needs. - The work files CCW.* stay in the current directory (replaced by the next compile; pack reclaims the space); cc9 could delete them, at the cost of bytes in the tightest pass. - (done 2026-09-25: the full native self-host - tests/native/selfhost.py, make selfhost, in make check: under Y1/OS on the instruction-level emulator the host-built CC/CC1..CC9/ASM compile and assemble the nine passes, asm.c and cc.c from their sources on the disk, all eleven byte-identical to os/build; then the natively built tools, installed in their place, do it again, identical again: the fixed point. Nothing had to change (every table, stack and file held). 2,365M instructions a stage, about 21 hours at 1 MHz; the image grows to 5.7M (P8XFS: 32M at most). software/compiler/README.md "Self-host".) y1cc.c, the single-program twin, still does not fit the passes' tables (833 names, 130K of source) - it is not needed natively. - (done 2026-09-25: y1cc: a block comment that starts on a #define line and continues on the next was not skipped (the next line was lexed: $ in it was "bad character", found in os/lib_abi.c). Now a directive's /* */ comment is one space and may go on over newlines, a // ends the directive, neither inside quotes - y1cc.py, y1cc.c and cc1 alike; tests/compiler/dircomment.c, dircomline.c (the line numbers after it); os/lib_abi.c's EXIT/EXEC/SEEK comments are back beside their #defines.) 5. (done 2026-09-25: the on-target assembler /BIN/ASM, wave 3 of os/PORT-PLAN.md - byte-identical to the host assembler on the corpus, the nine passes' assembly among it; its 16,640-byte symbol pool holds cc8's 1,326 labels, the most of any pass. The host assembler's label table: done 2026-09-24, 8,191 labels with a clear error when full, was 1,000 with no check.) Left before a pass is assembled on the machine: its assembly is 110-257K and Y1/OS files stop at 64K (step 3), and the speed (about 9,000 instructions a line: BACKLOG "Disk operating system", the /BIN/ASM follow-ups). 6. Code size of what y1cc emits (the bullet below): every byte saved there shrinks the native compiler too — cc6, cc7 and cc9 are within 600 bytes of the 32K (cc6 since the --xisa page planning); the nine passes are 124K of code against y1cc.c's 75K, 102K when compiled with --xisa (2026-09-24: every pass then has 2-15K free). 7. y1cc.c, the single-program twin, stays until the passes run on the machine (a y1cc.py change now has two C counterparts to follow), then can go. - y1cc.py: a function defined twice is not an error — both definitions are laid out and the last is compiled twice under the second label (f_f_1: twice, which the assembler rejects); y1cc.c and the passes compile it once. Make it an error like "global declared twice" (a y1cc.py change, still to be decided; the C versions follow). Found 2026-09-24. - (done 2026-09-23: the rebuilt ROM is burned — ROM 2026-09-23, MD5 d2d7b027…, = firmware/rom/shipped/rom.bin; monitor G = JSRUR R7, so --vector is only for a 2021 chip.) Still to do: re-capture it with tests/memory/rom_verify.py (no read-back of the new chip is in the tree yet). - (done 2026-09-22: the sequencer EEPROM holds the regenerated image — BRUR at $AD, the H-2 fix in BRZ/BRNZ/BR16Z/BR16NZ, the H-1 fix in PUSHR; six records differed, all 256 sent with tools/ucode_send.py --all; the scope look at the old image's bus fight was skipped.) (done 2026-09-23 evening: tests/bench/run.py --port 14/14 on the machine after the SHIFT-OUT carry fix; first run found the fault.) Bench-check the reloaded microcode (first evidence 2026-09-22/23: tests/assembler/romcount ran overnight from ROM — BRNZ, DECR, MVRHA, MVAT/MVTA, ADDI, OUTA/INP, BRINL — after romdiag had shown the bring-up machine lacked register card 1; card fitted, R7 reads correctly). The rest is ONE command since 2026-09-23: burn the ROM, then python3 tests/bench/run.py --port /dev/cu.usbserial-X loads and runs hello, BRUR (ABC0123), the ISA sweep (every instruction, one hex byte each) and 12 compiled C programs through the : loader and diffs each against the microcode emulator's transcript; the log lands in tests/bench/logs/ (docs/procedures/BRING-UP.md section 6b). - (done 2026-09-23: compiled C runs on the machine — eleven y1cc programs in tests/bench/ pass through the : loader.) First hardware checks: rt_sub (INVA/moves between ADDTC), rt_divmod (SUBT/SUBI after a comparator branch), the shifts (LDAI 0 / CSHL carry clear), BRDEV selecting the BIOS path, and that BR $F000 after main is acceptable (it restarts the monitor: BASIC cold start, banners). A cmdloop BIOS vector would be cleaner than the restart. - (done 2026-09-24: recursion — static frames kept, a call inside a recursive cycle saves/restores the callee's frame on the stack; functions outside a cycle compile byte-identically, tests/compiler/diffcheck.py.) Still open: reentrancy (an interrupt handler in C), overlaying the static frames of functions that are never live together (cheap, no semantic change), a stack-overflow check (the stack is 768 bytes, $0C00-$0EFF). - (done 2026-09-22: switch, compare chain or BRUR jump table by size, --no-brur until the microcode is reloaded.) - Function pointers (BRUR/JSRUR), signed int (BRLT/BRGT are unsigned comparators: signed compare = flip bit 15 first), long, goto. - --xisa (2026-09-24: LDZ/STZ through a 256-byte variable page, ADDIW, SHL16; opt-in, the default output is byte-identical to before; README "--xisa"). Next, in order: (1) the EEPROM reload and tests/bench --port above; (2) then make it the default (y1cc.py, y1cc.c and cc1 take --no-xisa instead; os/Makefile XISA default on; the tests/os and bench expectations keep their sizes masked or are refreshed); (3) rebuild the passes with it: each is 2-4K smaller (passes.py --xisa), which is the room the native compiler needs. Later: rely on SHL16's carry out of bit 15 in rt_divmod's double-width shift (tested on both emulators, not on the machine yet); a second page (a family through R6.lo would need 16 more opcodes; only 5 are left) or word offsets; drop LDZ/STZ's three idle microcode steps (they take 30 steps like LDR: they save a byte, not time); ADDIW R1 for stack frames. - Code size: peephole over R3/R4 traffic (store-then-reload across labels, MVIW R3,k / MVRLA R3 → LDAI), 8-bit paths for char arithmetic (c + 1 still goes through 16-bit add), constant compares with a zero high byte, for loops counting down to 0. Measure with run.py (bytes + instruction counts per test). - (done 2026-09-22: software/ucemu counts steps and clocks; a per-opcode cost table from it is a one-liner away.) - Microcode emulator follow-ups: interrupts (a source, INT/IRET/IADDR checked against the generator), automatic trace comparison against the interpreter, the video card. (The CF model with a disk image, -c, is done: 2026-09-22 on P8/P9.) - Port the P8X libraries/programs that fit the subset (the P8X OS itself needs its syscalls; recursion exists since 2026-09-24). - Emulator (done 2026-09-22, tools/patched_files.txt): -x scripted mode; BRVR and JSRUR now follow the microcode, so the monitor's G and T commands work on the emulator (they never had). Still stubs vs the hardware: IRET/INT/IADDR, SUB borrow into carry, shifts loading carry, opcode $00. (LDTVR/STTVR, which it ran without microcode, became ADDIW/SHL16 on 2026-09-24.) - Assembler (done 2026-09-22): DS flushes the hex record. Still open: negative numbers silently mis-assemble, labels over 29 chars crash it, source lines are upper-cased (strings in DB "..." come out upper-case).