Rung 2: the machine starts itself off a disk

Stage one exists and works. It is 330 bytes of program and everything it
knows is a thing that will be true forever: which port the disk is on, that
a SplitBit disk begins with its own name, and where two numbers sit in that
first block. Not what a file is, not what a directory is, not that SBFS has
versions. It reads the live boot slot into Program Memory, jumps to the
first byte, and prints one character and halts if there is nothing there.

It is an ordinary boot image for now, so the whole chain runs on machinery
that already exists and the emulator has not been touched. Nothing about it
changes when it moves into ROM except who puts it in memory.

SplitDisk gained "boot" to write a slot and "bootslot" to choose one, kept
apart on purpose: writing a slot and starting from it are different
decisions, and joining them would make every write a commitment. A slot is
always written WHOLE, because one still holding the tail of what was there
before is one whose contents depend on its history, and stage one reads all
of it without knowing where the file stopped.

Three recorded tests, and the pair is the point: two disks differing only
in which slot the superblock names, with payloads that say different
things. One prints "booted" and the other does not, so this is a test of
CHOOSING a slot rather than a test that some bytes were read. The third
boots a disk with no boot area and gets the one character a ROM has room
for. Eight more host checks, including that a slot is padded whole.

Two things worth recording. The first draft used #Align to put the scratch
buffer at 0x8000 and produced a 33K file - thirty two kilobytes of zeroes
in something meant to be a ROM. It is an address, not storage, which is
exactly what the assembler's own scratch map exists to say.

And SplitLint caught the second in code written an hour after the baseline
that catches it. In the blit set-up, RSTA writes a source address of zero
and then RSTA writes a bank number of zero - two unrelated quantities that
are equal by accident, in the most safety critical file in the repository.
It is marked with a reason rather than removed.
This commit is contained in:
Anachronaut
2026-08-26 23:31:04 -04:00
parent 612bd1b97c
commit d07b23f90b
11 changed files with 509 additions and 0 deletions
+33
View File
@@ -0,0 +1,33 @@
; slotTest.asm
; Something small to put in a boot slot, so that the chain can be proved end to end.
;
; NO DATA SEGMENT, and that is not tidiness - it is the shape of what stage one can do.
; Stage one reads raw blocks into Program Memory and jumps to the first byte. It places no
; data, because it has no way to know where a payload's data ends and its code begins
; without knowing a format, and knowing a format is the thing ROM must do as little of as
; possible. So a payload either carries no initialised data or arranges its own.
;
; Which is why this prints with INIA and OUTA rather than from a string. The real stage two
; needs a Data Segment, and how it gets one is an open question written up beside this.
;
; Written by Anachronaut
#Program
#Base 0xC000
here:
INIA 0x62 ; "b"
OUTA 0x00
INIA 0x6F ; "o"
OUTA 0x00
INIA 0x6F ; splitlint[redundant-assignment]: a second "o", spelled out like the rest
OUTA 0x00
INIA 0x74 ; "t"
OUTA 0x00
INIA 0x65 ; "e"
OUTA 0x00
INIA 0x64 ; "d"
OUTA 0x00
INIA 0x0A
OUTA 0x00
HALT
+263
View File
@@ -0,0 +1,263 @@
; stage1.asm
; The first thing the machine runs. Reads the boot area off the disk and jumps into it.
;
; THIS IS THE PART THAT ONE DAY CANNOT BE CHANGED. It is written to go in ROM, so
; everything it knows has to be a thing that is true forever: which port the disk is on,
; that a SplitBit disk begins with its own name, and where two numbers sit in the block
; that name is in. It does not know what a file is, what a directory is, or that SBFS has
; versions. All of that lives in the boot area, on the disk, where it can be replaced.
;
; The test to apply to any line added here is the only test that matters for ROM: am I
; certain this is right forever? A loader that could find /System/cosmos.bin by name would
; be friendlier and would freeze the filesystem format in silicon.
;
; It is an ordinary boot image for now, so it can be run and tested with everything that
; already exists. Nothing about it changes when it moves into ROM except who puts it in
; memory.
;
; Written by Anachronaut
#Program
; ---- What is known forever ----
;
; Disk: 0x20 block high, 0x21 block low, 0x22 command, 0x23 status
; Controller: 0xE0 source bank, 0xE1/0xE2 source, 0xE3 dest bank, 0xE4/0xE5 dest,
; 0xE6/0xE7 count, 0xE8 command
; Banks: 0 Program Memory, 1 Data Memory, 3 the disk's buffer once registered
start:
; The disk's buffer becomes bank 3. Memory a device brings is reachable only through the
; controller, so this is what makes the block readable at all.
INIA 0d3
OUTA 0xE3
INIA 0x20
OUTA 0xE2
INIA 0x03
OUTA 0xE8
; Block 0, the superblock.
RSTA
SETD.0 BlockHigh
STA.0
SETD.0 BlockLow
STA.0
RCAL readBlock
BNQ bootFailed
; "SBFS", or there is nothing here to boot from. Four bytes, compared where they landed.
SETD.2 ScratchAt
LDD.0.2
SETD.2 DiskMagic
INIA 0d4
SETD.1 Counter
STA.1
magicLoop:
LDA.0
LDB.2
XOR
BNQ bootFailed
INCD.0
INCD.2
SETD.1 Counter
LDA.1
DECA
STA.1
BNA magicLoop
; Two numbers, at fixed offsets in the block whose name has just been checked: how many
; blocks a boot slot holds, and which of the two slots to start from.
SETD.2 ScratchAt
LDD.0.2
DPUP.0 0d14
LDA.0
BNA bootFailed ; A high byte means a slot larger than this will ever read.
INCD.0
LDA.0
BRA bootFailed ; No boot area at all, so this disk cannot be started.
SETD.1 SlotBlocks
STA.1
SETD.2 ScratchAt
LDD.0.2
DPUP.0 0d16
LDA.0
; The first block of the live slot. Slot 0 begins at block 1 and slot 1 begins a whole
; slot later, so the only arithmetic is an addition and there is nothing to multiply.
BRA slotZero
SETD.0 SlotBlocks
LDA.0
INCA
BRI slotFound
slotZero:
INIA 0d1
slotFound:
SETD.0 BlockLow
STA.0
RSTA
SETD.0 BlockHigh
STA.0
; And where it goes. Stage two lives above everything the system will occupy, so that
; loading the system does not walk over the loader while it is still running.
SETD.0 StageHigh
INIA 0xC0
STA.0
readLoop:
RCAL readBlock
BNQ bootFailed
; The block, out of the disk's buffer and into Program Memory where it will be run.
INIA 0d3
OUTA 0xE0
RSTA
OUTA 0xE1
OUTA 0xE2
RSTA ; splitlint[redundant-assignment]: a bank number, not the address above
OUTA 0xE3 ; DestBank: Program Memory.
SETD.0 StageHigh
LDA.0
OUTA 0xE4
RSTA
OUTA 0xE5
INIA 0x01
OUTA 0xE6
RSTA
OUTA 0xE7 ; A whole block.
INIA 0x01
OUTA 0xE8
; On to the next block, and the next page of Program Memory to put it in.
SETD.0 StageHigh
LDA.0
INCA
STA.0
SETD.0 BlockLow
LDA.0
INCA
STA.0
BNA blockStepped
SETD.0 BlockHigh
LDA.0
INCA
STA.0
blockStepped:
SETD.0 SlotBlocks
LDA.0
DECA
STA.0
BNA readLoop
; Into it. Nothing is checked about what was read, because there is nothing here that
; could check it: what a valid stage two looks like is stage two's business, and a ROM
; that knew would be a ROM that could be wrong about it later.
SETD.0 StageStart
LDD.1.0
BRD.1
; ---- Reading the block the two block registers name ----
;
; RCAL rather than CALL because what it hands back is Q, and an ordinary call would put
; back the registers this leaves its answer in.
readBlock:
SETD.0 BlockHigh
LDA.0
OUTA 0x20
SETD.0 BlockLow
LDA.0
OUTA 0x21
INIA 0x01
OUTA 0x22 ; Read.
waitDisk:
INA 0x23
INIB 0x01
AND
BRQ readDone ; The busy bit is down, so there is nothing to wait for.
WAIT
BRI waitDisk
readDone:
; The error bit, which is the whole of what can go wrong down here.
INIB 0x02
AND
BNQ readBad
; And into Data Memory, where the CPU can look at it.
INIA 0d3
OUTA 0xE0
RSTA
OUTA 0xE1
OUTA 0xE2
INIA 0d1
OUTA 0xE3 ; DestBank: Data Memory.
INIA 0x80
OUTA 0xE4
RSTA
OUTA 0xE5
INIA 0x01
OUTA 0xE6
RSTA
OUTA 0xE7
INIA 0x01
OUTA 0xE8
RSTA
RSTB
CCF
ADD ; Q is zero: the block is in Scratch.
RRET
readBad:
RSTA
INIB 0d1
CCF
ADD
RRET
; ---- When there is nothing to start ----
;
; One character and a stop. A ROM has no room for an explanation and nowhere to put one:
; the console is the only thing it can be sure of, and even that only in the sense that
; writing to a port nobody is listening to costs nothing.
bootFailed:
INIA 0x3F ; "?"
OUTA 0x00
INIA 0x0A
OUTA 0x00
HALT
#Data
DiskMagic:
"SBFS"
BlockHigh:
0x00
BlockLow:
0x00
SlotBlocks:
0x00
Counter:
0x00
; Where stage two is being written, a page at a time, and where it begins. The high byte is
; stepped as the blocks go by; the low byte is always zero, because a block is a page.
StageHigh:
0xC0
StageStart:
0xC0 0x00
; Where the superblock, and then each block of the boot area, lands on its way past.
;
; AN ADDRESS RATHER THAN STORAGE. Reserving it, or aligning to it, would put thirty two
; kilobytes of zeroes into a file that is going to be a ROM - which is what the assembler's
; own scratch map exists to avoid, for the same reason. Two bytes here say where; nothing
; carries what.
ScratchAt:
0x80 0x00
#Vectors
Boot start