Give the Programming Manual a title, and send the boot image format away

Last of the four. What was left after the reorder was a document whose first
heading was "General Description" doing a part title's job without being
one, and a section called "Input and Output In the Emulator" that held two
console ports, a worked program, and a file format.

  A title and an opening that says what this document is FOR, and what the
  other two are for, so a reader who wants the operating system or the
  language knows immediately they are in the wrong file.

  "General Description" is "The Machine", which matches the three part
  headings the reorder gave the rest.

  "Input and Output In the Emulator" is "Making It Print Something", which
  is what the section is: port 0, and the shortest program that uses it.

THE BOOT IMAGE FORMAT MOVES TO THE ASSEMBLER MANUAL, beside the loadable
program format, for the reason SBEX went there: it is a thing the assembler
WRITES. It is fair that the emulator reads them too - both tools speak it,
the way SplitDisk and sbfs.asm both speak the filesystem - but only one of
them makes one.

And it is called a boot image now, in the text as well as the heading. That
is what this project has been calling these files for a while; the manual
was still saying "binary", which now means either kind of output file and so
means neither.

A CHECK THAT GOT BETTER BY BEING SPLIT. The hello world program and the hex
dump of it were both in the Programming Manual, and docs.sh compared them
with each other and with the assembler. The program stays with the machine,
where the reorder put it just after the instruction list; the dump goes with
the format it demonstrates. So the check now settles THREE things against
each other: what one manual prints, what the other prints, and what the
assembler actually makes. Verified both ways - a wrong byte in the dump, and
the anchor renamed.

The manual is 692 lines and four parts. It was 1,116 lines and nineteen flat
sections when this started.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-08-21 14:44:11 -04:00
co-authored by Claude Opus 5
parent e594f44cce
commit 306b4dce92
3 changed files with 67 additions and 47 deletions
+46
View File
@@ -207,6 +207,52 @@ A program that genuinely wants a segment at the bottom of memory says so:
`#Base` is the one directive that takes zero. `#Align` and `#Reserve` are counts, and a count of nothing is a typo, so they still require at least one. `#Base` is the one directive that takes zero. `#Align` and `#Reserve` are counts, and a count of nothing is a typo, so they still require at least one.
## The Boot Image Format:
A boot image is what the machine starts from: the Program and Data segments in one file,
with nothing to say where they go, because they go at the bottom of each memory. It is what
the assembler writes when a program does not say where it lives, and what the emulator is
given on the command line.
Every value in it is stored most significant byte first, which is the same order the CPU reads addresses out of Program Memory.
A file begins with a nine byte header:
| Offset | Size | Field |
| -- | -- | -- |
| 0 | 4 | The characters `SPBT`, so that a file which is not a boot image is recognised as such straight away. |
| 4 | 1 | The format version. This document describes version 1. |
| 5 | 4 | Required feature flags. |
The feature flags are how a boot image states that it needs something the base machine does not provide. An emulator that cannot provide everything an image asks for refuses to run it, rather than running it and going quietly wrong. No feature bits are defined yet, so the field is currently zero in every image.
After the header come the segments. The Program Segment comes first and then the Data Segment, each beginning with a three character marker, `PRG` or `DAT`, followed by a two byte length. The system loads each memory with the bytes that follow, in sequence, starting from address 0x0000.
A third segment may follow them, marked `VEC`, holding the vectors a program named in its Vector Segment. It is four bytes an entry: two saying where in Program Memory the vector sits, and two saying where its handler is. A program that named no vectors has no such segment, and a file that simply ends after its Data Segment is one written before vectors existed. Either way the reader treats the end of the file as an empty table, which is why adding this cost no format version and left every image already written still loadable.
Here is the hello world program from the Programming Manual, assembled and dumped as hex:
```
53 50 42 54 01 00 00 00 00 50 52 47 00 11 42 00 12 00 0c d1 00 40 00 10 00 00 26 0a d1 00 ff 44
41 54 00 0e 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21 00
```
Taken apart:
| Bytes | Meaning |
| -- | -- |
| `53 50 42 54` | `SPBT` |
| `01` | Format version 1 |
| `00 00 00 00` | No features required |
| `50 52 47` | `PRG` |
| `00 11` | The Program Segment is 17 bytes long |
| `42 00 12 00 0c d1 00 40 00 10 00 00 26 0a d1 00 ff` | The Program Segment |
| `44 41 54` | `DAT` |
| `00 0e` | The Data Segment is 14 bytes long |
| `48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21 00` | The Data Segment |
The `42 00` at the start of the Program Segment is worth a look: `42` is LDA, and the `00` after it is the Data Pointer selector the assembler filled in, because the program did not name one.
## Loading A Program From A Disk: ## Loading A Program From A Disk:
A program that was not the one the machine booted from carries sixteen bytes in front of it saying where it belongs. A program that was not the one the machine booted from carries sixteen bytes in front of it saying where it belongs.
+13 -44
View File
@@ -1,4 +1,14 @@
# General Description: # The SplitBit Programming Manual
This describes the machine: what the CPU has, what its instructions do, how something gets
its attention, and what a SplitBit is made of. Everything here is true of any SplitBit,
whatever happens to be running on it.
The **Assembler Manual** describes the language you write for it and the two file formats
it produces. `Programs/CosmOS/README.md` describes CosmOS, which is one operating system
that runs on this machine rather than part of the machine itself.
# The Machine
SplitBit is a small 8 bit CPU. SplitBit is a small 8 bit CPU.
It is a Harvard Architecture machine with a separate 64k memory space for its Program and another for its Data. It is a Harvard Architecture machine with a separate 64k memory space for its Program and another for its Data.
@@ -157,7 +167,7 @@ LDD and STD are how a program follows an address it has stored, rather than one
# Making It Do Something # Making It Do Something
## Input and Output In the Emulator: ## Making It Print Something:
Port 0 is the console: writing sends a byte to standard output and reading takes one from standard input. It answers on two more ports than that, which are described under The Console and which a program can ignore entirely if all it wants is to read and write bytes. Everything else this machine has is listed under Devices, and a program that wants to know what is actually there asks the bus registry rather than assuming. Port 0 is the console: writing sends a byte to standard output and reading takes one from standard input. It answers on two more ports than that, which are described under The Console and which a program can ignore entirely if all it wants is to read and write bytes. Everything else this machine has is listed under Devices, and a program that wants to know what is actually there asks the bus registry rather than assuming.
@@ -187,47 +197,6 @@ End:
Nothing in that program names a Data Pointer, so all of it runs through Data Pointer 0. Nothing in that program names a Data Pointer, so all of it runs through Data Pointer 0.
### Structure of a SplitBit Binary File:
The Program and Data values are both stored in a single file for loading into the system. Every multi byte value in the format is stored most significant byte first, which is the same order the CPU reads addresses out of Program Memory.
A file begins with a nine byte header:
| Offset | Size | Field |
| -- | -- | -- |
| 0 | 4 | The characters `SPBT`, so that a file which is not a SplitBit binary is recognised as such straight away. |
| 4 | 1 | The format version. This document describes version 1. |
| 5 | 4 | Required feature flags. |
The feature flags are how a binary states that it needs something the base machine does not provide. An emulator that cannot provide everything a binary asks for refuses to run it, rather than running it and going quietly wrong. No feature bits are defined yet, so the field is currently zero in every binary.
After the header come the segments. The Program Segment comes first and then the Data Segment, each beginning with a three character marker, `PRG` or `DAT`, followed by a two byte length. The system loads each memory with the bytes that follow, in sequence, starting from address 0x0000.
A third segment may follow them, marked `VEC`, holding the vectors a program named in its Vector Segment. It is four bytes an entry: two saying where in Program Memory the vector sits, and two saying where its handler is. A program that named no vectors has no such segment, and a file that simply ends after its Data Segment is one written before vectors existed. Either way the reader treats the end of the file as an empty table, which is why adding this cost no format version and left every binary already written still loadable.
Here is the hello world program above, assembled and dumped as hex:
```
53 50 42 54 01 00 00 00 00 50 52 47 00 11 42 00 12 00 0c d1 00 40 00 10 00 00 26 0a d1 00 ff 44
41 54 00 0e 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21 00
```
Taken apart:
| Bytes | Meaning |
| -- | -- |
| `53 50 42 54` | `SPBT` |
| `01` | Format version 1 |
| `00 00 00 00` | No features required |
| `50 52 47` | `PRG` |
| `00 11` | The Program Segment is 17 bytes long |
| `42 00 12 00 0c d1 00 40 00 10 00 00 26 0a d1 00 ff` | The Program Segment |
| `44 41 54` | `DAT` |
| `00 0e` | The Data Segment is 14 bytes long |
| `48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21 00` | The Data Segment |
The `42 00` at the start of the Program Segment is worth a look: `42` is LDA, and the `00` after it is the Data Pointer selector the assembler filled in, because the program did not name one.
## The Console: ## The Console:
Port 0x00 is the oldest thing on this machine and it has not changed: writing sends a byte out, reading takes one in and waits until there is one. Every program ever written for SplitBit uses it that way and still does. What is new is that a program can say what it wants a keypress to mean, can ask whether a read would have to wait, and can arrange to be told when a byte arrives instead of having to ask at all. Port 0x00 is the oldest thing on this machine and it has not changed: writing sends a byte out, reading takes one in and waits until there is one. Every program ever written for SplitBit uses it that way and still does. What is new is that a program can say what it wants a keypress to mean, can ask whether a read would have to wait, and can arrange to be told when a byte arrives instead of having to ask at all.
@@ -645,7 +614,7 @@ So the fence protects code from a mistaken loader. It is not general memory prot
### Loading A Program: ### Loading A Program:
Everything the controller does adds up to one thing a SplitBit machine could not do before: run code that was not in the binary it started from. Everything the controller does adds up to one thing a SplitBit machine could not do before: run code that was not in the boot image it started from.
The sequence is short. Put the bytes of a routine somewhere, blit them into Program Memory, write their address into a vector, and call it. The sequence is short. Put the bytes of a routine somewhere, blit them into Program Memory, write their address into a vector, and call it.
+8 -3
View File
@@ -351,17 +351,22 @@ import tempfile
# says what it could not find; these two were the exception, and a rename here produced an # says what it could not find; these two were the exception, and a rename here produced an
# IndexError and a traceback instead of a sentence. That is a worse answer than a stale # IndexError and a traceback instead of a sentence. That is a worse answer than a stale
# manual, because whoever reads it learns nothing about which heading moved. # manual, because whoever reads it learns nothing about which heading moved.
# THE TWO HALVES ARE IN DIFFERENT MANUALS NOW, and that makes this a better check than it
# was. The program belongs with the machine, where it arrives just after the instruction
# list; the hex dump belongs with the boot image format it is an example of, which is the
# assembler's business. So this settles three things against each other at once: what the
# Programming Manual prints, what the Assembler Manual prints, and what the assembler does.
exampleAnchor = "### Example Program: Hello World" exampleAnchor = "### Example Program: Hello World"
dumpAnchor = "assembled and dumped as hex:" dumpAnchor = "assembled and dumped as hex:"
if exampleAnchor not in pm: if exampleAnchor not in pm:
problems.append("the Programming Manual has lost its \"Example Program: Hello World\"" problems.append("the Programming Manual has lost its \"Example Program: Hello World\""
" heading, so the worked example cannot be found") " heading, so the worked example cannot be found")
elif dumpAnchor not in pm: elif dumpAnchor not in am:
problems.append("the Programming Manual no longer says \"%s\" before the hex dump, so" problems.append("the Assembler Manual no longer says \"%s\" before the hex dump, so"
" there is nothing to compare the worked example against" % dumpAnchor) " there is nothing to compare the worked example against" % dumpAnchor)
else: else:
source = pm.split(exampleAnchor)[1].split("```")[1] source = pm.split(exampleAnchor)[1].split("```")[1]
claimed = pm.split(dumpAnchor)[1].split("```")[1].split() claimed = am.split(dumpAnchor)[1].split("```")[1].split()
with tempfile.TemporaryDirectory() as work: with tempfile.TemporaryDirectory() as work:
asm = os.path.join(work, "hello.asm") asm = os.path.join(work, "hello.asm")
binary = os.path.join(work, "hello.bin") binary = os.path.join(work, "hello.bin")