<?xml version='1.0' encoding='utf-8'?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Swirlsea Tidings</title>
  <id>https://swirlsea.org/jam/atom.xml</id>
  <link rel="self" href="https://swirlsea.org/jam/atom.xml" />
  <author>
    <name>Michael Mangelsdorf</name>
  </author>
  <generator>Lydia Static Site Generator</generator>
  <updated>2026-08-04T08:00:00Z</updated>
  <entry>
    <title>Myth — An Eight-Bit CPU Design based on Sonne</title>
    <id>https://swirlsea.org/permalink/jam/0x6</id>
    <link href="https://swirlsea.org/jam/sections/embedded/myth/page.html" />
    <published>2023-08-01T08:00:00Z</published>
    <updated>2026-08-04T08:00:00Z</updated>
    <author>
      <name>Michael</name>
    </author>
    <content type="html">&lt;!DOCTYPE html&gt;
&lt;html lang="EN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;Myth – An Eight-Bit CPU Design based on Sonne&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;

  &lt;main&gt;

  &lt;p&gt;Posted in my &lt;a href="https://swirlsea.org/jam/section_embedded.html"&gt;'Embedded' section&lt;/a&gt;&lt;/p&gt;


  &lt;h3 id="myth"&gt;Myth&lt;/h3&gt;
&lt;p&gt;This is my successor project to &lt;a
href="https://swirlsea.org/jam/sections/embedded/sonne.html"&gt;Sonne&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="what-was-sonne-again"&gt;What was Sonne again?&lt;/h4&gt;
&lt;p&gt;Sonne is a discrete micro-controller I designed and built from
scratch using about one-hundred 74HC series logic chips, some CMOS
memory and passive-components only. It runs about 1 million instructions
per second on a four-layer PCB fabricated by JLCPCB. See the link for
more details and the KiCad files I used for ordering.&lt;/p&gt;
&lt;p&gt;A
&lt;a href="https://swirlsea.org/jam/common/sonne8-spi-multiply-demo.mov"&gt;demonstration
video&lt;/a&gt; shows the Sonne controller board with a companion I/O-board I
designed stacked on top of it. In the video, it loads a program for
multiplying 7 by 13 from a serial EEPROM and executes it. A boot loader
program is stored in a parallel EEPROM (large chip top-center marked
“App”) mapped into the CPU’s address space. When reset is applied, the
boot loader “bit-bangs” the SPI interface of the IO board to load the
multiplication routine from a Serial EEPROM (seen bottom center). Both
programs are written in the CPU’s native assembly language. The object
code is generated by an assembler I wrote for the project. The
multiplication routine then takes over, computes the result and displays
it on the I/O boards 7-segment display. What is interesting about this
is that none of the electronics components used is an Arduino or
micro-controller or CPU – the circuit &lt;em&gt;is&lt;/em&gt; the CPU described in
this project.&lt;/p&gt;
&lt;figure&gt;
&lt;img
src="https://swirlsea.org/jam/sections/embedded/myth/sonne8pcb-400.jpg"
class="responsive" data-max-width="400" data-lazy="false"
alt="Working PCB based CPU with similar design" /&gt;
&lt;figcaption aria-hidden="true"&gt;Working PCB based CPU with similar
design&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 id="from-sonne-to-myth"&gt;From Sonne to Myth&lt;/h4&gt;
&lt;p&gt;Myth is a heavily revised version of Sonne. Although I am not
planning to build a version using old-fashioned logic chips, care has
been taken to retain the simplicity of it, so that I could still do so
in principle. Being able to build it and being able to visualise the
circuit on a basic component level (as opposed to writing Verilog like
in my &lt;a
href="https://swirlsea.org/jam/section/embedded/paver/post.html"&gt;16-bit
controller&lt;/a&gt; project) was a design goal.&lt;/p&gt;
&lt;p&gt;The schematics have been stripped of everything I thought wasn’t
essential, for it to be easier to read. In other words, although it’s
still possible to load the schematics file into KiCad, its intended use
is only for reference and tinkering with the design.&lt;/p&gt;
&lt;h5 id="simulator"&gt;Simulator&lt;/h5&gt;
&lt;p&gt;A minimal reference implementation of the CPU in C is provided in the
download package (see end of article). It shows the intended workings of
the hardware and has very little code, so have a look.&lt;/p&gt;
&lt;figure&gt;
&lt;img
src="https://swirlsea.org/jam/sections/embedded/myth/mythkicad_thumb-256.jpg"
class="responsive" data-max-width="200" data-lazy="false"
alt="Myth POC Schematics" /&gt;
&lt;figcaption aria-hidden="true"&gt;Myth POC Schematics&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a
href="https://swirlsea.org/jam/sections/embedded/myth/myth.pdf"&gt;Schematics
PDF&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The component count is now higher than for the prototype, because in
this project I’ve implemented the ALU (top and top-right in schematics)
in discrete logic, where it used to be just look-up tables in a big
PROM.&lt;/p&gt;
&lt;h4 id="status"&gt;Status&lt;/h4&gt;
&lt;p&gt;There is now a command-line tool (&lt;code&gt;my&lt;/code&gt;) for exploring
Myth. It includes an assembler.&lt;/p&gt;
&lt;h4 id="code-example"&gt;Code Example&lt;/h4&gt;
&lt;p&gt;Here is an example of a native multiplication routine which
multiplies the two 8-bit numbers in the accumulator, leaving the
accumulator with the 16-bit result (high-order in A). The listing is
“wide” output of the assembler using &lt;code&gt;my -la&lt;/code&gt;. The hex
numbers on the left show the generated object code. As you can see, the
label “3@MUL8” instructs the assembler to emit opcodes into code page
0x03.&lt;/p&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;    ADDR:  OBJCODE:                  LIN:  SOURCE:
                                     0001  
                                     0002  (Multiply 4x7)
    0000:  84 04 84 07 80 03         0003  fa 4, fa 7 - fc &amp;gt;MUL8. ; Sets A to 7, X to 4 and calls mul8
    0006:  8C 06                     0004  @idle fj &amp;lt;idle         ; my-tool stops at 64k cycles
                                     0005                         ; Check the result using: &amp;quot;my -p&amp;quot;
                                     0006  
                                     0007  ; Multiply A times X, result in A:X
                                     0008  ; A:X accumulator acts as a two-element push-down stack when writing to A
                                     0009  ; Both regs are the implied ALU operands, primary result in A, secondary in X
                                     0010  
                                     0011  ; Using page 3: &amp;quot;my&amp;quot;-tool uses p1 for persisting regs, p2 as IO-buffer
                                     0012  
                                     0013  3@MUL8
    0300:  68                        0014      a1          (Save multiplicand into L1 - turns into low order result)
    0301:  17 6B                     0015      xa a4       (Save multiplier into L4)
    0303:  84 00 69                  0016      fa 0, a2    (Set high-order result to 0, keep in L2)
    0306:  85 07                     0017      fd 7        (Initialise loop counter, 8 bits to process)
                                     0018      @loop
                                     0019          fa b0000_0001, 1a AND  (Check LSB of multiplicand)
    030C:  8E 12                     0020          fz &amp;gt;a                  (Skip if zero)
    030E:  63 61 1D 69               0021              4a 2a ADDC a2      (Add multiplier to high order result)
                                     0022          @a
                                     0023          1a SHR, a1             (Shift low-order result right)
    0315:  61 1B 69                  0024          2a SHR, a2             (Shift high-order result right, LSB saved to X)
    0318:  17 60 15 68               0025          xa 1a IOR, a1          (Carry high-order LSB into low-order MSB)
    031C:  8B 08                     0026      fw &amp;lt;loop
    031E:  60                        0027      1a          (Push low-order result)
    031F:  61                        0028      2a          (Push high-order result)
    0320:  05                        0029      RTS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following is a screenshot that demonstrates a syntax highlighting
script I made for Sublime Text. It shows the same routine but as
editable source code as opposed to assembler output.&lt;/p&gt;
&lt;figure&gt;
&lt;img
src="https://swirlsea.org/jam/sections/embedded/myth/syntax-mul-400.jpg"
class="responsive" data-max-width="1200" data-lazy="false" alt=" " /&gt;
&lt;figcaption aria-hidden="true"&gt; &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;A division/modulus routines looks like this:&lt;/p&gt;
&lt;figure&gt;
&lt;img
src="https://swirlsea.org/jam/sections/embedded/myth/syntax-divmod-400.jpg"
class="responsive" data-max-width="1200" data-lazy="false" alt=" " /&gt;
&lt;figcaption aria-hidden="true"&gt; &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The following parts are the reference documentation for the CPU.&lt;/p&gt;
&lt;h2 id="myth-cpumicro-controller"&gt;Myth CPU/Micro-Controller&lt;/h2&gt;
&lt;h2 id="overview"&gt;OVERVIEW&lt;/h2&gt;
&lt;p&gt;Myth is an educational 8-bit CPU with a reduced, but hopefully
enjoyable feature set. It has no microcoded complex instructions, but
care has been taken to allow for practicable programming that should be
intuitive to anyone who has some familiarity with assembly language.&lt;/p&gt;
&lt;p&gt;The project is based on an earlier prototype, built successfully
using just under 100 74HC series chips, some CMOS memory and passive
components.&lt;/p&gt;
&lt;h2 id="part-1"&gt;PART 1&lt;/h2&gt;
&lt;h3 id="basic-cpu"&gt;Basic CPU&lt;/h3&gt;
&lt;h4 id="power-up-and-reset"&gt;Power-up and Reset&lt;/h4&gt;
&lt;p&gt;When the CPU is reset, registers L (stack frame pointer), C (program
page index), PC (program offset) and the BUSY flag (disable interrupts)
are set to zero, and the first instruction is fetched from address C:PC.
PC is then incremented for the next instruction fetch, and so on.&lt;/p&gt;
&lt;h4 id="accumulator-and-alu"&gt;Accumulator and ALU&lt;/h4&gt;
&lt;p&gt;An accumulator with two registers (A and X) feeds into an Arithmetic
Logic Unit (ALU), computes a result, then overwrites A and, in some
cases, X with the results.&lt;/p&gt;
&lt;p&gt;The primary result (the sum of both registers, for instance) is
stored in A, the secondary result (carry bit of the sum) is stored in
X.&lt;/p&gt;
&lt;p&gt;When writing a value into A (instructions _A and GETA), the
accumulator functions as a two-element push-down stack: The old value of
A is saved into X before overwriting it with the new value. X can be
saved into A with the XA instruction.&lt;/p&gt;
&lt;p&gt;Here is an example that demonstrates the use of the ALU in
conjunction with the accumulator:&lt;/p&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;; F_ fetches a literal and places it into whatever _ is

FA 4  (Pushes 4 onto the accumulator stack AX)
FA 5  (Pushes 5)

(A is now 5, X is 4)

ADDC  ; Add A and X; secondary result/side effect:
      ; carry bit in X

(A is now 9, X is 0)

SHR   ; Shift A right; secondary result/side effect:
      ; previous low order bit as 0 or 80h in X

(A is now 4, X is 128/80h)

AGX   ; Produce flag: A greater than X, no side effect

(A is 0 - false, X is 128/80h - unchanged)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The ALU can run the following opcodes:&lt;/p&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt; 0 NOT   Set A to one&amp;#39;s complement of A, X unchanged
 1 ALX   Flag (A&amp;lt;X) in A (255 if true, 0 if false), X unchanged
 2 AEX   Flag (A==X) in A (255 if true, 0 if false), X unchanged
 3 AGX   Flag (A&amp;gt;X) in A (255 if true, 0 if false), X unchanged
 4 AND   Set A to (A AND X), X unchanged
 5 IOR   Set A to (A OR X), X unchanged
 6 EOR   Set A to (A XOR X), X unchanged
 7 XA    Set A equal to X, X unchanged
 8 AX    Set X equal to A
 9 SWAP  Swap A and X
10 SHL   Shift A left, result in A, set X to previous MSB of A as LSB (0 or 1)
11 SHR   Shift A right logically, result in A, set X to previous LSB of A as MSB (0 or 80h)
12 ASR   Shift A right arithmetically, set X to previous LSB of A as MSB (0 or 80h)
13 ADDC  Add A to X, result in A, CARRY bit in X (0 or 1)
14 ADDV  Add A to X, result in A, OVERFLOW flag in X (255 if OVF, else 0)
15 SUBB  Subtract A from X, result in A, BORROW bit in X (0 or 1)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id="memory-layout"&gt;Memory Layout&lt;/h4&gt;
&lt;p&gt;Memory is accessed as 256 pages of 256 bytes each (64k).&lt;/p&gt;
&lt;p&gt;A memory address is composed of a page index (high order address
byte) and an offset (low order address byte) within that page. For
example address 0x6502 has a page index of 0x65 and a byte offset of
0x02.&lt;/p&gt;
&lt;h5 id="offset-registers"&gt;Offset Registers&lt;/h5&gt;
&lt;p&gt;There are exactly two address offset registers, one for fetching and
storing data, and one for fetching code literals and pointing at the
next instruction.&lt;/p&gt;
&lt;p&gt;For data memory access, the value in the offset register (O) is
used.&lt;/p&gt;
&lt;p&gt;The value in the program counter register (PC) is used as the address
offset of the current instruction or code literal in memory.&lt;/p&gt;
&lt;p&gt;There are instructions that access memory with implied offsets,
however, such as GETPUT instructions.&lt;/p&gt;
&lt;h5 id="page-index-registers"&gt;Page-Index Registers&lt;/h5&gt;
&lt;p&gt;There are four page-index registers: B for Base, C for Code, K for
Key, and L for Local.&lt;/p&gt;
&lt;h6 id="register-b"&gt;Register B&lt;/h6&gt;
&lt;p&gt;For data memory access, the base register (B) is used together with O
as the memory pointer to the address where read or write operations
occur.&lt;/p&gt;
&lt;p&gt;The 16-bit value B:O is called the base pointer. The base pointer is
the only means of composing complete 16-bit addresses (pointers)
directly.&lt;/p&gt;
&lt;h6 id="register-c"&gt;Register C&lt;/h6&gt;
&lt;p&gt;During code execution, the program counter register (PC) holds the
byte offset of the current instruction in memory. The page index
(address high byte) to which this offset is added is stored in C (Code).
The current instruction or code literal is thus pointed to by C:PC. PC
auto-increments and wraps without affecting C.&lt;/p&gt;
&lt;h6 id="register-k"&gt;Register K&lt;/h6&gt;
&lt;p&gt;This is an amenity register that can be set to B using the KEY
instruction. It is used in conjunction with the PAIR transfer target xK,
an effect register. When writing into _K, B is set to the value in K,
and then the source value is stored in O. This sets the base pointer to
K:O.&lt;/p&gt;
&lt;p&gt;The intended use for this instruction is to provide a shortcut:&lt;/p&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;;Load a system variable (offset MYVAR) from a table in page 2
fb 2 KEY (Set page-index in K to 2, setting the implied page for writing to the _K effect)
...
fk MYVAR ma (Set base pointer B:O to 2:MYVAR, then read from there into A)&lt;/code&gt;&lt;/pre&gt;
&lt;h6 id="register-l"&gt;Register L&lt;/h6&gt;
&lt;p&gt;The purpose of the L (local) page index register is to provide single
page stack frames for subroutines. During subroutine calls, the page
index in L is decremented, so that memory reads and writes using the L
page index transparently access memory which is local to the currently
running subroutine. When the subroutine returns, the page index in L is
incremented, so that the previous stack frame (or L page) is restored to
the calling subroutine. While conceptually each stack frame uses the
whole page, a conservative memory map will probably reserve the initial
dozens of bytes for subroutine code, so that the call stack can grow or
shrink “overlaying” the code pages. See the following section on stack
frames and GETPUT variables, where this is demonstrated by a design
feature.&lt;/p&gt;
&lt;h4 id="getput-instructions-and-stack-frames"&gt;GETPUT Instructions and
Stack Frames&lt;/h4&gt;
&lt;p&gt;Instructions of type GETPUT complement the behaviour explained in the
previous section on L. GETPUT instructions have mnemonics that consist
of a digit and a letter. The letters name one of the allowed registers
for this type of instruction: B, O, A, or D. The digit is a number
between 1 and 8, used as a short-hand to reference offsets 0xF8 to 0xFF
in the local page (page index L) in ascending order. These memory
locations are also referred to as L1 to L8. By means of the GETPUT
instructions, L1 to L8 become quick-access memory locations that can be
used as local variables within the current stack frame.&lt;/p&gt;
&lt;p&gt;The position of the digit relative to the letter determines the
direction of the data move. For example: 1a transfers the memory content
at offset 0xF8 into register A. And d5 transfers register D
(down-counter) into memory at offset 0xFC of the local page.&lt;/p&gt;
&lt;p&gt;The local-page index can be manually decremented using ENTER, and
manually incremented using LEAVE.&lt;/p&gt;
&lt;h4 id="pair-instructions-and-effect-registers"&gt;PAIR Instructions and
Effect Registers&lt;/h4&gt;
&lt;p&gt;Data move instructions of type PAIR have mnemonics that consist of
two letters, one for the source, followed by one for the target of the
data move. For example, in order to write register B into A, there is an
instruction with the mnemonic “ba”.&lt;/p&gt;
&lt;p&gt;Some registers are “conventional” data registers, such as B and A,
which correspond to physical registers, but there are also EFFECT
registers, which source or distribute data indirectly, or trigger
conditional actions. One of them is the F register (“fetch”). It
extracts the next byte in the instruction stream, and then skips to the
next instruction byte.&lt;/p&gt;
&lt;p&gt;Example: “fa” fetches the byte following the current instruction in
memory, and stores it into A. It then increments the program counter by
1 so that the next instruction is fetched instead of the literal.&lt;/p&gt;
&lt;h4 id="intrapage-control-flow-and-d-register"&gt;Intrapage Control Flow
and D Register&lt;/h4&gt;
&lt;p&gt;Branching inside of the current code page (page index C) is
controlled by writing into the five effect registers J (Jump), W
(While), H (Hot/Not zero), Z (Zero), or N (Negative). These instructions
(conditionally) set the byte offset of the C:PC instruction pointer to
an absolute value. You cannot leave the code page with them, which can
be accomplished using the “COR” jump instruction, or any of the
call/return mechanisms. Relative branching instructions can be
implemented by writing a custom (TRAP) instruction that does this.&lt;/p&gt;
&lt;p&gt;By writing a branch offset into J, the program counter is set to this
new offset without condition. Writing to H loads the PC with the new
offset only if register A (Accumulator) is non-zero (“hot”). Writing to
Z loads PC with the new offset only if A is zero. W (while) works in
conjunction with the D (down-counter) register; when a branch target
offset is written into W, PC is loaded with the new offset only while/if
the D register is non-zero. Then, in either case, the D register is
decremented.&lt;/p&gt;
&lt;h4 id="interpage-control-flow-and-trap-instructions"&gt;Interpage Control
Flow and TRAP Instructions&lt;/h4&gt;
&lt;p&gt;On power-up and reset, registers C and PC are set to zero and the
first instruction is fetched from address C:PC.&lt;/p&gt;
&lt;p&gt;Branching to code in a different page is done by writing into effect
register C (call), by executing a return instruction (RTS or RTI), the
COR (coroutine) instruction, or executing a TRAP call instruction.&lt;/p&gt;
&lt;h5 id="calls"&gt;CALLS&lt;/h5&gt;
&lt;p&gt;Writing into the _C effect register (“call”) triggers the following
sequence of events: C:PC is saved into B:O, the source value of the
instruction (target page) is stored into C, PC is set to zero and L is
decremented by 1.&lt;/p&gt;
&lt;h5 id="traps"&gt;TRAPS&lt;/h5&gt;
&lt;p&gt;TRAP instructions have opcodes that encode an immediate 5-bit target
page-index for a call. When executed, an implicit subroutine call to
this encoded page-index (0-31) occurs within a single instruction. C:PC
is saved into IA, the trap page-index is stored into C, and PC is set to
zero, so that just as for _C calls, the call goes to the head/first byte
of the target page. A TRAP sets the BUSY flag and thus disables
interrupts. The BUSY flag is cleared by executing RTI. Trap calls must
be left by executing RTI (Return from Interrupt)!, since they are using
another pointer (IA) instead of B:O for saving and restoring the return
address.&lt;/p&gt;
&lt;h5 id="coroutines"&gt;COROUTINES&lt;/h5&gt;
&lt;p&gt;The COR (“coroutine”) instruction swaps C:PC and B:O and does not
modify L. The instruction transfers control to the instruction pointed
to by B:O, and then overwrites B:O with the return address (previous
value of C:PC) upon closing the instruction. The behaviour that you get
is a “ratcheting” back-and-forth execution that alternates between two
routines using the same stack frame. Of course you are free at any time
to modify the return address in B:O.&lt;/p&gt;
&lt;h5 id="returns"&gt;RETURNS&lt;/h5&gt;
&lt;p&gt;The RTS instruction conceptually reverses _C call instructions and
return control to the calling routine. When executing RTS, the C:PC
instruction pointer is restored from B:O, and the local-page index in
register L is incremented.&lt;/p&gt;
&lt;p&gt;The RTI instructions conceptually reverse TRAP calls and return
control to the calling routine. When executing RTI, the C:PC pair is
loaded from the IA amenity pointer, and the local-page index in register
L is incremented.&lt;/p&gt;
&lt;h4 id="pair-memory-access---effect-register-m"&gt;PAIR Memory access -
Effect Register M&lt;/h4&gt;
&lt;p&gt;Writing into M (&lt;em&gt;M) stores the value into memory at page index B
offset O. Conversely, reading from M (M&lt;/em&gt;) transfers the value stored
into that memory cell into the target of the PAIR instruction. There are
no other memory transfer instructions besides xMx, the GETPUT
instructions, and F_.&lt;/p&gt;
&lt;h4 id="scrounged-pair-opcodes"&gt;Scrounged PAIR opcodes&lt;/h4&gt;
&lt;p&gt;Inherent NOP instructions such as BB, OO, AA, and DD, and impractical
instructions such as FM and MM (same-cycle memory load-store) are
repurposed (“scrounged”), and their respective opcodes execute different
instructions.&lt;/p&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;FM routed to: KEY (Copy B into K)
MM routed to: CODE (set B:O to C:PC)
BB routed to: LOCAL (set B:O to L:0xF7 - &amp;quot;L0&amp;quot;)
OO routed to: LEAVE (increment L)
AA routed to: ENTER (decrement L)
DD routed to: INC (increment A)
SS routed to: DEC (decrement A)
PP routed to: EA (Copy E to A)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id="bo-pointer-register-bops-and-amenity-pointers"&gt;B:O Pointer
Register (BOPs) and Amenity Pointers&lt;/h4&gt;
&lt;p&gt;As mentioned, registers B (base) and O (offset) form a 16-bit pointer
for memory access. The xU (update) instruction is used to add an 8-bit
signed number to this pointer for doing address arithmetic.&lt;/p&gt;
&lt;p&gt;There are four 16-bit amenity registers into which the B:O pointer
can be saved, or from which it can be loaded in a single instruction
(instruction group BOP). For instance: BOP1 stores the B:O pointer into
P1, and P1BO stores P1 into B:O.&lt;/p&gt;
&lt;p&gt;The fourth amenity pointer (P4 =&amp;gt; IA) is reserved for trap and
interrupt operation. See the note on usage conventions for these
pointers in the “Programming” section.&lt;/p&gt;
&lt;h4 id="interrupts"&gt;Interrupts&lt;/h4&gt;
&lt;p&gt;An external device can make an interrupt request (IRQ) by asserting
the IRQ signal.&lt;/p&gt;
&lt;p&gt;At the beginning of each instruction cycle, the CPU checks whether an
Interrupt must be serviced. There are two conditions which prevent an
interrupt from being serviced by the microcontroller during a given
instruction cycle. Firstly, when the CPU is running code within page 0,
for example just after RESET, and secondly when the BUSY flag is
set.&lt;/p&gt;
&lt;p&gt;If the BUSY flag is not set, and the page index in C is not zero, the
CPU injects a “fake” TRAP call instruction to page 0, instead of
fetching a proper instruction opcode. By entering page 0, an interrupt
service routine in page zero at address-offset 0 is run, and the busy
flag is set, preventing the CPU from accepting (nesting) further
interrupts. The service routine can poll registers attached to the GPIO
bus and dispatch to second-level service handlers if needed and
prioritise interrupts in this way.&lt;/p&gt;
&lt;p&gt;To re-enable interrupts, the software must execute an RTI instruction
(Return from Interrupt). RTI behaves like RTS (Return from Subroutine),
but clears the BUSY flag and uses a separate pointer register (IA) to
save/restore the return address. As long as the BUSY flag remains set,
downstream service routines or other code will not be interrupted by
interrupts.&lt;/p&gt;
&lt;p&gt;The interrupt service subroutine, once it returns, resumes execution
at the point in code where the interrupt occurred.&lt;/p&gt;
&lt;p&gt;You can manually set BUSY by executing a TRAP instruction. Regular
calls to trap destinations (by other instructions than TRAP) do not have
this side effect.&lt;/p&gt;
&lt;h2
id="part-2---io-functionality-dedicated-registers-and-instructions"&gt;PART
2 - I/O Functionality (Dedicated Registers and Instructions)&lt;/h2&gt;
&lt;h4 id="device-selection"&gt;Device Selection&lt;/h4&gt;
&lt;p&gt;The CPU can control serial and parallel communication with external
components. This is facilitated by dedicated hardware-registers and
instructions.&lt;/p&gt;
&lt;h5 id="e-enable-register"&gt;E (Enable) register&lt;/h5&gt;
&lt;p&gt;The 8-bit E register is used to control the select state of devices
attached to the serial or parallel bus lines. To this end, the register
is divided into two independent four-bit groups for device
selection.&lt;/p&gt;
&lt;p&gt;Each four-bit group (L for the low-order, H for the high-order)
drives a 4-to-16 line decoder, which maps the bit pattern encoded by
that group to 1 of 16 possible, mutually exclusive select signals
(SL0-15 and SH0-15) per group.&lt;/p&gt;
&lt;h6 id="special-purpose-selectors"&gt;Special Purpose Selectors&lt;/h6&gt;
&lt;p&gt;Select signals SL0 and SH0 are reserved, and select a NULL device
(“nothing”). These signals are selected on power-up or reset.&lt;/p&gt;
&lt;p&gt;SL1 corresponds to the internal POR register output enable signal
(POE). SH1 corresponds to the internal PIR register latch enable signal
(PLE).&lt;/p&gt;
&lt;p&gt;In order to latch the current value of the GPIO bus into the PIR, the
PLE signal must be set by the high-order nybble of E. Selecting the POE
signal by the low-order nybble of E enables the output of the POR
register onto the GPIO bus.&lt;/p&gt;
&lt;p&gt;All remaining selectors can be used freely.&lt;/p&gt;
&lt;h4 id="communication-registers"&gt;Communication Registers&lt;/h4&gt;
&lt;p&gt;SOR (Serial Output Register)&lt;/p&gt;
&lt;p&gt;A write-only parallel-to-serial shift register for serialising an
output byte, modelled after a 74HC165 chip. Writing an output value for
serialisation is done by writing the value into register S.&lt;/p&gt;
&lt;p&gt;This value is clocked out/serialized by pulsing the SCLK clock line.
This is achieved by alternating SCL and SCH instructions (set clock
low/high).&lt;/p&gt;
&lt;p&gt;SCL-SCH-SCL generates a positive clock edge. SCH-SCL-SCH generates an
inverted clock. Eight clock cycles are required to send-out a byte.&lt;/p&gt;
&lt;p&gt;SIR (Serial Input Register)&lt;/p&gt;
&lt;p&gt;A read-only serial-to-parallel shift register for de-serialising an
incoming bit stream into an input byte, modelled after a 74HC595
chip.&lt;/p&gt;
&lt;p&gt;Receiving a byte is done by executing the SCL/SCH instructions eight
times as explained above. Reading the deserialised input byte is done by
reading register S.&lt;/p&gt;
&lt;p&gt;POR (Parallel Output Register)&lt;/p&gt;
&lt;p&gt;A tri-state register with 8-bit parallel output, modelled after a
74HC574 chip. Writing an output byte onto the parallel bus is a two step
process. First, the data byte must be latched into the register by
writing it into P. Then, the register output must be enabled by
selecting POE in the E register, as described above.&lt;/p&gt;
&lt;p&gt;PIR (Parallel Input Register)&lt;/p&gt;
&lt;p&gt;A read-only 8-bit parallel input register, modelled after a 74HC574
chip. Latching the current 8-bit value of the parallel bus into the
register is done by selecting PLE in E. The latched data byte can then
be read from P. The bus operates in weak pull-down mode, so when all
bus-devices are in tri-state mode, a zero value is registered.&lt;/p&gt;
&lt;h4 id="communication-instructions"&gt;Communication Instructions&lt;/h4&gt;
&lt;h4 id="serial"&gt;SERIAL&lt;/h4&gt;
&lt;p&gt;The following instructions contained in the SYS group operate on the
communication registers:&lt;/p&gt;
&lt;p&gt;SSI (Shift Serial In)&lt;/p&gt;
&lt;p&gt;This instruction receives a serial bit via the serial input line. It
then shifts SIR left and sets its least significant bit (LSB) to the
received bit state.&lt;/p&gt;
&lt;p&gt;SSO (Shift Serial Out)&lt;/p&gt;
&lt;p&gt;This instruction outputs the most significant bit (MSB) of SOR onto
the serial output line and then shifts SOR left.&lt;/p&gt;
&lt;p&gt;SCH (Serial clock high)&lt;/p&gt;
&lt;p&gt;This instruction sets the clock line to HIGH.&lt;/p&gt;
&lt;p&gt;SCL (Serial clock low)&lt;/p&gt;
&lt;p&gt;This instruction sets the clock line to LOW.&lt;/p&gt;
&lt;h4 id="parallel"&gt;PARALLEL&lt;/h4&gt;
&lt;p&gt;The CPU interfaces to an external bidirectional 8-bit wide bus (GPIO
bus).&lt;/p&gt;
&lt;p&gt;It can communicate on this bus by writing a data byte into P (POR
register), and then enabling POE in the E register by setting its lower
nybble to 1. Setting the bit to 1 switches the POR from tri-state output
to active output, so that the byte value is output on the bus lines.&lt;/p&gt;
&lt;p&gt;While the output is active, other devices on the bus can read the
data byte. Usually, such a device will be controlled or synchronised by
the Myth controller. It does this by enabling or disabling latches or
outputs of the required device in E as explained above. This generates
output signals made available to external devices on the
micro-controller pins. Exactly two output signals (one per nybble in E)
can be active at the same time.&lt;/p&gt;
&lt;p&gt;Deselecting POE in E again (setting the low-order nybble to a value
different from 1) tristates the POR output, so that other devices can
put data bytes on the GPIO bus.&lt;/p&gt;
&lt;p&gt;Enabling PLE in E (setting the high-order nybble to 1) latches a data
byte into the PIR. This byte can then be read from the P register.&lt;/p&gt;
&lt;p&gt;Once a data byte has been read, the PIR input should be deselected
again in E by setting the high-order nybble to a value different from
1.&lt;/p&gt;
&lt;h3 id="serial-communication"&gt;Serial Communication&lt;/h3&gt;
&lt;p&gt;The Serial Peripheral Interface (SPI) protocol can be implemented
using the device enable register E, serial registers SIR and SOR, and
instructions SCL, SCH, SSI, and SSO.&lt;/p&gt;
&lt;h4 id="device-selection-1"&gt;Device Selection&lt;/h4&gt;
&lt;p&gt;Before communicating with a specific device connected to the serial
bus, the corresponding selector bit representing the device must be set
in the E register.&lt;/p&gt;
&lt;h4 id="data-transmission"&gt;Data Transmission&lt;/h4&gt;
&lt;p&gt;To transmit data to the selected device, the processor writes a data
byte (8 bits) to be serialised for output into the SOR (Serial Output)
register.&lt;/p&gt;
&lt;p&gt;The SSO (Serial Shift Out) instruction is then used, which clocks the
serial output shift register and produces a data bit on the MOSI line.
Using the instruction sequence SCL SCH SCL (Serial Clock Low/High), a
positive edge clock pulse is generated.&lt;/p&gt;
&lt;p&gt;As each bit is shifted out, it is sent to the selected device through
the serial bus. The passive device processes the transmitted bit and the
cycle repeats.&lt;/p&gt;
&lt;h4 id="data-reception"&gt;Data Reception&lt;/h4&gt;
&lt;p&gt;To receive data from an external device, the SSI (Serial Shift In)
instruction is used. It clocks the serial input shift register, allowing
the processor to receive one bit of data at a time from the selected
device via the MISO line. The received data can then be read from the S
register. Clocking is done as above.&lt;/p&gt;
&lt;h5 id="cpol-clock-polarity"&gt;CPOL (Clock Polarity)&lt;/h5&gt;
&lt;p&gt;The CPOL parameter determines the idle state of the clock signal. The
controller provides signals SCL (Serial Clock Low) and SCH (Serial Clock
High) instructions which can be used to control the clock signal’s
state.&lt;/p&gt;
&lt;p&gt;To configure CPOL=0 (clock idles low), execute SCL to set the clock
signal low during the idle state. To configure CPOL=1 (clock idles
high), execute SCH to set the clock signal high during the idle
state.&lt;/p&gt;
&lt;h5 id="cpha-clock-phase"&gt;CPHA (Clock Phase)&lt;/h5&gt;
&lt;p&gt;The CPHA parameter determines the edge of the clock signal where data
is captured or changed. The Myth controller provides instructions SSI
(Serial shift in) and SSO (Serial shift out) to control data transfer on
each clock transition.&lt;/p&gt;
&lt;p&gt;To configure CPHA=0 (data captured on the leading edge), execute SSI
before the clock transition to capture the incoming data. To configure
CPHA=1 (data captured on the trailing edge), execute SSI after the clock
transition to capture the incoming data.&lt;/p&gt;
&lt;p&gt;Similarly, to transmit data on the leading or trailing edge, execute
SSO before or after the clock transition, respectively.&lt;/p&gt;
&lt;h4 id="device-deselection"&gt;Device Deselection&lt;/h4&gt;
&lt;p&gt;After data transmission is complete, the selected device needs to be
deselected to allow other devices to communicate on the bus. This is
done by updating the E register with the appropriate value.&lt;/p&gt;
&lt;h2 id="part-3---programming"&gt;Part 3 - Programming&lt;/h2&gt;
&lt;h3 id="opcode-format"&gt;Opcode Format&lt;/h3&gt;
&lt;p&gt;Operation codes fall into 6 format groups, which are decodable using
a priority encoder.&lt;/p&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;                  -- Opcode Bits --
                     MSB     LSB
all 0: OPC_SYS       00000   xxx    See table @ SYS decoder
 else: OPC_BOP       00001   xxx    See table @ BOP decoder
       OPC_ALU       0001   xxxx    See table @ ALU
       OPC_TRAP      001   xxxxx    b0-4: DESTPAGE
       OPC_GETPUT    01 xx x xxx    b0-2: OFFS, b3: GET/PUT, b4-5: REG
       OPC_PAIR      1  xxx xxxx    b0-3: DST, b4-6: SRC&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="assembler"&gt;Assembler&lt;/h3&gt;
&lt;h4 id="labels"&gt;Labels&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Address labels are defined using identifiers prefixed with an
at-sign (&lt;code&gt;@labelname&lt;/code&gt;). A decimal number before the at sign
(&lt;code&gt;123@labelname&lt;/code&gt;) sets the page-index for emitting object
code to that number, if the labelname is all-uppercase, or sets the
page-offset to that number if the labelname contains lowercase
letters.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A label may optionally be followed by a colon (&lt;code&gt;:&lt;/code&gt;),
like &lt;code&gt;@FOO:&lt;/code&gt; — this marks it as a &lt;strong&gt;global
label&lt;/strong&gt;. Global labels are inserted into the &lt;strong&gt;native
symbol table&lt;/strong&gt; (inside the resulting binary image).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Labels must be unique &lt;strong&gt;unless&lt;/strong&gt; they are a
&lt;strong&gt;single lowercase letter&lt;/strong&gt; (&lt;code&gt;@a&lt;/code&gt;,
&lt;code&gt;@b&lt;/code&gt;, etc.), which may be defined multiple times (for generic
labels such as short jumps). When defined multiple times, the nearest
matching label in either direction will be used, see below.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="label-references"&gt;Label References&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use &lt;code&gt;&amp;lt;label&lt;/code&gt; for a &lt;strong&gt;backward
reference&lt;/strong&gt; to the closest matching label earlier in the
file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use &lt;code&gt;&amp;gt;label&lt;/code&gt; for a &lt;strong&gt;forward
reference&lt;/strong&gt; to the closest matching label later in the
file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use &lt;code&gt;#label&lt;/code&gt; for a general, first-match
search.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="trap-call-syntax"&gt;Trap Call Syntax&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;The asterisk is used for trap call references: &lt;code&gt;*label&lt;/code&gt;
or &lt;code&gt;*123&lt;/code&gt;, or &lt;code&gt;*1Fh&lt;/code&gt; etc. all assemble trap
instructions.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="constants-data-labels"&gt;Constants (Data Labels)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You can define a constant using &lt;code&gt;name=value&lt;/code&gt;. The
value can be any valid number literal (see below).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Defined constants can be used later by referencing their name in
the source code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A colon after the label name (&lt;code&gt;name:=value&lt;/code&gt;) defines a
global label.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="special-tokens"&gt;Special Tokens&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;PAGE&lt;/code&gt; — Page-index of the current
instruction.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;OFFSET&lt;/code&gt; — Page-offset of the current
instruction.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="literals"&gt;Literals&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Decimal&lt;/strong&gt;: e.g., &lt;code&gt;42&lt;/code&gt;,
&lt;code&gt;-5&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hexadecimal&lt;/strong&gt;: Suffix &lt;code&gt;h&lt;/code&gt; (e.g.,
&lt;code&gt;2Ah&lt;/code&gt;, &lt;code&gt;0FFh&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Binary&lt;/strong&gt;: Prefix &lt;code&gt;b&lt;/code&gt;, underscores
allowed (e.g., &lt;code&gt;b1010_0001&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Character literal&lt;/strong&gt;: Single character in quotes
(e.g., &lt;code&gt;'A'&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;String literal&lt;/strong&gt;: Double quotes, may contain
spaces (e.g., &lt;code&gt;" hello world "&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="mnemonics"&gt;Mnemonics&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Mnemonics are case insensitive — &lt;code&gt;addc&lt;/code&gt;,
&lt;code&gt;ADDC&lt;/code&gt;, and &lt;code&gt;AddC&lt;/code&gt; are all valid.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Example mnemonics: &lt;code&gt;AND&lt;/code&gt;, &lt;code&gt;ADDC&lt;/code&gt;,
&lt;code&gt;RET&lt;/code&gt;, &lt;code&gt;2r&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="comments"&gt;Comments&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Any text after a semicolon (&lt;code&gt;;&lt;/code&gt;) is a
comment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Text enclosed in parentheses &lt;code&gt;(A comment)&lt;/code&gt; is also
treated as a comment — including the parentheses themselves. Useful for
commenting out just one or two mnemonics.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="phrasing"&gt;Phrasing&lt;/h4&gt;
&lt;p&gt;Any assembly token can be followed by a comma (,) or a dot (.), and
dashes are ignored.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fa 1, fa 2 (pushed onto A) - ADDC. ; These are fine&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id="syntax-highlighting-in-sublime"&gt;Syntax-Highlighting in
Sublime&lt;/h4&gt;
&lt;p&gt;Place the syntax and color scheme definition files from the repo
inside the folder:
&lt;code&gt;/Users/???/Library/Application Support/Sublime Text 3/Packages/User'&lt;/code&gt;
(macOS).&lt;/p&gt;
&lt;p&gt;Then in Sublime, press CMD-Shift-P. In the dialog, navigate to:
&lt;code&gt;Preferences: Settings- Syntax Specific&lt;/code&gt; and paste the
following snippet.&lt;/p&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;// These settings override both User and Default settings for the myth-my8 syntax
{
    // Sets the colors used within the text area.
    // The value &amp;quot;auto&amp;quot; will switch between the &amp;quot;light_color_scheme&amp;quot; and
    // &amp;quot;dark_color_scheme&amp;quot; based on the operating system appearance.
    &amp;quot;color_scheme&amp;quot;: &amp;quot;myth-dark-my8.sublime-color-scheme&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is also a light theme
(&lt;code&gt;myth-light-my8.sublime-color-scheme&lt;/code&gt;) in the repo.&lt;/p&gt;
&lt;h3 id="my-tool-for-native-development"&gt;“My”-Tool for Native
Development&lt;/h3&gt;
&lt;p&gt;The command line tool &lt;code&gt;my&lt;/code&gt; (for Myth) can be used to set
registers, print memory read-outs, and for assembling and running
assembler code. The source-code for &lt;code&gt;my&lt;/code&gt; is in the
&lt;code&gt;util&lt;/code&gt; folder of the Myth GitHub repo.&lt;/p&gt;
&lt;p&gt;On each invocation, the program reads in a complete 64k RAM image
(default name: &lt;code&gt;ram.bin&lt;/code&gt; that is used as memory for a virtual
Myth CPU. You can create this file by running
&lt;code&gt;my -N &amp;lt;filename&amp;gt;&lt;/code&gt;. Before the tool terminates, the
(possibly modified) RAM is persisted back into the image file.&lt;/p&gt;
&lt;p&gt;You can assemble a source file into this image with
&lt;code&gt;my -la &amp;lt;filename&amp;gt;&lt;/code&gt;. The &lt;code&gt;l&lt;/code&gt; option in this
example prints an additional assembly listing including the emitted
object code by source line.&lt;/p&gt;
&lt;p&gt;A memory read-out (dump) can be printed out with
&lt;code&gt;my -b 2 -d 2000h&lt;/code&gt;. This example prints 16 data bytes stored
starting at address 0x2000, listing them in three number bases. See
&lt;code&gt;my -h&lt;/code&gt; for more options.&lt;/p&gt;
&lt;p&gt;You can set individual CPU registers using
&lt;code&gt;my -w regname=value&lt;/code&gt;. The command &lt;code&gt;my -p&lt;/code&gt; prints
out a text block of all registers (“pulley”).&lt;/p&gt;
&lt;p&gt;Individual instructions can be executed with
&lt;code&gt;my -o mnemonic&lt;/code&gt;, and the virtual CPU can be instructed to
run n cycles with &lt;code&gt;my -r n&lt;/code&gt; (for single-step only use
&lt;code&gt;my -s&lt;/code&gt;). Be mindful of setting C and PC to suitable
values!&lt;/p&gt;
&lt;p&gt;There is a special dialog mode, when &lt;code&gt;my&lt;/code&gt; is run with a
command line where the first character is not a ‘-’ (not a command line
option). The command line (max 127 ascii bytes) is then copied into the
RAM image at 0x2100 and the CPU is run in order to have it write an
output string (max 127 bytes) at 0x2180. The CPU is stopped and the tool
terminates as soon as the output string becomes not NULL, or once 64k
cycles have elapsed. You can then run &lt;code&gt;my -m&lt;/code&gt; to try for
another 64k cycles.&lt;/p&gt;
&lt;p&gt;Example &lt;code&gt;my&lt;/code&gt; session:&lt;/p&gt;
&lt;p&gt;The example sets the accumulator registers A and X, and executes the
ADDC instruction, which produces the sum of A and X in A, and the carry
generated by the addition into X.&lt;/p&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;(base) ➜  myth-tool git:(main) ✗ my -p

C:00 PC:A7          E:00(0000_0000) E_OLD:00(0000_0000)
SCLK:0 MISO:0 MOSI:0      SIR:00 SOR:00   PIR:00 POR:00
A:09(+009,0000_1001)  X:00(+000,0000_0000)   D:00  L:00
BO:0000  P1:0000 P2:0000 P3:0000 P4:0000   KEY:00 L0:00
L1:00(+000) L2:00(+000) L3:00(+000) L4:00(+000)   IRQ:0
L5:00(+000) L6:00(+000) L7:00(+000) L8:00(+000)  BUSY:0

(base) ➜  myth-tool git:(main) ✗ my -w a=4
(base) ➜  myth-tool git:(main) ✗ my -w x=253
(base) ➜  myth-tool git:(main) ✗ my -p

C:00 PC:A7          E:00(0000_0000) E_OLD:00(0000_0000)
SCLK:0 MISO:0 MOSI:0      SIR:00 SOR:00   PIR:00 POR:00
A:04(+004,0000_0100)  X:FD(-003,1111_1101)   D:00  L:00
BO:0000  P1:0000 P2:0000 P3:0000 P4:0000   KEY:00 L0:00
L1:00(+000) L2:00(+000) L3:00(+000) L4:00(+000)   IRQ:0
L5:00(+000) L6:00(+000) L7:00(+000) L8:00(+000)  BUSY:0

(base) ➜  myth-tool git:(main) ✗ my -o ADDC
(base) ➜  myth-tool git:(main) ✗ my -p

C:00 PC:A7          E:00(0000_0000) E_OLD:00(0000_0000)
SCLK:0 MISO:0 MOSI:0      SIR:00 SOR:00   PIR:00 POR:00
A:01(+001,0000_0001)  X:01(+001,0000_0001)   D:00  L:00
BO:0000  P1:0000 P2:0000 P3:0000 P4:0000   KEY:00 L0:00
L1:00(+000) L2:00(+000) L3:00(+000) L4:00(+000)   IRQ:0
L5:00(+000) L6:00(+000) L7:00(+000) L8:00(+000)  BUSY:0&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="preliminary-rom-image-firmware"&gt;Preliminary ROM image
(“Firmware”)&lt;/h3&gt;
&lt;h4 id="reserved-pages-for-my-tool"&gt;Reserved Pages for My-Tool&lt;/h4&gt;
&lt;h5 id="page-0---interrupts"&gt;Page 0 - Interrupts&lt;/h5&gt;
&lt;p&gt;Due to how interrupts work, code execution after power-on, reset or
when an interrupt request is accepted, starts at address 0h. Page 0
should be reserved for handling these various cases, particularly the
main interrupt service handler.&lt;/p&gt;
&lt;h5 id="pages-1-31-trap-handlers"&gt;Pages 1-31 Trap Handlers&lt;/h5&gt;
&lt;p&gt;The TRAP instruction (*n) is a single-instruction subroutine call to
an immediate address encoded in the opcode using 5 bits. The range of
call target pages is therefore 0..31. Trap 0 is equivalent to causing an
interrupt to happen: doing this calls page 0 and sets the BUSY flag.&lt;/p&gt;
&lt;h5 id="page-32-2000h---register-store"&gt;Page 32 (2000h) - Register
Store&lt;/h5&gt;
&lt;p&gt;My-Tool persists the CPU registers in the 64k firmware image starting
at page index 32, address 2000h (see my-tool project files), with the
whole page being reserved.&lt;/p&gt;
&lt;h5 id="page-33-2100h---text-buffers"&gt;Page 33 (2100h) - Text
Buffers&lt;/h5&gt;
&lt;p&gt;Further, page 33 is used for two text buffers which my-tool used to
communicate with the Myth VM: In dialog mode, a maximum of 127 bytes of
the command line text is stored as a zero terminated string at address
2100h (input buffer). The VM is expected to respond by writing a zero
terminated string not exceeding 127 characters into the output buffer at
address 2180h.&lt;/p&gt;
&lt;p&gt;The first byte of the output buffer should be monitored; when it
becomes non-zero, this is a termination/ready signal from the VM. This
implies that the output string should be written, with the first
character last, overwriting the initial zero at 2180h put there by
My-Tool before running the VM.&lt;/p&gt;
&lt;h5 id="page-34-2200h---key"&gt;Page 34 (2200h) - Key&lt;/h5&gt;
&lt;p&gt;The firmware currently sets K to page 34, so that xK instructions set
the B:O pointer to 34:x. The xK instruction was implemented to have
quick access to one “key” page of frequently used system variables.&lt;/p&gt;
&lt;p&gt;(A table of these will be maintained here)&lt;/p&gt;
&lt;h5 id="page-35-2300h---stack-and-p3-pointer"&gt;Page 35 (2300h) - Stack
and P3 Pointer&lt;/h5&gt;
&lt;p&gt;The amenity pointer P3 is currently reserved as a system wide stack
pointer, which serves as parameter stack pointer and threading token
pointer, and it is set to 23FFh, growing towards lower addresses. If
your routines use it for other purposes, you should restore its value on
return.&lt;/p&gt;
&lt;h5 id="page-36-2400h---threading-stack"&gt;Page 36 (2400h) - Threading
Stack&lt;/h5&gt;
&lt;p&gt;Amenity pointer P2 is currently reserved as a system wide threading
stack pointer, set to 24FFh.&lt;/p&gt;
&lt;p&gt;P1 can be used as a scratch register for the base pointer (P:O).&lt;/p&gt;
&lt;h4 id="remarks"&gt;Remarks&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Local Page Frames&lt;/strong&gt; Be aware that the local frame
pointer in L decrements during subroutine nesting. Subroutines use the
highest 9 bytes (F7h - FFh) for local variables L0-L8. The address of L0
is loaded into B:O by the LOCAL instruction. L1-L8 are accessible using
GETPUT-instructions. In principal, the whole local page is available to
the currently running subroutine. This implies a tradeoff between how
much data you store in your local frames, and how long your subroutines
are since they must stay clear of the local storage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Threaded Code&lt;/strong&gt; This is particular to this
firmware only, but the subroutines implementing threaded code will use
80h as the implied page offset for interpreter-called code.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="symbol-table"&gt;Symbol Table&lt;/h4&gt;
&lt;p&gt;The assembler outputs a “Global” symbol table. Entries in this table
are formed as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Link-Byte&lt;/strong&gt;: Relative byte offset to the next
entry, or zero for end-of-table.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Info-Byte&lt;/strong&gt;: High-order nybble encodes the symbol
type, low order is the length in bytes of the symbol name -1; hence a
maximum length of 16.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Name-String&lt;/strong&gt; Name of the symbol&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Zero&lt;/strong&gt;: Zero for string termination of the name
string&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data-Bytes&lt;/strong&gt;: Variable number of data-bytes,
corresponding to the type of the symbol&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following types are currently used:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Assembly label&lt;/strong&gt;: Type=1, data-bytes:
none&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mnemonic&lt;/strong&gt;: Type=2, data-bytes: opcode&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="debugging-native-code"&gt;Debugging Native Code&lt;/h3&gt;
&lt;p&gt;Don’t forget that you can place hooks directly into cpu.c for
instance. As a temporary debugging aid, writing into E (_E) is caught in
cpu.c und causes My-Tool to print-out a register dump.&lt;/p&gt;
&lt;h3 id="parameter-passing"&gt;Parameter passing&lt;/h3&gt;
&lt;p&gt;Use the accumulator (AX) for primary arguments in general.&lt;/p&gt;
&lt;p&gt;There is a “hidden” local variable shortcut “L0”. You can obtain a
pointer to this memory location by executing the instruction “LOCAL”.
Local sets B to the Local page, and O to F7h, the byte offset just below
L1. Then use MxM instructions such as “am” to read or store into the L0
variable:&lt;/p&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;; Store number 4 in L0:
(fa 2 shl,) local am.&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="tables"&gt;Tables&lt;/h2&gt;
&lt;h3 id="opcode-matrix"&gt;Opcode Matrix&lt;/h3&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;       x0    x1    x2    x3    x4    x5    x6    x7    x8    x9    xA    xB    xC    xD    xE    xF
0x    NOP   SSI   SSO   SCL   SCH   RTS   RTI   COR  P1BO  BOP1  P2BO  BOP2  P3BO  BOP3  IABO  BOIA
1x    NOT   ALX   AEX   AGX   AND   IOR   EOR    XA    AX  SWAP   SHL   SHR   ASR  ADDC  ADDV  SUBB
2x     *0    *1    *2    *3    *4    *5    *6    *7    *8    *9   *10   *11   *12   *13   *14   *15
3x    *16   *17   *18   *19   *20   *21   *22   *23   *24   *25   *26   *27   *28   *29   *30   *31
4x     1b    2b    3b    4b    5b    6b    7b    8b    b1    b2    b3    b4    b5    b6    b7    b8
5x     1o    2o    3o    4o    5o    6o    7o    8o    o1    o2    o3    o4    o5    o6    o7    o8
6x     1a    2a    3a    4a    5a    6a    7a    8a    a1    a2    a3    a4    a5    a6    a7    a8
7x     1d    2d    3d    4d    5d    6d    7d    8d    d1    d2    d3    d4    d5    d6    d7    d8
8x     fc   KEY    fb    fo    fa    fd    fs    fp    fe    fk    fu    fw    fj    fh    fz    fn
9x     mc  CODE    mb    mo    ma    md    ms    mp    me    mk    mu    mw    mj    mh    mz    mn
Ax     bc    bm LOCAL    bo    ba    bd    bs    bp    be    bk    bu    bw    bj    bh    bz    bn
Bx     oc    om    ob LEAVE    oa    od    os    op    oe    ok    ou    ow    oj    oh    oz    on
Cx     ac    am    ab    ao ENTER    ad    as    ap    ae    ak    au    aw    aj    ah    az    an
Dx     dc    dm    db    do    da   INC    ds    dp    de    dk    du    dw    dj    dh    dz    dn
Ex     sc    sm    sb    so    sa    sd   DEC    sp    se    sk    su    sw    sj    sh    sz    sn
Fx     pc    pm    pb    po    pa    pd    ps    EA    pe    pk    pu    pw    pj    ph    pz    pn&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="opcode-descriptions"&gt;Opcode Descriptions&lt;/h3&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;Group SYS

0x00: NOP   Pass the turn (no operation)
0x01: SSI   Shift serial bit in
0x02: SSO   Shift serial bit out
0x03: SCL   Set serial clock low
0x04: SCH   Set serial clock high
0x05: RTS   Return from subroutine
0x06: RTI   Return from interrupt
0x07: COR   Coroutine Jump&lt;/code&gt;&lt;/pre&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;Group BOP

0x08: P1BO  Copy pointer P1 into B:O
0x09: BOP1  Copy B:O into pointer P1

0x0A: P2BO  Copy pointer P2 into B:O
0x0B: BOP2  Copy B:O into pointer P2

0x0C: P3BO  Copy pointer P3 into B:O
0x0D: BOP3  Copy B:O into pointer P3

0x0E: IABO  Copy pointer IA into B:O
0x0F: BOIA  Copy B:O into pointer IA&lt;/code&gt;&lt;/pre&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;Group ALU

0x10: NOT   Set A to one&amp;#39;s complement of A , X unchanged
0x11: ALX   Flag (A&amp;lt;X) in A (255 if true, 0 if false), X unchanged
0x12: AEX   Flag (A==X) in A (255 if true, 0 if false), X unchanged
0x13: AGX   Flag (A&amp;gt;X) in A (255 if true, 0 if false), X unchanged
0x14: AND   Set A to (A AND X), X unchanged
0x15: IOR   Set A to (A OR X), X unchanged
0x16: EOR   Set A to (A XOR X), X unchanged
0x17: XA    Set A equal to X, X unchanged
0x18: AX    Set X equal to A
0x19: SWAP  Swap A and X
0x1A: SHL   Shift A left, result in A, set X to previous MSB of A as LSB (0 or 1)
0x1B: SHR   Shift A right logically, result in A, set X to previous LSB of A as MSB (0 or 80h)
0x1C: ASR   Shift A right arithmetically, set X to previous LSB of A as MSB (0 or 80h)
0x1D: ADDC  Add A to X, result in A, CARRY bit in X (0 or 1)
0x1E: ADDV  Add A to X, result in A, OVERFLOW flag in X (255 if OVF, else 0)
0x1F: SUBB  Subtract A from X, result in A, BORROW bit in X (0 or 1)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;Group TRAP

0x20: *0    Trap call to page 0, offset 0 - Set BUSY flag
0x21: *1    Trap call to page 1, offset 0
0x22: *2    Trap call to page 2, offset 0
0x23: *3    Trap call to page 3, offset 0
0x24: *4    Trap call to page 4, offset 0
0x25: *5    Trap call to page 5, offset 0
0x26: *6    Trap call to page 6, offset 0
0x27: *7    Trap call to page 7, offset 0
0x28: *8    Trap call to page 8, offset 0
0x29: *9    Trap call to page 9, offset 0
0x2A: *10   Trap call to page 10, offset 0
0x2B: *11   Trap call to page 11, offset 0
0x2C: *12   Trap call to page 12, offset 0
0x2D: *13   Trap call to page 13, offset 0
0x2E: *14   Trap call to page 14, offset 0
0x2F: *15   Trap call to page 15, offset 0
0x30: *16   Trap call to page 16, offset 0
0x31: *17   Trap call to page 17, offset 0
0x32: *18   Trap call to page 18, offset 0
0x33: *19   Trap call to page 19, offset 0
0x34: *20   Trap call to page 20, offset 0
0x35: *21   Trap call to page 21, offset 0
0x36: *22   Trap call to page 22, offset 0
0x37: *23   Trap call to page 23, offset 0
0x38: *24   Trap call to page 24, offset 0
0x39: *25   Trap call to page 25, offset 0
0x3A: *26   Trap call to page 26, offset 0
0x3B: *27   Trap call to page 27, offset 0
0x3C: *28   Trap call to page 28, offset 0
0x3D: *29   Trap call to page 29, offset 0
0x3E: *30   Trap call to page 30, offset 0
0x3F: *31   Trap call to page 31, offset 0&lt;/code&gt;&lt;/pre&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;Group GETPUT

0x40: 1b    Load B from L1 (M[L:F8h])
0x41: 2b    Load B from L2 (M[L:F9h])
0x42: 3b    Load B from L3 (M[L:FAh])
0x43: 4b    Load B from L4 (M[L:FBh])
0x44: 5b    Load B from L5 (M[L:FCh])
0x45: 6b    Load B from L6 (M[L:FDh])
0x46: 7b    Load B from L7 (M[L:FEh])
0x47: 8b    Load B from L8 (M[L:FFh])

0x48: b1    Store B into L1 (M[L:F8h])
0x49: b2    Store B into L2 (M[L:F9h])
0x4A: b3    Store B into L3 (M[L:FAh])
0x4B: b4    Store B into L4 (M[L:FBh])
0x4C: b5    Store B into L5 (M[L:FCh])
0x4D: b6    Store B into L6 (M[L:FDh])
0x4E: b7    Store B into L7 (M[L:FEh])
0x4F: b8    Store B into L8 (M[L:FFh])

0x50: 1o    Load O from L1 (M[L:F8h])
0x51: 2o    Load O from L2 (M[L:F9h])
0x52: 3o    Load O from L3 (M[L:FAh])
0x53: 4o    Load O from L4 (M[L:FBh])
0x54: 5o    Load O from L5 (M[L:FCh])
0x55: 6o    Load O from L6 (M[L:FDh])
0x56: 7o    Load O from L7 (M[L:FEh])
0x57: 8o    Load O from L8 (M[L:FFh])

0x58: o1    Store O into L1 (M[L:F8h])
0x59: o2    Store O into L2 (M[L:F9h])
0x5A: o3    Store O into L3 (M[L:FAh])
0x5B: o4    Store O into L4 (M[L:FBh])
0x5C: o5    Store O into L5 (M[L:FCh])
0x5D: o6    Store O into L6 (M[L:FDh])
0x5E: o7    Store O into L7 (M[L:FEh])
0x5F: o8    Store O into L8 (M[L:FFh])

0x60: 1a    Load A from L1 (M[L:F8h])
0x61: 2a    Load A from L2 (M[L:F9h])
0x62: 3a    Load A from L3 (M[L:FAh])
0x63: 4a    Load A from L4 (M[L:FBh])
0x64: 5a    Load A from L5 (M[L:FCh])
0x65: 6a    Load A from L6 (M[L:FDh])
0x66: 7a    Load A from L7 (M[L:FEh])
0x67: 8a    Load A from L8 (M[L:FFh])

0x68: a1    Store A into L1 (M[L:F8h])
0x69: a2    Store A into L2 (M[L:F9h])
0x6A: a3    Store A into L3 (M[L:FAh])
0x6B: a4    Store A into L4 (M[L:FBh])
0x6C: a5    Store A into L5 (M[L:FCh])
0x6D: a6    Store A into L6 (M[L:FDh])
0x6E: a7    Store A into L7 (M[L:FEh])
0x6F: a8    Store A into L8 (M[L:FFh])

0x70: 1d    Load D from L1 (M[L:F8h])
0x71: 2d    Load D from L2 (M[L:F9h])
0x72: 3d    Load D from L3 (M[L:FAh])
0x73: 4d    Load D from L4 (M[L:FBh])
0x74: 5d    Load D from L5 (M[L:FCh])
0x75: 6d    Load D from L6 (M[L:FDh])
0x76: 7d    Load D from L7 (M[L:FEh])
0x77: 8d    Load D from L8 (M[L:FFh])

0x78: d1    Store D into L1 (M[L:F8h])
0x79: d2    Store D into L2 (M[L:F9h])
0x7A: d3    Store D into L3 (M[L:FAh])
0x7B: d4    Store D into L4 (M[L:FBh])
0x7C: d5    Store D into L5 (M[L:FCh])
0x7D: d6    Store D into L6 (M[L:FDh])
0x7E: d7    Store D into L7 (M[L:FEh])
0x7F: d8    Store D into L8 (M[L:FFh])&lt;/code&gt;&lt;/pre&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;Group PAIR

0x80: FC    Take M[C:PC++] as page-index, load the index into C, set PC to 0. Save return pointer into B:O. Decrement L
0x81: KEY   Copy register B into K
0x82: FB    Take M[C:PC++] into B
0x83: FO    Take M[C:PC++] into O
0x84: FA    Push M[C:PC++] into Acc
0x85: FD    Take M[C:PC++] into D
0x86: FS    Take M[C:PC++] into SOR
0x87: FP    Take M[C:PC++] into POR
0x88: FE    Take M[C:PC++] into E, sets device enable signals
0x89: FK    Take M[C:PC++] into O, load K into B
0x8A: FU    Take M[C:PC++] as 8-bit signed number and add it to 16-bit pointer B:O
0x8B: FW    Take M[C:PC++] as page offset and store it into PC - while register D is not zero. In either case, decrement D
0x8C: FJ    Take M[C:PC++] as page offset and store it into PC - always
0x8D: FH    Take M[C:PC++] as page offset and store it into PC - if A is not equal to zero
0x8E: FZ    Take M[C:PC++] as page offset and store it into PC - if A is equal to zero
0x8F: FN    Take M[C:PC++] as page offset and store it into PC - if A is negative (has bit 7 set)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;0x90: MC    Take M[B:O] as page-index, load the index into C, set PC to 0. Save return pointer into B:O. Decrement L
0x91: CODE  Copy pointer C:PC into B:O
0x92: MB    Take M[B:O] into B
0x93: MO    Take M[B:O] into O
0x94: MA    Push M[B:O] into Acc
0x95: MD    Take M[B:O] into D
0x96: MS    Take M[B:O] into SOR
0x97: MP    Take M[B:O] into POR
0x98: ME    Take M[B:O] into E, sets device enable signals
0x99: MK    Take M[B:O] into O, load K into B
0x9A: MU    Take M[B:O] as 8-bit signed number and add it to 16-bit pointer B:O
0x9B: MW    Take M[B:O] as page offset and store it into PC - while register D is not zero. In either case, decrement D
0x9C: MJ    Take M[B:O] as page offset and store it into PC - always
0x9D: MH    Take M[B:O] as page offset and store it into PC - if A is not equal to zero
0x9E: MZ    Take M[B:O] as page offset and store it into PC - if A is equal to zero
0x9F: MN    Take M[B:O] as page offset and store it into PC - if A is negative (has bit 7 set)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;0xA0: BC    Take B as page-index, load the index into C, set PC to 0. Save return pointer into B:O. Decrement L
0xA1: BM    Take B into M[B:O]
0xA2: LOCAL Copy pointer L:F7h (L0) into B:O
0xA3: BO    Take B into O
0xA4: BA    PushB into Acc
0xA5: BD    Take B into D
0xA6: BS    Take B into SOR
0xA7: BP    Take B into POR
0xA8: BE    Take B into E, sets device enable signals
0xA9: BK    Take B into O, load K into B
0xAA: BU    Take B as 8-bit signed number and add it to 16-bit pointer B:O
0xAB: BW    Take B as page offset and store it into PC - while register D is not zero. In either case, decrement D
0xAC: BJ    Take B as page offset and store it into PC - always
0xAD: BH    Take B as page offset and store it into PC - if A is not equal to zero
0xAE: BZ    Take B as page offset and store it into PC - if A is equal to zero
0xAF: BN    Take B as page offset and store it into PC - if A is negative (has bit 7 set)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;0xB0: OC    Take O as page-index, load the index into C, set PC to 0. Save return pointer into B:O. Decrement L
0xB1: OM    Take O into M[B:O]
0xB2: OB    Take O into B
0xB3: LEAVE Increment L
0xB4: OA    Push O into Acc
0xB5: OD    Take O into D
0xB6: OS    Take O into SOR
0xB7: OP    Take O into POR
0xB8: OE    Take O into E, sets device enable signals
0xB9: OK    Take O into O, load K into B
0xBA: OU    Take O as 8-bit signed number and add it to 16-bit pointer B:O
0xBB: OW    Take O as page offset and store it into PC - while register D is not zero. In either case, decrement D
0xBC: OJ    Take O as page offset and store it into PC - always
0xBD: OH    Take O as page offset and store it into PC - if A is not equal to zero
0xBE: OZ    Take O as page offset and store it into PC - if A is equal to zero
0xBF: ON    Take O as page offset and store it into PC - if A is negative (has bit 7 set)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;0xC0: AC    Take A as page-index, load the index into C, set PC to 0. Save return pointer into B:O. Decrement L
0xC1: AM    Take A into M[B:O]
0xC2: AB    Take A into B
0xC3: AO    Take A into O
0xC4: ENTER Decrement L
0xC5: AD    Take A into D
0xC6: AS    Take A into SOR
0xC7: AP    Take A into POR
0xC8: AE    Take A into E, sets device enable signals
0xC9: AK    Take A into O, load K into B
0xCA: AU    Take A as 8-bit signed number and add it to 16-bit pointer B:O
0xCB: AW    Take A as page offset and store it into PC - while register D is not zero. In either case, decrement D
0xCC: AJ    Take A as page offset and store it into PC - always
0xCD: AH    Take A as page offset and store it into PC - if A is not equal to zero
0xCE: AZ    Take A as page offset and store it into PC - if A is equal to zero
0xCF: AN    Take A as page offset and store it into PC - if A is negative (has bit 7 set)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;0xD0: DC    Take D as page-index, load the index into C, set PC to 0. Save return pointer into B:O. Decrement L
0xD1: DM    Take D into M[B:O]
0xD2: DB    Take D into B
0xD3: DO    Take D into O
0xD4: DA    Push D into Acc
0xD5: INC   Increment A
0xD6: DS    Take D into SOR
0xD7: DP    Take D into POR
0xD8: DE    Take D into E, sets device enable signals
0xD9: DK    Take D into O, load K into B
0xDA: DU    Take D as 8-bit signed number and add it to 16-bit pointer B:O
0xDB: DW    Take D as page offset and store it into PC - while register D is not zero. In either case, decrement D
0xDC: DJ    Take D as page offset and store it into PC - always
0xDD: DH    Take D as page offset and store it into PC - if A is not equal to zero
0xDE: DZ    Take D as page offset and store it into PC - if A is equal to zero
0xDF: DN    Take D as page offset and store it into PC - if A is negative (has bit 7 set)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;0xE0: SC    Take SIR as page-index, load the index into C, set PC to 0. Save return pointer into B:O. Decrement L
0xE1: SM    Take SIR into M[B:O]
0xE2: SB    Take SIR into B
0xE3: SO    Take SIR into O
0xE4: SA    Push SIR into Acc
0xE5: SD    Take SIR into D
0xE6: DEC   Decrement A
0xE7: SP    Take SIR into POR
0xE8: SE    Take SIR into E, sets device enable signals
0xE9: SK    Take SIR into O, load K into B
0xEA: SU    Take SIR as 8-bit signed number and add it to 16-bit pointer B:O
0xEB: SW    Take SIR as page offset and store it into PC - while register D is not zero. In either case, decrement D
0xEC: SJ    Take SIR as page offset and store it into PC - always
0xED: SH    Take SIR as page offset and store it into PC - if A is not equal to zero
0xEE: SZ    Take SIR as page offset and store it into PC - if A is equal to zero
0xEF: SN    Take SIR as page offset and store it into PC - if A is negative (has bit 7 set)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class="sourceCode"&gt;&lt;code&gt;0xF0: PC    Take PIR as page-index, load the index into C, set PC to 0. Save return pointer into B:O. Decrement L
0xF1: PM    Take PIR into M[B:O]
0xF2: PB    Take PIR into B
0xF3: PO    Take PIR into O
0xF4: PA    Push PIR into Acc
0xF5: PD    Take PIR into D
0xF6: PS    Take PIR into SOR
0xF7: EA    Push E into Acc
0xF8: PE    Take PIR into E, sets device enable signals
0xF9: PK    Take PIR into O, load K into B
0xFA: PU    Take PIR as 8-bit signed number and add it to 16-bit pointer B:O
0xFB: PW    Take PIR as page offset and store it into PC - while register D is not zero. In either case, decrement D
0xFC: PJ    Take PIR as page offset and store it into PC - always
0xFD: PH    Take PIR as page offset and store it into PC - if A is not equal to zero
0xFE: PZ    Take PIR as page offset and store it into PC - if A is equal to zero
0xFF: PN    Take PIR as page offset and store it into PC - if A is negative (has bit 7 set)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="instructions-of-type-pair"&gt;Instructions of Type PAIR&lt;/h3&gt;
&lt;p&gt;Pair instructions are best explained by looking at their mnemonics. A
pair mnemonic consists of two letters, the first being the source
register, the second being the target register. Pair instructions copy a
value from their source to their target.&lt;/p&gt;
&lt;p&gt;There are 8 sources: F, M, B, O, A, D, S, P.&lt;/p&gt;
&lt;p&gt;All sources can also be targets, except F.&lt;/p&gt;
&lt;p&gt;In addition to these 7 sources we can use as targets, there are 9
additional targets which in turn can’t be sources: C, E, K, U, W, J, H,
Z, N.&lt;/p&gt;
&lt;p&gt;So in total, there are 16 targets.&lt;/p&gt;
&lt;p&gt;You can now combine these to form “pairs”: FK (Copy F to K), AE (Copy
A to E), and so forth.&lt;/p&gt;
&lt;p&gt;Regarding pair instructions, there is one additional rule, and one
exception to this rule to remember.&lt;/p&gt;
&lt;h4 id="scrounging"&gt;Scrounging&lt;/h4&gt;
&lt;p&gt;The rule is that when both letters (source and target) are the same,
that means something different! - another instruction entirely.&lt;/p&gt;
&lt;p&gt;This is called “scrounging” the original pair. The simplified reason
for this feature is that copying the value from one register into itself
(AA - copy A to A, for example) generally has no effect, so we are doing
something else instead!&lt;/p&gt;
&lt;p&gt;Now, the exception to this rule of scrounging, just something to
remember, is that since F is not a target register, we scrounge FM
instead, even though F and M are not the same letters. The decision for
implementing this was not arbitrary: F_ fetches the next byte in the
instruction stream, a memory operation, and _M stores into memory. The
design of the CPU does not permit these two operations to happen in the
same instruction cycle.&lt;/p&gt;
&lt;p&gt;Here are all the scrounged combinations, and what they “redirect” to,
which instruction they do instead:&lt;/p&gt;
&lt;p&gt;FM: KEY, MM: CODE, BB: LOCAL, OO: LEAVE, AA: ENTER, DD: INC, SS: DEC,
PP: EA&lt;/p&gt;
&lt;p&gt;Each of these will be explained in the appropriate section.&lt;/p&gt;
&lt;h4 id="effects"&gt;Effects&lt;/h4&gt;
&lt;p&gt;Some of the sources and targets are what are called effects. Effect
means that instead of being a little physical storage location (called a
“register”), reading or writing it triggers a specific side effect. It
has an action. We could say that effects are just names for their
actions, instead of names for a storage cell or register.&lt;/p&gt;
&lt;p&gt;The actions can be quite surprising, they can enable hardware, or
jump to a different subroutine, so be prepared, we will explain them all
as we go on.&lt;/p&gt;
&lt;p&gt;We are now going to take a systematic look at each of the 8
sources.&lt;/p&gt;
&lt;h3 id="f"&gt;F&lt;/h3&gt;
&lt;p&gt;F stands for fetch. F is an effect, and it can’t be used as a target.
Its action is that when you read from it, the computer looks at the byte
that &lt;em&gt;would be&lt;/em&gt; the next instruction in memory. It reads that
byte and uses it as the source value. Then it skips over the byte, when
it looks for the next instruction. This process is called “fetching”,
and the byte is called a literal. The way you use this in your program
is like so: “FB 2”, for example. The computer sees the FB pair
instruction (copy F to B), and this triggers the read action of F. The
next instruction would be the literal 2, but F reads and then skips it,
instead of fetching it as an instruction in the next cycle. It then
takes the 2 as the source value and copies it to B.&lt;/p&gt;
&lt;h3 id="m"&gt;M&lt;/h3&gt;
&lt;p&gt;M stands for memory. M can be a source or a target. This computer has
only one memory pointer, in the form of the B:O register pair. This
register pair is called the base pointer, and B is called the base
register. The O register is called the offset register. To read a
sixteen bit address, you have to set B to the high order byte (the
“base” address or “page”), and O to the low order byte (the “offset”) of
the address. This is best viewed in hexadecimal: Setting B to 20h and O
to 30h means setting B:O to the 16-bit address 2030h. To actually access
the memory data pointed to by B:O, you then have to use a pair
instruction that uses M as source or target. M is an effect. Its actions
are as follows: When you read from M, you get as source the value of the
memory cell at the 16-bit address formed by B:O as we said before. When
you write to M, the written value is stored at the B:O location in
memory.&lt;/p&gt;
&lt;h3 id="a"&gt;A&lt;/h3&gt;
&lt;p&gt;A stands for accumulator. A can be source or target. The accumulator
is the heart of the computer, and it is both a very important effect and
a very important register. It takes its name from the fact that it
accumulates results. But the accumulator actually consists of
&lt;strong&gt;two&lt;/strong&gt; registers working together. Apart from the “main”
accumulator register A, there is its reclusive friend, the X register. X
stands for mystery.&lt;/p&gt;
&lt;p&gt;You may have noticed that we didn’t mention X when we listed the pair
sources or targets. Can we access it somehow? We will explain more about
A and X in the section about doing computations with the ALU. For now,
let’s just explain one curious thing about A: When you read from it,
it’s just a little storage cell with a value, but it has a
write-“action”. Writing to it does two things. First, the computer
copies A into X. And after that, it stores the source value of your
instruction into A. It works like a little push down stack - you “push”
the value into A, which in turn pushes its previous value down into X,
to save it for later.&lt;/p&gt;
&lt;p&gt;So register “A” is both a regular register, and an effect register!
Finally, as this seems an appropriate place for it, let’s reveal three
of the infamous “scrounge instructions” (remember, these are
combinations of equal pair letters: BB, OO, and so one, that are
replaced by other , more useful instructions): unspectacularly, INC
increments A, DEC decrements A. And then there is EA, which pushes E
into A, using A’s action that we have just explained. This instruction
is the only way to get to the value of E!&lt;/p&gt;
&lt;p&gt;We are getting ahead of ourselves, but for completeness let me spoil
it and tell you that there is an “XA” instruction, which only
&lt;em&gt;looks&lt;/em&gt; like a pair instruction but is really an ALU instruction,
for copying the value of X into A.&lt;/p&gt;
&lt;h3 id="d"&gt;D&lt;/h3&gt;
&lt;p&gt;D stands for “down” or “decrement”. D can be source or target. D is a
register, just a little storage location. What makes D interesting and
is the reason for its name, is that it can count itself down. This
happens when you write into _W effect (“while”), which effectively turns
D into a loop counter.&lt;/p&gt;
&lt;h3 id="s"&gt;S&lt;/h3&gt;
&lt;p&gt;S stands for serial. S can be source or target. Serial stands for two
registers or storage locations, depending on whether you read or write.
Reading S gets you what’s in the “serial input register” (SIR), and
writing S puts a byte into the “serial output register” (SOR).&lt;/p&gt;
&lt;h3 id="p"&gt;P&lt;/h3&gt;
&lt;p&gt;P stands for parallel. And what has been said for S applies verbatim
to P, if you change the letter S for a P: SIR becomes PIR, SOR becomes
POR, and so on.&lt;/p&gt;
&lt;p&gt;So now that we have finished explaining the source registers and
effects, let’s look at the targets, the second letters that occur in
each pair instruction. As has been said, the 8 sources we have explained
in the last section - except F! - can also be used as target. We will
concentrate now on the 9 remaining targets: C, E, K, U, W, H, Z, N.
These can’t occur as sources!&lt;/p&gt;
&lt;h3 id="c"&gt;_C&lt;/h3&gt;
&lt;p&gt;Target “C” is another effect register, like A. It acts like a
register and stores a value, but also has a powerful effect. C stands
for “call”. When you write a value to C, it is understood that this
value is the high-byte of a memory address where a subroutine is stored.
A subroutine is a program that your code jumps to, and which jumps back
to your own program at the exact place where you left off, once it’s
done.&lt;/p&gt;
&lt;p&gt;It can do this because it remembers the memory address in your
program it needs to return to. The “C” target effect helps with that: it
stores the high-byte of where the subroutine needs to return to in B
(the base) and the low-byte in O (the offset). Then it “calls” the
subroutine. Subroutines in this computer always start at the beginning
of a page of memory, where the offset is zero. Therefore, when you write
a value into C, the program counter (remember: it provides the offset of
the next instruction in memory) is set to zero, and the C register is
set to the value that is copied from the source of the pair instruction.
This has the effect that the next instruction will be the first of the
subroutine.&lt;/p&gt;
&lt;p&gt;So to resume: when you write a value into the C effect register, the
current value of C is copied into B, and PC is copied into O. This is so
that the subroutine can return to you. Then comes the actual call: The
source value is copied into C, and PC is set to zero.&lt;/p&gt;
&lt;p&gt;Now, unless your subroutine is very simple, it will need to store
both B and O somewhere (ideally using GETPUT instructions, since they
store into a “local” memory region especially made for your subroutine),
and then restore the values to B and O just before returning, so the CPU
knows where to go.&lt;/p&gt;
&lt;h3 id="e"&gt;_E&lt;/h3&gt;
&lt;p&gt;“E”, which stands for “enable” is another effect register. It stores
a value, but it also has an action when you write to it. The bit pattern
in the source byte selects or deselects hardware that is connected to
your computer. This is explained in the section on input/output. Don’t
write to E if you don’t understand yet how it works, just because the
hardware may do strange things as a result. But don’t worry, it isn’t
that complicated!&lt;/p&gt;
&lt;h3 id="k"&gt;_K&lt;/h3&gt;
&lt;p&gt;The pair target “K”, for “key”, is purely an effect, but it’s tied to
the K storage register via one of the infamous “scrounge” instructions
we mentioned, aptly called KEY. The KEY instruction copies B into K. But
we are here to discuss the target effect “K”. Its action is: Copy K into
B, and set O to the source value. So it sets the memory pointer B:O to
the address of a particular byte in page K - a shortcut you can use for
accessing a table of variables in a less verbose way.&lt;/p&gt;
&lt;h3 id="u"&gt;_U&lt;/h3&gt;
&lt;p&gt;The “U” effect (update) also has an action that involves the B:O
pointer. It treats B:O as a 16-bit number and adds the source value to
it as a signed number. So essentially it allows you to add or subtract a
constant to or from B:O.&lt;/p&gt;
&lt;h4 id="w-h-z-n"&gt;W, H, Z, N&lt;/h4&gt;
&lt;p&gt;The remaining four targets are all effects: W, H, Z and N. All of
them are about jumping to a new offset within the memory page you are
currently in.&lt;/p&gt;
&lt;h3 id="w"&gt;_W&lt;/h3&gt;
&lt;p&gt;Perhaps the most interesting is W (while). As we mentioned, W works
together with D (down-counter) register. When you write a value to W, it
is understood that the source value is an address offset in the current
program page, a new value that you want to set PC to. Now, the action of
W is the following: If D is not zero, set PC to the source value (jump
to this location within the page). Otherwise just continue what you are
doing. Then, however it turned out, decrement D by 1. So you can see
that this is essentially a while loop, with a dedicated register.&lt;/p&gt;
&lt;h3 id="h"&gt;_H&lt;/h3&gt;
&lt;p&gt;The action of the H effect is similar to W, but PC is set to the
source value and does the jump, if the accumulator register A is not
zero. Otherwise we just continue. The letter H stands for “hot”. That’s
a term used in electronics for when a data word has at least one bit
that is not zero. On other computers the action of this instruction
would be called “branch if not zero”.&lt;/p&gt;
&lt;h3 id="z"&gt;_Z&lt;/h3&gt;
&lt;p&gt;The action of the Z effect is almost identical, but this time we jump
if A &lt;strong&gt;is&lt;/strong&gt; in fact zero.&lt;/p&gt;
&lt;h3 id="n"&gt;_N&lt;/h3&gt;
&lt;p&gt;Then, finally, the N effect. N stands for negative, so we will jump
if the value in A is negative. A byte is negative, by a very practical
convention called the two’s complement, if its highest order bit (bit 7
if count from zero) is one. So this action can be used for checking that
bit, too.&lt;/p&gt;
&lt;p&gt;Now you know what all the pair instructions do! They make up half of
the entire instruction set of this computer, there are just so many
combinations - exactly 128: 8 sources x 16 targets, including the
“scrounges”.&lt;/p&gt;
&lt;h3 id="getput"&gt;Getput&lt;/h3&gt;
&lt;p&gt;Getput instructions are the second largest group of instructions,
after pair instructions. There are 64 of them, and just like pairs,
their great number is only because there are exactly that many
combinations of essentially the same type of instruction.&lt;/p&gt;
&lt;h4 id="local-variables"&gt;Local Variables&lt;/h4&gt;
&lt;p&gt;To understand what getput instructions do, you need to know what the
L register is for, and what local variables are. L stands for local, and
it contains a page index. Whenever you call a subroutine, that number is
decremented, and whenever you return from a subroutine, the number
increments.&lt;/p&gt;
&lt;p&gt;So when you call a subroutine, it gets its own page, and when it
returns, you get the same page number back that you had before the
call.&lt;/p&gt;
&lt;p&gt;This mechanism allows you to store “your stuff” into your page, and
the subroutine can store “its stuff” in its page. This is where the name
local comes from, it simply means “local” to a specific subroutine. The
advantage of this, and this may not be obvious!, is that when the
subroutine returns, your own local variables are guaranteed to be just
as you left them, because the subroutine can’t even get to them.&lt;/p&gt;
&lt;p&gt;But it also means that if you store, say, the value 2 into L1 and
call a subroutine, it will be another, fresh L1, and the 2 will not be
there, should you try to read it from within the subroutine. If you want
to pass along data to a subroutine, you will need to either store it
into a memory location both the calling routine and the subroutine agree
on, or in a register. You can’t use B or O though, because the CPU uses
it behind the scenes to carry out the call and return. The accumulator
is an ideal place for this (it can hold two values, A and X, for example
an address pointer).&lt;/p&gt;
&lt;h4 id="l1-to-l8"&gt;L1 to L8&lt;/h4&gt;
&lt;p&gt;The 8 very last bytes at the end of a local page are special: They
are called L1 to L8, and they are what getput instructions operate on.
So L8 corresponds to the last offset in page L, at offset FFh. Then L7
is at FEh, and so on.&lt;/p&gt;
&lt;p&gt;These instructions allow you to load (get) the registers B, O, A, or
D from one of these 8 memory locations, or to store (put) these
registers there.&lt;/p&gt;
&lt;p&gt;The syntax is very simple, you combine the number of the location (1
to 8) with the name of one of the four registers. If the number comes
first (1a - L1 into A) it means you want do store the memory variable
into the register. If the register comes first (a1 - A into L1) then
you’re storing the register into the memory variable.&lt;/p&gt;
&lt;h4 id="instruction-local-and-l0"&gt;Instruction LOCAL and L0&lt;/h4&gt;
&lt;p&gt;Time to disclose three more of the infamous “scrounge” instructions.
Let’s start with LOCAL. This instruction sets the B register to L, and
the offset to point to the memory cell just below L1. You guessed it,
that memory cell is called L0.&lt;/p&gt;
&lt;p&gt;So if you want to quickly store away A somewhere, you can say: “LOCAL
AM”, and this will copy A to L0. If you are unsure how AM works, read
the section on pair instructions. In a nutshell: A_ means “take A as a
source”, so M is the target. M is an “effect”, the storage effect. When
you write to it, it stores the value at address B:O (the register pair
is set by LOCAL, as we said) in memory.&lt;/p&gt;
&lt;p&gt;Note that you can obtain the page number stored in L by saying LOCAL
and inspecting B.&lt;/p&gt;
&lt;h4 id="enter-and-leave"&gt;ENTER and LEAVE&lt;/h4&gt;
&lt;p&gt;The other two scrounge instructions that belong in this section are
ENTER (decrement L), and LEAVE (increment L). The words evoke entering a
new local page (like a subroutine does), and leaving the page again
(when the subroutine returns).&lt;/p&gt;
&lt;p&gt;The L register cannot be set directly to a specific page, but if you
really, really want to, you can say “LOCAL BD (puts L into D to know
what page it is and then somehow calculate how far it is to where you
want it), and then say”ENTER” or “LEAVE” to wind L to the page you
need.&lt;/p&gt;
&lt;p&gt;If you just say LEAVE inside a subroutine, your local variables will
switch to those of the calling subroutine, be careful.&lt;/p&gt;
&lt;p&gt;But if you need &lt;strong&gt;more&lt;/strong&gt; local variable, you say ENTER
in your subroutine, and get another page, with another set of L0 to L8
(but your previous ones will not be availabe unless you switch back to
them doing LEAVE).&lt;/p&gt;
&lt;p&gt;Now you can see why there are 64 getput instructions: 8 memory cells
(L0-L8), 2 directions (get and put), and 4 registers (B, O, A, D). 8x2x4
combinations = 64.&lt;/p&gt;
&lt;h3 id="bops"&gt;BOPs&lt;/h3&gt;
&lt;p&gt;This group of 8 instructions is probably the easiest one to
understand. BOP stands for “B:O Pointer”. As you know by now, the B:O
register pair has a central place as the only address register in this
computer.&lt;/p&gt;
&lt;p&gt;In order to use it, you must copy suitable values into B and into O,
before you can read or write memory. To reduce repetitive code, and make
things faster, the BOP instructions copy two registers at once. The copy
action is always between one of four “wide” amenity pointers, P1, P2, P3
and IA, and the B:O register pair.&lt;/p&gt;
&lt;p&gt;All the BOP instructions do is the saving and restoring between B:O
and these pointers. Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;**P1BO**: copy P1 into BO.
**BOP1**: copy BO into P1.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same for P2BO and BOP2, P3BO and BOP3, … and IABO and BOIA.&lt;/p&gt;
&lt;p&gt;The IA amenity register is special, in exactly the same way that B:O
itself is special regarding subroutine calls. While it can be meaningful
to read or write it, you must only use it for its intended purpose, or
your program can “crash”.&lt;/p&gt;
&lt;p&gt;IA stores the return address during trap calls and interrupts, and
the RTI instruction relies on this address to be there. Interrupts can
happen at any time, if you haven’t disabled them. Let’s say you store a
copy of BO into IA. Now, when an interrupt comes along, your program
stops right in its tracks, and the address where you are is stored into
IA, overwriting the value that you were counting on to be there. When
the interrupt is done, while your program does successfully resume where
it left off, IA will have changed and still have the left-over address
in it. This will happen unbeknownst to you - your program will sometimes
work (no interrupt happened), sometimes fail!&lt;/p&gt;
&lt;h3 id="doing-maths-and-things-to-do-with-bits"&gt;Doing Maths and Things
to do with Bits&lt;/h3&gt;
&lt;p&gt;This section is all about the accumulator: register A and its
reclusive sister register, X. Well, there is actually a third player
that also has an important role: the Arithmetic Logic Unit, or ALU.&lt;/p&gt;
&lt;p&gt;Accumulator review: When you read A, you get its value back. When you
write A, its current value gets copied into X, and then the new value is
stored in A (like a little push-down stack).&lt;/p&gt;
&lt;h4 id="alu"&gt;ALU&lt;/h4&gt;
&lt;p&gt;What’s an ALU? It’s a very common, central part in a computer that
takes input from registers, performs one of a number of possible
operations (the instruction tells it which one it is) and then stores
the result back in some register.&lt;/p&gt;
&lt;p&gt;In this computer, the ALU sees what is in A and X, so it bases its
operation on those two values. There are 16 possible operations, 16
instructions. Let’s look at them in a systematic way.&lt;/p&gt;
&lt;p&gt;Something noteworthy is that the first half of the ALU instructions
in numerical order just produce a primary result (“the” result) and it
gets stored in A. X remains unchanged.&lt;/p&gt;
&lt;p&gt;The other half of the instructions gives you the primary result in A
(the one you &lt;strong&gt;probably&lt;/strong&gt; want, but also another useful
aside, stored in X).&lt;/p&gt;
&lt;h4 id="operations-that-leave-x-alone"&gt;Operations that leave X
alone&lt;/h4&gt;
&lt;h5 id="not"&gt;NOT&lt;/h5&gt;
&lt;p&gt;This instruction just inverts the bits in A. Those bits that are 0
become 1, those that are 1 become 0. This is also called the “one’s
complement”. When you add 1 to it, it becomes “minus” the original
number, the negative version of it. Look up how this works, it’s
fascinating: This is called the two’s complement. It is how the computer
actually does subtraction behind then scenes.&lt;/p&gt;
&lt;p&gt;Did you know that doing “EOR FFh” to a byte has the same effect as
NOT? This is beclause Exclusive-OR is 1 if and only if the two input
bits are different. The number FFh has all its bits set to 1. So if a
bit in the other number is 1, that is the same as the corresponding bit
in FFh, so the result bit is 0. And if the bit is 0, well then it’s
different from the 1 bit in FFh, so the result is 1.&lt;/p&gt;
&lt;h5 id="alx-aex-agx"&gt;ALX, AEX, AGX&lt;/h5&gt;
&lt;p&gt;These stand for A-less-than-X, A-equal-to-X, and A-greater-than-X.
They produce a number in A that is 0, if the named condition is false,
and 255 (all bits set) if the condition is true. If you are wondering
why 255: You can say NOT and get the opposite, for example to turn
“A-less-than-X” into “A-greater-than-or-equal-than-X”.&lt;/p&gt;
&lt;h5 id="and-ior-eor"&gt;AND, IOR, EOR&lt;/h5&gt;
&lt;p&gt;These compute A AND X, A OR X (inclusive or), and A XOR X (exclusive
or), and store their result in A.&lt;/p&gt;
&lt;h4 id="xa"&gt;XA&lt;/h4&gt;
&lt;p&gt;Aha! So you &lt;strong&gt;can&lt;/strong&gt; get the value of X. With this
instruction, it gets stored in A.&lt;/p&gt;
&lt;h4 id="operations-with-secondary-result-in-x"&gt;Operations with secondary
result in X&lt;/h4&gt;
&lt;h4 id="ax-swap"&gt;AX SWAP&lt;/h4&gt;
&lt;p&gt;AX: Stores A into X. Swap: Swaps A and X.&lt;/p&gt;
&lt;h4 id="shl-shr-asr"&gt;SHL SHR ASR&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;SHL shifts every bit in A to the left by one position. Position
one - the first bit - is set to zero. So far so good. At this point,
importantly, X gets zeroed. Now. The highest order bit from the original
number (the bit that got “pushed” out) is put &lt;strong&gt;back&lt;/strong&gt; as
the first bit of X. So at the end, X either contains 0 or 1.&lt;/p&gt;
&lt;p&gt;This instruction is exactly the same as multiplying A by two, or
adding A to itself. Think about it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SHR is the opposite. Every bit is shifted one position to the
right. The highest position - bit 7 - is set to zero. So far so good. X
gets cleared again. Now. The lowest order bit from the original number
(the bit that got “pushed” out) is put &lt;strong&gt;back&lt;/strong&gt; as the
highest-order bit of X. So at the end, X contains either 0 or 80h (bit 7
set).&lt;/p&gt;
&lt;p&gt;This instruction is exactly the same as diving A by two, if you
prefer to use the larger “unsigned” range of your bytes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ASR is just like SHR, with one little difference:&lt;/p&gt;
&lt;p&gt;Every bit is shifted one position to the right. The highest position
- bit 7 - is set to &lt;strong&gt;whatever the highest-order bit of the
original number was&lt;/strong&gt;. So far so good. X gets cleared again. Now.
The lowest order bit from the original number (the bit that got “pushed”
out) is put &lt;strong&gt;back&lt;/strong&gt; as the highest-order bit of X. So at
the end, X is either 0 or 80h.&lt;/p&gt;
&lt;p&gt;The reason why the high-order bit is copied down has to do with
negative binary numbers. As we said in an earlier section, negative
binary numbers have their highest bit set to 1. The highest bit is often
abbreviated to MSB (Most Significant Bit). When you use SHR (the other
shift-right instruction), the MSB changes from a possible 1 to a zero.
Practically, let’s say you divide -6 by 2, with SHR you would get 3, not
-3. Whereas ASR gives the correct result in this case. For this reason,
ASR stands for Arithmetic Shift Right.&lt;/p&gt;
&lt;p&gt;This instruction is exactly the same as diving A by two, if you
prefer to use the smaller, “signed” range of your bytes.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="addc-addv-subb"&gt;ADDC ADDV SUBB&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;ADDC is the easiest of the three. A receives the sum of A and X.
If the sum didn’t fit into a byte, then X is set to 1. Else, X is set to
zero.&lt;/p&gt;
&lt;p&gt;Example: I’m sure you know that the maximum unsigned number you can
store in a byte is 255. So if A=60 and X=200, then ADDC will leave A=5
and X set to 1.&lt;/p&gt;
&lt;p&gt;Just like in decimal: Add 6 to 5, and you need another 1 to the left,
because it’s greater than 9. That one is called the carry bit. ADDC
stands for ADD and CARRY. This instruction is for when you are treating
your bytes as “unsigned”.&lt;/p&gt;
&lt;p&gt;In preparation for explaining the next instruction, think about this.
Our example from before, 200+60=5 looks like this in hexadecimal:
C8+3C=5, CARRY=1. So the result (05h) is “wrong” in a way. But in some
other way, it only can’t stand by itself, the carry must be part of it.
So you can fix it by prefixing it with the carry: 105h = 255 + 5 =
260.&lt;/p&gt;
&lt;p&gt;The carry bit often comes into play when you have a 16-bit address,
and add a small number to the low-order byte. If the carry bit is clear,
you don’t have to do anything to the high-order byte. But if it is set,
you must increment the high-order byte by 1 to fix the address. The _U
effect does this for you. Remember, when you store a number info _U,
that number gets added to the B:O register pair. And although you are
just adding a byte to O, the result may spill over into B, or underflow
O. And _U has your back and updates B as required.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ADDV also puts the sum of A and X into A. But the value that will
be in X is somewhat less intuitive. X gets the “overflow flag”. This
instruction is strictly for when you’re treating your bytes as
signed.&lt;/p&gt;
&lt;p&gt;As we said, the carry bit is for &lt;strong&gt;fixing&lt;/strong&gt; the result
when the addition of two unsigned numbers overflows a byte.&lt;/p&gt;
&lt;p&gt;The Overflow flag is different - you won’t be able to use it directly
to fix your result. All it tells you is that the addition didn’t work,
the two operands when added are out of range. The overflow flag is an
error flag. ADDV stands for ADD and OVERFLOW.&lt;/p&gt;
&lt;p&gt;You can tell that your result is wrong, when you add two positive
numbers (A and X) but you get a negative result. And also a positive
result when you add two negative numbers is clearly wrong. And that is
what the overflow flag tells you, that one of these two cases
occurred.&lt;/p&gt;
&lt;p&gt;The interesting thing, and this does take a bit of thought, is to
realise this: when your two numbers have different signs, one positive
and a negative number, there is just no way that you can go wrong when
adding them as bytes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SUBB is more easy! It means: subtract A from X, assume that they
are both “unsigned” and store the “borrow” bit into X. What on earth is
the borrow bit? The borrow bit is 1 if the result of the subtraction is
negative.&lt;/p&gt;
&lt;p&gt;In other words, if A is greater than X. You are subtracting a larger
positive number from a smaller positive number, so the result is
negative? Boom, borrow bit is 1.&lt;/p&gt;
&lt;p&gt;Just like with ADDC and the carry bit, you &lt;strong&gt;can&lt;/strong&gt; use
the borrow bit to fix your result! It isn’t just an error flag. While
the carry meant that you have to add it to the higher-order byte of your
addition, the borrow flag means you have to &lt;strong&gt;subtract&lt;/strong&gt; it
from the higher-order byte to complete your calculation. (“You need a
larger number to subtract from and come out positive”).&lt;/p&gt;
&lt;p&gt;The borrow bit often comes into play when you have a 16-bit address,
and subtract a small number from the low-order byte. If the borrow bit
is clear, you don’t have to do anything to the high-order byte. But if
it is set, you must decrement the high-order byte by 1 to fix the
address. The _U effect does this for you. Remember, when you store a
number info _U, that number gets added to the B:O register pair. And
although you are just subtracting a byte from O (oh, not 0), the result
may spill over into B, or underflow O. And _U has your back and updates
B as required.&lt;/p&gt;
&lt;p&gt;A good way to remember that A gets subtracted from X, and not the
other way around, is the actual way in which you proceed. First you push
a number into A, then you realize you need to subtract 5 from it, so you
push the five. At this point, the number that is subtracted (the 5) is
in A, and your orginal number (the one you want to subtract from) has
been pushed into X. You then do SUBB and are left with the result in A.
As a side-effect, the borrow bit is stored in X.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="traps-1"&gt;Traps&lt;/h3&gt;
&lt;p&gt;Trap instructions are single-cycle subroutine calls. There are 32 of
them, and they all do the same thing, with just a different address.&lt;/p&gt;
&lt;p&gt;They act like instruction set extensions, because it’s completely up
to you to decide what happens, when their opcodes are executed.&lt;/p&gt;
&lt;p&gt;You may or may not know this, but below what is called machine-code,
many computers have another layer of even more primitive instructions,
called microcode. The regular machine-code instructions are built from
these microcode instructions, that tell the hardware exactly how the
(macro-)instruction must perform.&lt;/p&gt;
&lt;p&gt;So when you normally program in assembler, with each instruction, you
are actually running a little microcode program.&lt;/p&gt;
&lt;p&gt;The Myth computer is really primitive, so that it took fewer
components to build it. It’s instructions are actually microcode.&lt;/p&gt;
&lt;p&gt;And the idea with implementing trap instructions was that you then
have 32 free opcodes to implement more complex custom instructions in
the form of single-byte subroutine calls.&lt;/p&gt;
&lt;p&gt;A trap instruction opcode encodes a page number between 0 and 31.
That’s the base address for your call. The trap call always goes to
offset zero of “its” page.&lt;/p&gt;
&lt;p&gt;So if your goal is to write an instruction handler for “Trap5”, you
need to put a subroutine into page 5, starting at the first byte.&lt;/p&gt;
&lt;p&gt;A side-effect of trap calls is that they set the cpu “busy” flag for
reasons explained in the section on interrupts. As a general rule, while
you’re in page 0, or while the busy flag is on, your program can’t get
interrupted; you’re in a protected zone.&lt;/p&gt;
&lt;p&gt;In regular call-return type instructions (Call, COR, RTS), the base
pointer (B:O) is used for saving and restoring the return address. But
during trap calls and interrupts, the amenity pointer IA (Interrupt
address) is used (we explained this in the section on BOPS), and you
must use &lt;strong&gt;RTI&lt;/strong&gt; (Return from Interrupt) to return from a
trap or interrupt service routine. The advantage of this is that your
trap or interrupt is completely transparent to the caller. Using trap
instruction you can really build new, fully independent
instructions.&lt;/p&gt;
&lt;h3 id="sys"&gt;SYS&lt;/h3&gt;
&lt;p&gt;Finally!, the last little block of instructions, the SYS group. There
are only 8 of them: NOP, SSI - SSO, SCL - SCH, RTS - RTI, and COR. Let’s
go through them systematically.&lt;/p&gt;
&lt;h4 id="nop-no-operation"&gt;NOP (No operation)&lt;/h4&gt;
&lt;p&gt;Here, the computer just sits pretty and passes its turn for one
cycle. It’s opcode is zero, so this is something like the “default”
instruction.&lt;/p&gt;
&lt;h4 id="serial-port-control"&gt;Serial Port Control&lt;/h4&gt;
&lt;p&gt;The next four are for serial communication. In another document,
there is a whole section on serial communication. But the basics are
very simple.&lt;/p&gt;
&lt;p&gt;When you transmit or receive over a serial data line, which is
literally a wire, it goes just one bit at a time. So you put your data
bit of electricity - low or high level voltage, 0 or 1 - on the line, so
that the receiver can sample your data: is it a zero or a one bit?&lt;/p&gt;
&lt;h6 id="clock"&gt;Clock&lt;/h6&gt;
&lt;p&gt;Now that the received knows which kind of bit you sent, a good way
for her to let you know know that she has read your bit and is ready for
the next, is to use a second wire, the “clock” line.&lt;/p&gt;
&lt;p&gt;Just like you have put a data bit on your line, your partner now puts
a “tick” on the clock line. For example: low, then high, then low again.
This is an encoded message to you! It says: I’m done with your data
bit.&lt;/p&gt;
&lt;p&gt;Then you - the sender - detect that tick on the clock line and send
your next data bit. Rinse and repeat, that’s all there is to it.&lt;/p&gt;
&lt;p&gt;Apart from the clock line, this computer uses two data lines, one for
input, one for output.&lt;/p&gt;
&lt;h4 id="scl---sch-set-serial-clock-low-high"&gt;SCL - SCH (Set Serial Clock
Low / High)&lt;/h4&gt;
&lt;p&gt;These two instructions are for controlling the state of the clock
line. When you say SCL, the clock signal is set to “low” (0) and when
you say SCH, the clock line is set to “high” (1).&lt;/p&gt;
&lt;p&gt;Now we need to figure out a way to convert a byte into a series of
bits and vice-versa. There are two registers in this computer which do
that: SIR and SOR.&lt;/p&gt;
&lt;h4 id="ssi-shift-serial-bit-in"&gt;SSI (Shift Serial Bit In)&lt;/h4&gt;
&lt;p&gt;SIR is the Serial Input Register. One bit of it is connected to the
serial input wire, and it has what is called a shift register.&lt;/p&gt;
&lt;p&gt;Every time you say “SSI”, the bit that is on the serial input line is
shifted into SIR at the lowest bit position, pushing out its highest
order bit into nothing.&lt;/p&gt;
&lt;p&gt;So with every “SSI” instruction you execute, the SIR is slowly
filling up with bits, one by one, until 8 of them have been read in.
Since 8 bits are all that the SIR can hold, if you execute more SSIs
than that, the first bits that you shifted in will be “pushed out” of
the shift register and you would be losing them.&lt;/p&gt;
&lt;p&gt;So the sensible thing after 8 SSI instructions is to read out the SIR
and store your data byte somewhere. You can do this with a pair
instruction like SD (store SIR into D).&lt;/p&gt;
&lt;p&gt;Also, you in between issuing the SSI instructions, you should also
tick the clock-line using SCL and SCH as we said, to synchronise your
partner.&lt;/p&gt;
&lt;h4 id="sso-shift-serial-bit-out"&gt;SSO (Shift Serial Bit Out)&lt;/h4&gt;
&lt;p&gt;SSO is the Serial Output Register. Just like the SIR is converting
bits into bytes, the SOR is turning bytes into bits.&lt;/p&gt;
&lt;p&gt;For this to work, you store a byte into the SOR with a pair
instruction like DS (store D into SOR). Then you do 8 SSO instructions,
and with each of them, the current high-order bit of SOR gets shifted
out onto the serial output line, the low order places slowly filling
with zeros.&lt;/p&gt;
&lt;p&gt;Also, you in between issuing the SSI instructions, you should also
tick the clock-line using SCL and SCH as we said, to synchronise your
partner.&lt;/p&gt;
&lt;h4 id="rts-return-from-subroutine"&gt;RTS (Return from Subroutine)&lt;/h4&gt;
&lt;p&gt;This instruction reverses a call instruction. RTS increments L, so
that the caller will have his local page back. Then it puts whatever is
in B into C, and whatever is in O into PC. So the base pointer (B:O) had
better point to the exact address you need to return to, because the
next instruction is going to be fetched from there.&lt;/p&gt;
&lt;h4 id="rti-return-from-traps-and-interrupts"&gt;RTI (Return from TRAPs and
Interrupts)&lt;/h4&gt;
&lt;p&gt;RTI (Return from Interrupt) is similar in operation but “clears the
BUSY flag”.&lt;/p&gt;
&lt;p&gt;Let’s explain this in a little more detail: As long as this flag is
set, the computer will not accept an interrupt. An interrupt, when it is
accepted by the computer, is literally a “Trap0” instruction that you
can’t predict will happen!&lt;/p&gt;
&lt;p&gt;The computer - without you knowing - makes you run a call to an
“interrupt service routine”, and in the process, it sets the BUSY
flag.&lt;/p&gt;
&lt;p&gt;In contrast to RTS, trap calls, interrupts (same as Trap0!) and RTI
do &lt;strong&gt;not&lt;/strong&gt; use B:O to store the return address. And since
they don’t alter your base pointer, if the trap handler does a good job,
you will be returned to where you left off to exactly the same state you
were in and not notice a thing.&lt;/p&gt;
&lt;p&gt;Traps and RTS use the amenity pointer “IA” (Interrupt Address), to
store and retrieve the return address, instead of the base-pointer.&lt;/p&gt;
&lt;h4 id="cor"&gt;COR&lt;/h4&gt;
&lt;p&gt;COR stands for “Coroutine”. This word should always be in the plural,
since when you are using COR, you become the coroutine of another
subroutine, and you are its coroutine.&lt;/p&gt;
&lt;p&gt;COR puts whatever is in the base pointer (B:O) into C:PC, but keeps a
copy (the return address) before overwriting these registers. Then, just
before the jump, it overwrites the base pointer with the return address,
so your coroutine knows where you left off.&lt;/p&gt;
&lt;p&gt;The idea is that you both do your thing, taking turns: you jump to
the coroutine, it does a bit of work, jumps backs to you, you do a bit
of work and so on.&lt;/p&gt;
&lt;p&gt;This instruction acts very much like a call/return, but without
changing local pages. Of course you can also just use it as a jump to a
16-bit address.&lt;/p&gt;
&lt;h3 id="downloads"&gt;Downloads&lt;/h3&gt;
&lt;p&gt;Download the &lt;a
href="https://swirlsea.org/jam/sections/embedded/myth/myth.tar.gz"&gt;Source
Code&lt;/a&gt; for this project.&lt;/p&gt;

  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>AI against your Dead Cheries — Finally something useful</title>
    <id>https://swirlsea.org/permalink/jam/0x14A</id>
    <link href="https://swirlsea.org/jam/series/tech-blog/2026/260801_dead_cheries/post.html" />
    <published>2026-08-01T08:00:00Z</published>
    <updated>2026-08-01T08:00:00Z</updated>
    <author>
      <name>Michael</name>
    </author>
    <content type="html">&lt;!DOCTYPE html&gt;
&lt;html lang="EN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;AI against your Dead Cheries – Finally something useful&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;

  &lt;main&gt;

  &lt;p&gt;Posted in my &lt;a href="https://swirlsea.org/jam/series_tech-blog.html"&gt;Tech Blog series&lt;/a&gt;&lt;/p&gt;

  &lt;h5 id="resumen"&gt;Resumen&lt;/h5&gt;
&lt;p id="resumen"&gt;
Estoy hablando de algo que todos hemos experimentado: tienes una pequeña
cosa en tu proyecto que no quieres dejar atrás, bloqueándote. Puede ser
una idea favorita o una funcionalidad en la que invertiste mucho tiempo.
Una de las ventajas reales de la IA podría ser que permite (re)crear ese
feature favorito con facilidad &lt;i&gt;on demand&lt;/i&gt;, haciendo que sea más
sencillo dejarlo atrás y dejar de darle vueltas.
&lt;/p&gt;
&lt;h4 id="dead-cheries"&gt;Dead Cheries&lt;/h4&gt;
&lt;p&gt;I’m sure anyone who has written software is guilty of this: a nifty
feature, often not even essential, has taken the sufficient amount of
thought or development time for it to become endearing to you. You carry
it along the commits past its time, simply because you like the idea
behind that feature, or because you don’t want to discard all the effort
you spent on implementing it.&lt;/p&gt;
&lt;p&gt;I refer to these as &lt;em&gt;dead cheries&lt;/em&gt; because although you want
to drag them along somewhat obsessively, they are already some kind of
zombie you should bury. If you don’t, they will block fresh ideas and
can mess with your conception of what you want to achieve.&lt;/p&gt;
&lt;h4 id="ai-for-tactical-tasks-not-strategy"&gt;AI for “tactical” tasks, not
strategy&lt;/h4&gt;
&lt;p&gt;By my experience, LLMs are at their best when used with progressively
refined one-shot prompts for coding specific “tactical” features for
you. You then have a clear, verifiable goal state and a clear idea of
its implementation.&lt;/p&gt;
&lt;p&gt;For example, asking the AI to create a complex HTML table with an
associated stylesheet. You can get sidetracked by details doing that on
your own, and spend an entire afternoon tweaking. Once you do get that
perfect table, it can become a Dead Cherie and you will try and fit all
your data to it, to get a “return on investment” from it.&lt;/p&gt;
&lt;h4 id="agility-as-antidote-against-dead-cheries"&gt;Agility as antidote
against Dead Cheries&lt;/h4&gt;
&lt;p&gt;But on the other hand, if you work descriptively and formulate an LLM
prompt for this, it allows you to stay more agile, because the exact
look of the feature can be changed and verified very quickly just by
inspection, if you know your HTML and CSS. There will be much less
reason to be protective about the implementation of a particular
feature.&lt;/p&gt;

  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>Lydia — A Static Website Generator in Bash</title>
    <id>https://swirlsea.org/permalink/jam/0x5</id>
    <link href="https://swirlsea.org/jam/series/tech-blog/2026/260722_lydia/post.html" />
    <published>2026-07-28T08:00:00Z</published>
    <updated>2026-07-31T08:00:00Z</updated>
    <author>
      <name>Michael</name>
    </author>
    <content type="html">&lt;!DOCTYPE html&gt;
&lt;html lang="EN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;Lydia – A Static Website Generator in Bash&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;

  &lt;main&gt;

  &lt;p&gt;Posted in my &lt;a href="https://swirlsea.org/jam/series_tech-blog.html"&gt;Tech Blog series&lt;/a&gt;&lt;/p&gt;

  &lt;h3 id="lydia"&gt;Lydia&lt;/h3&gt;
&lt;h4 id="background"&gt;Background&lt;/h4&gt;
&lt;p&gt;There are so many static website builders. Each with catchy names
like Fimmy, Dogg or Splendish (I’m making these up). Here are some of
them listed on colorful &lt;a href="https://jamstack.org/generators/"&gt;web
cards&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The concept of a static website builder is promising. You keep
working on your writing or programming projects in the usual way, in
some folder hierarchy on your computer. Don’t worry about HTML or
anything, just use markdown text files for example. And then the site
builder transforms whatever is in your project folders into a web of
HTML pages that you can publish on a webserver, asset files and graphics
included.&lt;/p&gt;
&lt;p&gt;Most of these tools let you chose and contribute nicely designed CSS
themes for your website. They also provide a way for you to create page
templates, into which your writing gets inserted, so you don’t have to
type that much and so that your website has a uniform, polished
look.&lt;/p&gt;
&lt;p&gt;I despondedly icked out of my “try-it-out” installation of &lt;a
href="https://gohugo.io"&gt;Hugo&lt;/a&gt;, to name a particularly popular site
building tool. I know from looking into it in some detail, that Hugo is
a robust and very useful tool – it just isn’t for me in my current,
simplistic mindset.&lt;/p&gt;
&lt;p&gt;Three things happened: First, I got the impression that in chosing a
theme, I would have to provide all the content that the theme assumed I
had.&lt;/p&gt;
&lt;p&gt;Then I realized that installing a very basic theme (so I could build
my own from it) had littered my site structure with Lorem placeholder
content, with no obvious way to know where it was all stored in order to
remove it.&lt;/p&gt;
&lt;p&gt;And thirdly, I opened one of the template files, only to find a wall
of impenetrable line noise that resisted my eyes parsing or even
scanning it.&lt;/p&gt;
&lt;p&gt;So since Hugo was out, and that is the name of my grandpa, I sat
there in my Wezterm in the dumps for a while thinking about what to do.
I decided to write my own tiny, hypocritally hubristic builder named
after my grandma, Lydia.&lt;/p&gt;
&lt;h4 id="what-i-learned"&gt;What I learned&lt;/h4&gt;
&lt;p&gt;This might be the wrong order of things, but let me start-out by
writing about my personal findings.&lt;/p&gt;
&lt;p&gt;For one thing, I found a sweet spot for dealing with HTML by taking
the role of a “director” and my shell to compose it for me. It may be a
round-about way, but in doing so, I don’t have to constantly switch
gears in my mind, since I am only ever doing one thing: composing text,
either literally, or by obtaining it from a script.&lt;/p&gt;
&lt;p&gt;The way I do this in Lydia is by running a top-level build-script
that execs (thus sharing environment variables) all kinds of helper
scripts using the &lt;code&gt;source&lt;/code&gt; command. I was surprised how
tersely you can put things together. Using only here-documents and
command substitution allows you to work with a library of snippets and
“plugins” that you can curate while you go along.&lt;/p&gt;
&lt;div class="sourceCode" id="cb1"&gt;&lt;pre
class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb1-1"&gt;&lt;a href="#cb1-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# FILE build-script.sh&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-2"&gt;&lt;a href="#cb1-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb1-3"&gt;&lt;a href="#cb1-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;export&lt;/span&gt; &lt;span class="va"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;sloppy&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-4"&gt;&lt;a href="#cb1-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;lib/sh/date-header.sh&lt;/span&gt; &lt;span class="op"&gt;&amp;gt;&lt;/span&gt; website/index.html&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="sourceCode" id="cb2"&gt;&lt;pre
class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb2-1"&gt;&lt;a href="#cb2-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# FILE date-header.sh&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-2"&gt;&lt;a href="#cb2-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;/span&gt;
&lt;span id="cb2-3"&gt;&lt;a href="#cb2-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# Use here-documents to inline literal HTML&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-4"&gt;&lt;a href="#cb2-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# with command substitution to capture the output&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-5"&gt;&lt;a href="#cb2-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# of any shell command&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-6"&gt;&lt;a href="#cb2-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;cat&lt;/span&gt; &lt;span class="op"&gt;&amp;lt;&amp;lt; EOF&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-7"&gt;&lt;a href="#cb2-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;/* My great &lt;/span&gt;&lt;span class="va"&gt;$(&lt;/span&gt;&lt;span class="bu"&gt;echo&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$PROJECT_NAME&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;)&lt;/span&gt;&lt;span class="st"&gt;HTML page */&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-8"&gt;&lt;a href="#cb2-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;/* published on &lt;/span&gt;&lt;span class="va"&gt;$(&lt;/span&gt;&lt;span class="fu"&gt;date&lt;/span&gt;&lt;span class="va"&gt;)&lt;/span&gt;&lt;span class="st"&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-9"&gt;&lt;a href="#cb2-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;EOF&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Running &lt;code&gt;build-script.sh&lt;/code&gt; then creates a stub for your
&lt;code&gt;index.html&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* My great HTML page */
/* published on Wed Jul 29 08:56:57 CEST 2026 */&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using this approach, the distance towards converting your markdown
project file to an HTML fragment using Pandoc, say, and injecting it
into your page skeleton with &lt;code&gt;sed -i&lt;/code&gt; shrinks to about two
lines of code.&lt;/p&gt;
&lt;p&gt;What I find appealing about working in the shell, is the fluidity of
putting things together.&lt;/p&gt;
&lt;p&gt;Even with Perl, which was specifically designed for this, you do have
a small “setup fee” when working with command line arguments,
environment vars and file descriptors. It’s minimal, but as with any
other scripting language, you quickly get sucked into just using
&lt;em&gt;it&lt;/em&gt; instead of keeping it flat. Eclectic tool-chain
vs. swiss-army knife.&lt;/p&gt;
&lt;p&gt;Swiss-army knives and their confusing Tower of Babel. One-trick tools
don’t compete or vie for attention, by definition they are good at doing
their job, nothing more, so you can accumulate any number of them and be
good. Having two or more swiss-army knives is like having two or more
tooth brushes.&lt;/p&gt;
&lt;h4 id="awk-for-querying-text-files"&gt;Awk for querying text files&lt;/h4&gt;
&lt;p&gt;I you are reading this and haven’t heard of or never used Awk, go and
experiment some with it before reading on, honestly. Awk is a small
language that’s been with Unix since almost the beginning. It is a Posix
standard tool, so it should already be available on your system.&lt;/p&gt;
&lt;p&gt;In Lydia, I am maintaining what is essentially a csv file, but with a
pipe symbol as the field separator. This file (&lt;code&gt;pages.db&lt;/code&gt;)
serves as my website page database, with each line of the file
representing one of the pages.&lt;/p&gt;
&lt;p&gt;Using this “database”, I can generate page lists – for example by
category – using Awk. I tell it to match all records/lines where the
CATEGORY field matches the category I am interested in, and build up an
an HTML list structure with links to those pages, using the other fields
of each matching record.&lt;/p&gt;
&lt;p&gt;The database text file is generated during the build process. Each
project folder has a file called “metadata” with project-specific
information that gets included into the &lt;code&gt;pages.db&lt;/code&gt;
database.&lt;/p&gt;
&lt;h4 id="a-method-to-this-madness---jam"&gt;A method to this madness -
JAM&lt;/h4&gt;
&lt;p&gt;An acronym I’ve come across in the sphere of static site builders is
JAM, which stands for Javascript, API and Markup. The idea is that
instead of a backend like Express, your static pages can get by just
fine by injecting text converted from markdown files, and adding some
Javascript that communicates with a remote API.&lt;/p&gt;
&lt;p&gt;And Lydia does use two API endpoints from my Express website:
/permalink and /tags, although it does not use JavaScript for this.
Since both Lydia and Express are on the same machine, Express can access
the database text file I mentioned. Each row in this file, as I said,
contains data fields for a particular page.&lt;/p&gt;
&lt;p&gt;One of these fields is a unique string, a hex number called PERMAKEY.
Although in theory I could move around my project folders in the Lydia
hierarchy or change their names, the PERMAKEY will stay the same, since
it’s encoded in the &lt;code&gt;metadata&lt;/code&gt; file inside the project
folder.&lt;/p&gt;
&lt;p&gt;Now, when I hit my Express server endpoint
&lt;code&gt;/permalink/&amp;lt;PERMAKEY&amp;gt;&lt;/code&gt;, the backend redirects the
client browser to the appropriate page of the Lydia static website. It
implements this by reading the &lt;code&gt;pages.db&lt;/code&gt; text file and
building a hash, mapping permalinks to URLs.&lt;/p&gt;
&lt;p&gt;Similarly, the &lt;code&gt;/tags&lt;/code&gt; endpoint uses a hash map to map tag
strings to URLs, allowing me to render an HTML page with a list of Lydia
pages that mention this tag in their &lt;code&gt;metadata&lt;/code&gt; file. Both
Lydia and Express are behind the same NGINX reverse proxy server, and so
these API calls are not obvious to the user.&lt;/p&gt;
&lt;h4 id="pandoc"&gt;Pandoc&lt;/h4&gt;
&lt;p&gt;I am using the &lt;code&gt;pandoc&lt;/code&gt; command line tool to convert the
markdown files in my project folders to HTML. Pandoc is mature, easy to
use and rich in helpful features. You can write Lua filters, and I wrote
one that automatically rewrites my markdown image references to
responsive &lt;code&gt;srcset&lt;/code&gt; picture elements when exporting HTML.&lt;/p&gt;
&lt;p&gt;I hadn’t looked at Pandoc for a decade or so, and the markdown
extensions it provides, for example regarding syntax highlighting,
tables, Latex etc are compelling.&lt;/p&gt;
&lt;h4 id="status"&gt;Status&lt;/h4&gt;
&lt;p&gt;For the moment, I’m still testing out if Lydia is a viable tool. Most
importantly for me, it needs to be scalable and get out of the way. I’ll
write some more about it in a year or so.&lt;/p&gt;
&lt;p&gt;Currently, it has the following features.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Keep your landing pages like &lt;code&gt;index&lt;/code&gt;,
&lt;code&gt;about&lt;/code&gt;, etc. in folders:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  landings/
  └── &amp;lt;pagename&amp;gt;/
      └── &amp;lt;subpage&amp;gt;/
          └── PROJECT&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each landing page can own subpages. That means that although the
subpages don’t appear in the &lt;code&gt;pages.db&lt;/code&gt; database file, they
&lt;em&gt;are&lt;/em&gt; generated as physical HTML pages in the website which the
landing page can reference. This helps organising, since I can slice and
dice my landing pages the way I like and keep them together in the same
parent folder.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;File recurring projects into:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  series/
  └── &amp;lt;name&amp;gt;/
      └── &amp;lt;part&amp;gt;/
          └── &amp;lt;sortcode&amp;gt;_&amp;lt;projectname&amp;gt;/
              └── PROJECT&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, &lt;code&gt;part&lt;/code&gt; will most often be the running year
(&lt;code&gt;YYYY&lt;/code&gt;), but could be any ascending series. For each PROJECT
folder you add, an additional landing page is automatically created
which you then need to “link in” manually into your home page by placing
a link. Lydia can’t really know where you would want the link on your
page.&lt;/p&gt;
&lt;p&gt;Then on the landing page for the series, you will want to place a
list of all the posts in your series by capturing the HTML output from
an Awk script, provided in the &lt;code&gt;lib/sh/...&lt;/code&gt; component library
I mentioned in the opening paragraph.&lt;/p&gt;
&lt;p&gt;The script iterates over all the post records in ´pages.db´ that
match your series name and generates an unordered list of links using
the remaining fields in each matching record. The script also handles
outputting the list in chunks per year of publication with appropriate
title elements. Awk is fine for this as it does have hash maps, but the
nice thing is that it acts as a tool, not an army knife, so you could
replace the script with Python with zero fuss.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Put projects that belong to the same “section” into folders:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  section/
  └── &amp;lt;name&amp;gt;/
      └── &amp;lt;sortcode&amp;gt;_&amp;lt;projectname&amp;gt;/
          └── PROJECT&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CSS “bundling” on &lt;del&gt;steroids&lt;/del&gt; weed&lt;/p&gt;
&lt;p&gt;There is a folder called &lt;code&gt;automerge/css&lt;/code&gt;. Put your CSS
files into that folder, prefixed by &lt;code&gt;&amp;lt;number&amp;gt;_&lt;/code&gt;. The
build tool will concatenate these in numeric order to obtain the full
CSS file referenced by the HTML templates. I’ve added this for purely
mnemonic reasons, since I can now have a text file for each HTML
component or feature that I am using, allowing me to find things more
quickly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Templating based on symlinks&lt;/p&gt;
&lt;p&gt;The PROJECT folders mentioned above have an &lt;code&gt;intro.sh&lt;/code&gt;
file, a &lt;code&gt;page.md&lt;/code&gt; markdown file, and an &lt;code&gt;outro.sh&lt;/code&gt;
file. Since I am using Pandoc to generate the HTML output from the
markdown file, I have good control over the HTML I get from the
markdown. It’s relatively easy to customize the output, even from within
the markdown file itself.&lt;/p&gt;
&lt;p&gt;Now, &lt;code&gt;intro.sh&lt;/code&gt; and &lt;code&gt;outro.sh&lt;/code&gt; are there to
generate project-specific HTML content using the &lt;code&gt;lib/sh&lt;/code&gt;
components I mentioned. You may not even need this, so by default, both
files are just symbolic links to default scripts that are located in a
parent folder. Then if you do need to customise one of those, just
replace its symlink by a copy of the default file and modify
it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="testing"&gt;Testing&lt;/h4&gt;
&lt;p&gt;I’ve been using Lydia to generate a static website for my personal
website (&lt;a href="https://swirlsea.org/jam/index.html"&gt;Swirly
Tidings&lt;/a&gt;). I serve these pages using a location block in my NGINX
server.&lt;/p&gt;

  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>Calle Norte — Fotografías de Puebla</title>
    <id>https://swirlsea.org/permalink/jam/0xD</id>
    <link href="https://swirlsea.org/jam/series/fotomontajes/2026/260721_calle_norte/post.html" />
    <published>2026-07-21T08:00:00Z</published>
    <updated>2026-07-29T08:00:00Z</updated>
    <author>
      <name>Michael</name>
    </author>
    <content type="html">&lt;!DOCTYPE html&gt;
&lt;html lang="ES"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;Calle Norte – Fotografías de Puebla&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;

  &lt;main&gt;
    &lt;p&gt;Nuevo fotomontaje / Another clone-brush painting!&lt;/p&gt;

    &lt;img src="https://swirlsea.org/jam/series/fotomontajes/2026/260721_calle_norte/260721_calle_norte-400.jpg"&gt;

    &lt;p style="color: silver; font-size: smaller;"&gt;Artwork copyr. 2026 Michael Mangelsdorf&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Opciones de &lt;a href="https://picwok.com/catalog/260721_calle_norte"&gt;impresión y envío&lt;/a&gt; disponibles.
    I &lt;a href="https://picwok.com/catalog/260721_calle_norte"&gt;custom print and ship&lt;/a&gt;
    archival quality versions of these on high-grammage paper, if you are interested.&lt;/p&gt;
  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>Almond Sill — Catalog Page 1</title>
    <id>https://swirlsea.org/permalink/jam/0x10</id>
    <link href="https://swirlsea.org/jam/series/catalog/1/almond-sill/post.html" />
    <published>2026-07-24T08:00:00Z</published>
    <updated>2026-07-24T08:00:00Z</updated>
    <author>
      <name>Michael</name>
    </author>
    <content type="html">&lt;!DOCTYPE html&gt;
&lt;html lang="EN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;Almond Sill&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;main&gt;
    &lt;p&gt;Nuevo fotomontaje del catalogo!&lt;/p&gt;
    
    &lt;p&gt;Another clone-brush painting in my
     &lt;a href="/jam/series_catalog.html"&gt;Picwok Catalog&lt;/a&gt;&lt;/p&gt;

    &lt;img src="https://swirlsea.org/jam/series/catalog/1/almond-sill/almond-sill-400.jpg"&gt; 

    &lt;p style="color: silver; font-size: smaller;"&gt;Artwork copyr. 2026 Michael Mangelsdorf&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Opciones de &lt;a href="https://picwok.com/catalog/almond-sill"&gt;impresión y envío&lt;/a&gt; disponibles.
    I &lt;a href="https://picwok.com/catalog/almond-sill"&gt;custom print and ship&lt;/a&gt;
    archival quality versions of these on high-grammage paper, if you are interested.&lt;/p&gt;
  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>August — Catalog Page 1</title>
    <id>https://swirlsea.org/permalink/jam/0x11</id>
    <link href="https://swirlsea.org/jam/series/catalog/1/august/post.html" />
    <published>2026-07-24T08:00:00Z</published>
    <updated>2026-07-24T08:00:00Z</updated>
    <author>
      <name>Michael</name>
    </author>
    <content type="html">&lt;!DOCTYPE html&gt;
&lt;html lang="EN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;August&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;main&gt;
    &lt;p&gt;Nuevo fotomontaje del catalogo!&lt;/p&gt;
    
    &lt;p&gt;Another clone-brush painting in my
     &lt;a href="/jam/series_catalog.html"&gt;Picwok Catalog&lt;/a&gt;&lt;/p&gt;

    &lt;img src="https://swirlsea.org/jam/series/catalog/1/august/august-400.jpg"&gt; 

    &lt;p style="color: silver; font-size: smaller;"&gt;Artwork copyr. 2026 Michael Mangelsdorf&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Opciones de &lt;a href="https://picwok.com/catalog/august"&gt;impresión y envío&lt;/a&gt; disponibles.
    I &lt;a href="https://picwok.com/catalog/august"&gt;custom print and ship&lt;/a&gt;
    archival quality versions of these on high-grammage paper, if you are interested.&lt;/p&gt;
  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>Convo Crash — Catalog Page 1</title>
    <id>https://swirlsea.org/permalink/jam/0x12</id>
    <link href="https://swirlsea.org/jam/series/catalog/1/convo-crash/post.html" />
    <published>2026-07-24T08:00:00Z</published>
    <updated>2026-07-24T08:00:00Z</updated>
    <author>
      <name>Michael</name>
    </author>
    <content type="html">&lt;!DOCTYPE html&gt;
&lt;html lang="EN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;Convo Crash&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;main&gt;
    &lt;p&gt;Nuevo fotomontaje del catalogo!&lt;/p&gt;
    
    &lt;p&gt;Another clone-brush painting in my
     &lt;a href="/jam/series_catalog.html"&gt;Picwok Catalog&lt;/a&gt;&lt;/p&gt;

    &lt;img src="https://swirlsea.org/jam/series/catalog/1/convo-crash/convo-crash-400.jpg"&gt; 

    &lt;p style="color: silver; font-size: smaller;"&gt;Artwork copyr. 2026 Michael Mangelsdorf&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Opciones de &lt;a href="https://picwok.com/catalog/convo-crash"&gt;impresión y envío&lt;/a&gt; disponibles.
    I &lt;a href="https://picwok.com/catalog/convo-crash"&gt;custom print and ship&lt;/a&gt;
    archival quality versions of these on high-grammage paper, if you are interested.&lt;/p&gt;
  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>Fishing The Apocalypse Green — Catalog Page 1</title>
    <id>https://swirlsea.org/permalink/jam/0x13</id>
    <link href="https://swirlsea.org/jam/series/catalog/1/fishing-the-apocalypse-green/post.html" />
    <published>2026-07-24T08:00:00Z</published>
    <updated>2026-07-24T08:00:00Z</updated>
    <author>
      <name>Michael</name>
    </author>
    <content type="html">&lt;!DOCTYPE html&gt;
&lt;html lang="EN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;Fishing The Apocalypse Green&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;main&gt;
    &lt;p&gt;Nuevo fotomontaje del catalogo!&lt;/p&gt;
    
    &lt;p&gt;Another clone-brush painting in my
     &lt;a href="/jam/series_catalog.html"&gt;Picwok Catalog&lt;/a&gt;&lt;/p&gt;

    &lt;img src="https://swirlsea.org/jam/series/catalog/1/fishing-the-apocalypse-green/fishing-the-apocalypse-green-400.jpg"&gt; 

    &lt;p style="color: silver; font-size: smaller;"&gt;Artwork copyr. 2026 Michael Mangelsdorf&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Opciones de &lt;a href="https://picwok.com/catalog/fishing-the-apocalypse-green"&gt;impresión y envío&lt;/a&gt; disponibles.
    I &lt;a href="https://picwok.com/catalog/fishing-the-apocalypse-green"&gt;custom print and ship&lt;/a&gt;
    archival quality versions of these on high-grammage paper, if you are interested.&lt;/p&gt;
  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>Fishing The Apocalypse Red — Catalog Page 1</title>
    <id>https://swirlsea.org/permalink/jam/0x14</id>
    <link href="https://swirlsea.org/jam/series/catalog/1/fishing-the-apocalypse-red/post.html" />
    <published>2026-07-24T08:00:00Z</published>
    <updated>2026-07-24T08:00:00Z</updated>
    <author>
      <name>Michael</name>
    </author>
    <content type="html">&lt;!DOCTYPE html&gt;
&lt;html lang="EN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;Fishing The Apocalypse Red&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;main&gt;
    &lt;p&gt;Nuevo fotomontaje del catalogo!&lt;/p&gt;
    
    &lt;p&gt;Another clone-brush painting in my
     &lt;a href="/jam/series_catalog.html"&gt;Picwok Catalog&lt;/a&gt;&lt;/p&gt;

    &lt;img src="https://swirlsea.org/jam/series/catalog/1/fishing-the-apocalypse-red/fishing-the-apocalypse-red-400.jpg"&gt; 

    &lt;p style="color: silver; font-size: smaller;"&gt;Artwork copyr. 2026 Michael Mangelsdorf&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Opciones de &lt;a href="https://picwok.com/catalog/fishing-the-apocalypse-red"&gt;impresión y envío&lt;/a&gt; disponibles.
    I &lt;a href="https://picwok.com/catalog/fishing-the-apocalypse-red"&gt;custom print and ship&lt;/a&gt;
    archival quality versions of these on high-grammage paper, if you are interested.&lt;/p&gt;
  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>Ginkgo Cita — Catalog Page 1</title>
    <id>https://swirlsea.org/permalink/jam/0x15</id>
    <link href="https://swirlsea.org/jam/series/catalog/1/ginkgo-cita/post.html" />
    <published>2026-07-24T08:00:00Z</published>
    <updated>2026-07-24T08:00:00Z</updated>
    <author>
      <name>Michael</name>
    </author>
    <content type="html">&lt;!DOCTYPE html&gt;
&lt;html lang="EN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;Ginkgo Cita&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;main&gt;
    &lt;p&gt;Nuevo fotomontaje del catalogo!&lt;/p&gt;
    
    &lt;p&gt;Another clone-brush painting in my
     &lt;a href="/jam/series_catalog.html"&gt;Picwok Catalog&lt;/a&gt;&lt;/p&gt;

    &lt;img src="https://swirlsea.org/jam/series/catalog/1/ginkgo-cita/ginkgo-cita-400.jpg"&gt; 

    &lt;p style="color: silver; font-size: smaller;"&gt;Artwork copyr. 2026 Michael Mangelsdorf&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Opciones de &lt;a href="https://picwok.com/catalog/ginkgo-cita"&gt;impresión y envío&lt;/a&gt; disponibles.
    I &lt;a href="https://picwok.com/catalog/ginkgo-cita"&gt;custom print and ship&lt;/a&gt;
    archival quality versions of these on high-grammage paper, if you are interested.&lt;/p&gt;
  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>Hermanas Al Mar Luna — Catalog Page 1</title>
    <id>https://swirlsea.org/permalink/jam/0x16</id>
    <link href="https://swirlsea.org/jam/series/catalog/1/hermanas-al-mar-luna/post.html" />
    <published>2026-07-24T08:00:00Z</published>
    <updated>2026-07-24T08:00:00Z</updated>
    <author>
      <name>Michael</name>
    </author>
    <content type="html">&lt;!DOCTYPE html&gt;
&lt;html lang="EN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;Hermanas Al Mar Luna&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;main&gt;
    &lt;p&gt;Nuevo fotomontaje del catalogo!&lt;/p&gt;
    
    &lt;p&gt;Another clone-brush painting in my
     &lt;a href="/jam/series_catalog.html"&gt;Picwok Catalog&lt;/a&gt;&lt;/p&gt;

    &lt;img src="https://swirlsea.org/jam/series/catalog/1/hermanas-al-mar-luna/hermanas-al-mar-luna-400.jpg"&gt; 

    &lt;p style="color: silver; font-size: smaller;"&gt;Artwork copyr. 2026 Michael Mangelsdorf&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Opciones de &lt;a href="https://picwok.com/catalog/hermanas-al-mar-luna"&gt;impresión y envío&lt;/a&gt; disponibles.
    I &lt;a href="https://picwok.com/catalog/hermanas-al-mar-luna"&gt;custom print and ship&lt;/a&gt;
    archival quality versions of these on high-grammage paper, if you are interested.&lt;/p&gt;
  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>Hermanas Al Mar Portrait — Catalog Page 1</title>
    <id>https://swirlsea.org/permalink/jam/0x17</id>
    <link href="https://swirlsea.org/jam/series/catalog/1/hermanas-al-mar-portrait/post.html" />
    <published>2026-07-24T08:00:00Z</published>
    <updated>2026-07-24T08:00:00Z</updated>
    <author>
      <name>Michael</name>
    </author>
    <content type="html">&lt;!DOCTYPE html&gt;
&lt;html lang="EN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;Hermanas Al Mar Portrait&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;main&gt;
    &lt;p&gt;Nuevo fotomontaje del catalogo!&lt;/p&gt;
    
    &lt;p&gt;Another clone-brush painting in my
     &lt;a href="/jam/series_catalog.html"&gt;Picwok Catalog&lt;/a&gt;&lt;/p&gt;

    &lt;img src="https://swirlsea.org/jam/series/catalog/1/hermanas-al-mar-portrait/hermanas-al-mar-portrait-400.jpg"&gt; 

    &lt;p style="color: silver; font-size: smaller;"&gt;Artwork copyr. 2026 Michael Mangelsdorf&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Opciones de &lt;a href="https://picwok.com/catalog/hermanas-al-mar-portrait"&gt;impresión y envío&lt;/a&gt; disponibles.
    I &lt;a href="https://picwok.com/catalog/hermanas-al-mar-portrait"&gt;custom print and ship&lt;/a&gt;
    archival quality versions of these on high-grammage paper, if you are interested.&lt;/p&gt;
  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>Hierro — Catalog Page 1</title>
    <id>https://swirlsea.org/permalink/jam/0x18</id>
    <link href="https://swirlsea.org/jam/series/catalog/1/hierro/post.html" />
    <published>2026-07-24T08:00:00Z</published>
    <updated>2026-07-24T08:00:00Z</updated>
    <author>
      <name>Michael</name>
    </author>
    <content type="html">&lt;!DOCTYPE html&gt;
&lt;html lang="EN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;Hierro&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;main&gt;
    &lt;p&gt;Nuevo fotomontaje del catalogo!&lt;/p&gt;
    
    &lt;p&gt;Another clone-brush painting in my
     &lt;a href="/jam/series_catalog.html"&gt;Picwok Catalog&lt;/a&gt;&lt;/p&gt;

    &lt;img src="https://swirlsea.org/jam/series/catalog/1/hierro/hierro-400.jpg"&gt; 

    &lt;p style="color: silver; font-size: smaller;"&gt;Artwork copyr. 2026 Michael Mangelsdorf&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Opciones de &lt;a href="https://picwok.com/catalog/hierro"&gt;impresión y envío&lt;/a&gt; disponibles.
    I &lt;a href="https://picwok.com/catalog/hierro"&gt;custom print and ship&lt;/a&gt;
    archival quality versions of these on high-grammage paper, if you are interested.&lt;/p&gt;
  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>Hueman — Catalog Page 1</title>
    <id>https://swirlsea.org/permalink/jam/0x19</id>
    <link href="https://swirlsea.org/jam/series/catalog/1/hueman/post.html" />
    <published>2026-07-24T08:00:00Z</published>
    <updated>2026-07-24T08:00:00Z</updated>
    <author>
      <name>Michael</name>
    </author>
    <content type="html">&lt;!DOCTYPE html&gt;
&lt;html lang="EN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;Hueman&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;main&gt;
    &lt;p&gt;Nuevo fotomontaje del catalogo!&lt;/p&gt;
    
    &lt;p&gt;Another clone-brush painting in my
     &lt;a href="/jam/series_catalog.html"&gt;Picwok Catalog&lt;/a&gt;&lt;/p&gt;

    &lt;img src="https://swirlsea.org/jam/series/catalog/1/hueman/hueman-400.jpg"&gt; 

    &lt;p style="color: silver; font-size: smaller;"&gt;Artwork copyr. 2026 Michael Mangelsdorf&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Opciones de &lt;a href="https://picwok.com/catalog/hueman"&gt;impresión y envío&lt;/a&gt; disponibles.
    I &lt;a href="https://picwok.com/catalog/hueman"&gt;custom print and ship&lt;/a&gt;
    archival quality versions of these on high-grammage paper, if you are interested.&lt;/p&gt;
  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>Imaginary Friends — Catalog Page 1</title>
    <id>https://swirlsea.org/permalink/jam/0x1A</id>
    <link href="https://swirlsea.org/jam/series/catalog/1/imaginary-friends/post.html" />
    <published>2026-07-24T08:00:00Z</published>
    <updated>2026-07-24T08:00:00Z</updated>
    <author>
      <name>Michael</name>
    </author>
    <content type="html">&lt;!DOCTYPE html&gt;
&lt;html lang="EN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;Imaginary Friends&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;main&gt;
    &lt;p&gt;Nuevo fotomontaje del catalogo!&lt;/p&gt;
    
    &lt;p&gt;Another clone-brush painting in my
     &lt;a href="/jam/series_catalog.html"&gt;Picwok Catalog&lt;/a&gt;&lt;/p&gt;

    &lt;img src="https://swirlsea.org/jam/series/catalog/1/imaginary-friends/imaginary-friends-400.jpg"&gt; 

    &lt;p style="color: silver; font-size: smaller;"&gt;Artwork copyr. 2026 Michael Mangelsdorf&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Opciones de &lt;a href="https://picwok.com/catalog/imaginary-friends"&gt;impresión y envío&lt;/a&gt; disponibles.
    I &lt;a href="https://picwok.com/catalog/imaginary-friends"&gt;custom print and ship&lt;/a&gt;
    archival quality versions of these on high-grammage paper, if you are interested.&lt;/p&gt;
  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>Info Warfare — Catalog Page 1</title>
    <id>https://swirlsea.org/permalink/jam/0x1B</id>
    <link href="https://swirlsea.org/jam/series/catalog/1/info-warfare/post.html" />
    <published>2026-07-24T08:00:00Z</published>
    <updated>2026-07-24T08:00:00Z</updated>
    <author>
      <name>Michael</name>
    </author>
    <content type="html">&lt;!DOCTYPE html&gt;
&lt;html lang="EN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;Info Warfare&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;main&gt;
    &lt;p&gt;Nuevo fotomontaje del catalogo!&lt;/p&gt;
    
    &lt;p&gt;Another clone-brush painting in my
     &lt;a href="/jam/series_catalog.html"&gt;Picwok Catalog&lt;/a&gt;&lt;/p&gt;

    &lt;img src="https://swirlsea.org/jam/series/catalog/1/info-warfare/info-warfare-400.jpg"&gt; 

    &lt;p style="color: silver; font-size: smaller;"&gt;Artwork copyr. 2026 Michael Mangelsdorf&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Opciones de &lt;a href="https://picwok.com/catalog/info-warfare"&gt;impresión y envío&lt;/a&gt; disponibles.
    I &lt;a href="https://picwok.com/catalog/info-warfare"&gt;custom print and ship&lt;/a&gt;
    archival quality versions of these on high-grammage paper, if you are interested.&lt;/p&gt;
  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>Lakehouse — Catalog Page 1</title>
    <id>https://swirlsea.org/permalink/jam/0x1C</id>
    <link href="https://swirlsea.org/jam/series/catalog/1/lakehouse/post.html" />
    <published>2026-07-24T08:00:00Z</published>
    <updated>2026-07-24T08:00:00Z</updated>
    <author>
      <name>Michael</name>
    </author>
    <content type="html">&lt;!DOCTYPE html&gt;
&lt;html lang="EN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;Lakehouse&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;main&gt;
    &lt;p&gt;Nuevo fotomontaje del catalogo!&lt;/p&gt;
    
    &lt;p&gt;Another clone-brush painting in my
     &lt;a href="/jam/series_catalog.html"&gt;Picwok Catalog&lt;/a&gt;&lt;/p&gt;

    &lt;img src="https://swirlsea.org/jam/series/catalog/1/lakehouse/lakehouse-400.jpg"&gt; 

    &lt;p style="color: silver; font-size: smaller;"&gt;Artwork copyr. 2026 Michael Mangelsdorf&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Opciones de &lt;a href="https://picwok.com/catalog/lakehouse"&gt;impresión y envío&lt;/a&gt; disponibles.
    I &lt;a href="https://picwok.com/catalog/lakehouse"&gt;custom print and ship&lt;/a&gt;
    archival quality versions of these on high-grammage paper, if you are interested.&lt;/p&gt;
  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>Pyre — Catalog Page 1</title>
    <id>https://swirlsea.org/permalink/jam/0x1D</id>
    <link href="https://swirlsea.org/jam/series/catalog/1/pyre/post.html" />
    <published>2026-07-24T08:00:00Z</published>
    <updated>2026-07-24T08:00:00Z</updated>
    <author>
      <name>Michael</name>
    </author>
    <content type="html">&lt;!DOCTYPE html&gt;
&lt;html lang="EN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;Pyre&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;main&gt;
    &lt;p&gt;Nuevo fotomontaje del catalogo!&lt;/p&gt;
    
    &lt;p&gt;Another clone-brush painting in my
     &lt;a href="/jam/series_catalog.html"&gt;Picwok Catalog&lt;/a&gt;&lt;/p&gt;

    &lt;img src="https://swirlsea.org/jam/series/catalog/1/pyre/pyre-400.jpg"&gt; 

    &lt;p style="color: silver; font-size: smaller;"&gt;Artwork copyr. 2026 Michael Mangelsdorf&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Opciones de &lt;a href="https://picwok.com/catalog/pyre"&gt;impresión y envío&lt;/a&gt; disponibles.
    I &lt;a href="https://picwok.com/catalog/pyre"&gt;custom print and ship&lt;/a&gt;
    archival quality versions of these on high-grammage paper, if you are interested.&lt;/p&gt;
  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>Salish Bay Martian — Catalog Page 1</title>
    <id>https://swirlsea.org/permalink/jam/0x1E</id>
    <link href="https://swirlsea.org/jam/series/catalog/1/salish-bay-martian/post.html" />
    <published>2026-07-24T08:00:00Z</published>
    <updated>2026-07-24T08:00:00Z</updated>
    <author>
      <name>Michael</name>
    </author>
    <content type="html">&lt;!DOCTYPE html&gt;
&lt;html lang="EN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;Salish Bay Martian&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;main&gt;
    &lt;p&gt;Nuevo fotomontaje del catalogo!&lt;/p&gt;
    
    &lt;p&gt;Another clone-brush painting in my
     &lt;a href="/jam/series_catalog.html"&gt;Picwok Catalog&lt;/a&gt;&lt;/p&gt;

    &lt;img src="https://swirlsea.org/jam/series/catalog/1/salish-bay-martian/salish-bay-martian-400.jpg"&gt; 

    &lt;p style="color: silver; font-size: smaller;"&gt;Artwork copyr. 2026 Michael Mangelsdorf&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Opciones de &lt;a href="https://picwok.com/catalog/salish-bay-martian"&gt;impresión y envío&lt;/a&gt; disponibles.
    I &lt;a href="https://picwok.com/catalog/salish-bay-martian"&gt;custom print and ship&lt;/a&gt;
    archival quality versions of these on high-grammage paper, if you are interested.&lt;/p&gt;
  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>Shedding Red — Catalog Page 1</title>
    <id>https://swirlsea.org/permalink/jam/0x1F</id>
    <link href="https://swirlsea.org/jam/series/catalog/1/shedding-red/post.html" />
    <published>2026-07-24T08:00:00Z</published>
    <updated>2026-07-24T08:00:00Z</updated>
    <author>
      <name>Michael</name>
    </author>
    <content type="html">&lt;!DOCTYPE html&gt;
&lt;html lang="EN"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;Shedding Red&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;main&gt;
    &lt;p&gt;Nuevo fotomontaje del catalogo!&lt;/p&gt;
    
    &lt;p&gt;Another clone-brush painting in my
     &lt;a href="/jam/series_catalog.html"&gt;Picwok Catalog&lt;/a&gt;&lt;/p&gt;

    &lt;img src="https://swirlsea.org/jam/series/catalog/1/shedding-red/shedding-red-400.jpg"&gt; 

    &lt;p style="color: silver; font-size: smaller;"&gt;Artwork copyr. 2026 Michael Mangelsdorf&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Opciones de &lt;a href="https://picwok.com/catalog/shedding-red"&gt;impresión y envío&lt;/a&gt; disponibles.
    I &lt;a href="https://picwok.com/catalog/shedding-red"&gt;custom print and ship&lt;/a&gt;
    archival quality versions of these on high-grammage paper, if you are interested.&lt;/p&gt;
  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</content>
  </entry>
</feed>