Files
SplitBit-Emulator/Tests/manifest
T
AnachronautandClaude Opus 5 5fd995aa62 Breakpoints: SWI osBreak, and s refuses a read only bank
A breakpoint that shows every register as the program had them, waits for a
key, and carries on.

NOTHING IS OVERWRITTEN, and that is the design rather than a shortcut. A
breakpoint poked into a running program has to replace an instruction, and
putting that instruction back in order to continue is the same act as
disarming the breakpoint; firing a second time would mean stepping over the
restored instruction and putting the breakpoint back behind it, and this
machine cannot step a single instruction. SWI is two bytes, dispatches
through a vector, and its frame already holds the address after it, so RETI
resumes at the next instruction with nothing to restore and nothing to
re-arm. It fires every time it is reached.

The price is that a breakpoint is part of the program: a build with them in
has different addresses from a build without. That is the bargain every
machine with a break instruction makes.

Every value shown comes out of the frame rather than the registers, because
by the time the handler runs the registers are the handler's. Apps/Break.asm
stops twice so that the second stop is checked as well as the first.

Also here, found by the test that came with it: the monitor's s wrote into
whichever bank was selected, and bank 2 is the controller's own table,
published read only. Writing to it was refused, and a refusal nobody catches
stops the machine - so selecting the bank table to look at it and then typing
s killed the session. bankPresent now keeps the whole flags byte and s
declines. The recorded output of cosmosMonitor had contained that crash,
having been blessed without being read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 22:09:21 -04:00

373 lines
27 KiB
Plaintext

# SplitBit test manifest
# Written by Anachronaut
#
# One test per line, fields separated by '|'. Blank lines and lines starting
# with '#' are ignored.
#
# name | source | mode | stdin | limit | disk
#
# source is relative to Programs/. Everything assembles from there with
# Libraries/ on the include path, and the binary is written into Tests/build.
#
# Modes:
# run Assemble, execute, compare all output against Tests/expected/<name>.out
# assemble Assemble only. For library files that have no entry point to run
# xfail Assembly is expected to fail. Records a known breakage so that
# fixing one is noticed, and so an accidental new one is too
#
# limit is a cycle count, for programs that never halt on their own. It is passed
# to the emulator as --cycles, which bounds the run by cycles rather than by wall
# clock time and so keeps the recorded output identical from one run to the next.
#
# disk names a disk image to attach, made fresh inside Tests/build for every run so that
# nothing a test writes can be seen by the next one. Leave it off for a machine with no
# disk, which is most of them. A trailing :ro attaches it write protected.
#
# A disk name with a directory in it, such as disks/sbfs.img, is one of the images that
# Tests/makedisks.sh builds with SplitDisk before the run. Those are used as they stand,
# so a test can read a filesystem written by the other implementation of the format.
# Every program is run with --fast, since the emulated cycle rate has no bearing
# on what a program prints.
# ---- Programs that halt on their own ----
hello | hello.asm | run | - | -
printHello | printHello.asm | run | - | -
8bitFibonacci | Fibonacci/8bitFibonacci.asm | run | - | -
16bitFibonacci | Fibonacci/16bitFibonacci.asm | run | - | -
32bitFibonacci | Fibonacci/32bitFibonacci.asm | run | - | -
8bitSieve | primeSieve/8bitSieve.asm | run | - | -
16bitSegmentedSieve | primeSieve/16bitSegmentedSieve.asm | run | - | -
# The four pointer rewrite. It emits exactly the same primes as the line above, which
# is the whole point of keeping both: the pair is a direct before and after.
16bitSegmentedSieveModern | primeSieve/16bitSegmentedSieveModern.asm | run | - | -
mathTest | testPrograms/mathTest.asm | run | - | -
printTest | testPrograms/printTest.asm | run | - | -
int16print | Libraries/int16print.asm | run | - | -
# ---- The multiple Data Pointer behaviour, which nothing else exercises ----
dataPointerTest | testPrograms/dataPointerTest.asm | run | - | -
twoPointerCopy | testPrograms/twoPointerCopy.asm | run | - | -
pointerTableTest | testPrograms/pointerTableTest.asm | run | - | -
staticTableTest | testPrograms/staticTableTest.asm | run | - | -
dispatchTest | testPrograms/dispatchTest.asm | run | - | -
# ---- Branching both ways round ----
# Each of the four conditions is checked taken and not taken, so a branch that always
# went the same way would be caught rather than looking right half the time.
branchTest | testPrograms/branchTest.asm | run | - | -
# ---- Moving an ALU result back into an operand register ----
moveQTest | testPrograms/moveQTest.asm | run | - | -
# ---- The memory controller ----
# Reading and writing Program Memory, which the instruction set deliberately cannot do,
# and the bank table that says what is reachable.
controllerReadTest | testPrograms/controllerReadTest.asm | run | - | -
controllerWriteTest | testPrograms/controllerWriteTest.asm | run | - | -
# Block transfers: between banks, within one, overlapping, and one that is refused.
blitTest | testPrograms/blitTest.asm | run | - | -
# Reading a filesystem that the host tool wrote. The two are separate implementations of
# one written format, so this is where any drift between them would show.
sbfsReadTest | testPrograms/sbfsReadTest.asm | run | - | - | disks/sbfs.img
# Walking the directory rather than searching it, which is what listing a disk needs.
# Twelve files is more than the eight an entry block holds, so the walk has to cross into
# the second directory block to see them all.
sbfsWalkTest | testPrograms/sbfsWalkTest.asm | run | - | - | disks/sbfs.img
# Writing a filesystem, then reading back what was written. The disk starts with a file
# on it, so allocation has to find room rather than start at the beginning.
sbfsWriteTest | testPrograms/sbfsWriteTest.asm | run | - | - | disks/write.img
# Deleting, renaming, and saving over something already there, which is what any tool that
# edits a document needs and what none of the tests above touch. The interesting line is
# the third: saving a longer version moves the file, because files here are contiguous and
# do not grow. The last two lines are a rename and a delete having actually happened.
sbfsEditTest | testPrograms/sbfsEditTest.asm | run | - | - | disks/edit.img
# Loading a program off a disk and running it. Everything below this line existed before
# the loader did; the only new part is the sixteen bytes on the front of a loadable
# program saying where it goes.
loader | loader.asm | run | - | - | disks/load.img
# Storage. The image is made fresh for each run, so block 3 starts as zeroes.
diskTest | testPrograms/diskTest.asm | run | - | - | disk.img
# The same disk attached write protected, so the device bars writes rather than software.
diskProtectTest | testPrograms/diskProtectTest.asm | run | - | - | protected.img:ro
# A program that loads a program: blits code into Program Memory, installs a vector at
# run time, and calls it. If the vector install ever silently failed, the SWI would fault
# with "no handler" rather than printing, so this test cannot pass by accident.
loaderTest | testPrograms/loaderTest.asm | run | - | -
# Giving a bank number to a device's memory, which is what an OS does at boot.
registerBankTest | testPrograms/registerBankTest.asm | run | - | -
# The fence: raised, walked into, clipped by a blit, read through, and lowered again.
fenceTest | testPrograms/fenceTest.asm | run | - | -
# Meant to fault: a bank knows how big it is.
bankBoundsTest | testPrograms/bankBoundsTest.asm | run | - | -
# ---- A device saying no ----
# Refusing is not interrupting: it stops the instruction that asked, rather than asking
# for attention later. The device on port 0x11 refuses everything so this path stays
# tested before the memory controller becomes its only real user.
refusalTest | testPrograms/refusalTest.asm | run | - | -
# The same, with nothing installed to catch it. Meant to fault.
refusalFaultTest | testPrograms/refusalFaultTest.asm | run | - | -
# ---- Asking the machine what it is made of ----
registryTest | testPrograms/registryTest.asm | run | - | -
# ---- Moving the cursor along ----
paddingTest | testPrograms/paddingTest.asm | run | - | -
# ---- Finding the Stack, and moving it ----
stackPointerTest | testPrograms/stackPointerTest.asm | run | - | -
# MVDS, the dangerous one: a system taking its Stack back from a program that stopped
# without unwinding. The sequence is the one the Programming Manual prints under "The
# Stack Pointer, Set By Hand", so the manual's example cannot quietly stop working.
stackReclaimTest | testPrograms/stackReclaimTest.asm | run | - | -
# ---- The Interrupt Flag ----
# Nothing reads the flag yet. This checks that setting and clearing it leaves
# the registers and the Carry Flag it shares a byte with untouched.
interruptFlagTest | testPrograms/interruptFlagTest.asm | run | - | -
# ---- Vectors laid down by the assembler ----
# These are the interrupt path end to end. Until #Vectors existed, none of them could
# be written as a source file at all, because nothing could install a handler.
vectorTest | testPrograms/vectorTest.asm | run | - | -
# Numbers written down and numbers handed out, in one program. A vector number only matters
# where two separately assembled programs have to agree about it, so those are pinned by
# hand from a range the assembler never allocates, and everything else is drawn from above
# it. This pins both ends of that range and leaves a third to the assembler, then calls all
# three by name: a number going astray shows up as the wrong word rather than as silence.
pinnedVectorTest | testPrograms/pinnedVectorTest.asm | run | - | -
# A service handing something back, which RETI otherwise makes impossible: it restores every
# register from the frame, so a handler that worked something out has no way to say so. The
# answer is written into the frame over the saved register. All three cases are here - a
# service that says nothing and leaves Q exactly as it found it, one that answers in Q, and
# one that answers in DP3 - because the first is what makes the other two safe to rely on.
serviceReturnTest | testPrograms/serviceReturnTest.asm | run | - | -
deviceTest | testPrograms/deviceTest.asm | run | - | -
faultResumeTest | testPrograms/faultResumeTest.asm | run | - | -
# The worked example out of the Assembler Manual, so the manual cannot go stale.
interruptExample | testPrograms/interruptExample.asm | run | - | -
# ---- Holding a device off, and then letting it through ----
# Faults on purpose. The fault is the proof that the line was answered, and where it
# lands in the output is the proof of when.
maskTest | testPrograms/maskTest.asm | run | - | -
# ---- Dispatching through a vector with nothing in it ----
# Also meant to fault. Until the assembler can lay a vector table down, every entry
# reads as zero, so this is the only half of the interrupt path a source file can
# reach on its own. The round trip through a handler is covered by hand built
# binaries until #Vectors arrives.
swiFaultTest | testPrograms/swiFaultTest.asm | run | - | -
# ---- Meeting a byte that is not an instruction ----
# This one is meant to fault. It checks the CPU stops, says what it found and
# where, and exits non zero, rather than stepping over it and carrying on.
faultTest | testPrograms/faultTest.asm | run | - | -
# ---- The modern console library ----
# Every routine in console.asm, called with the cases that are easy to get wrong: a zero,
# the largest thing that fits, and a leading zero that should not print. The reading half
# is given a line longer than its buffer, so truncation and the swallowing of the rest
# are shown rather than assumed.
consoleTest | testPrograms/consoleTest.asm | run | consoleTest.in | -
# The console's status and control ports. Input here is a file rather than a terminal, so
# key mode has no terminal to change and only the mode bit moves: that is deliberate, since
# a program has to behave the same either way and a test needing a terminal could not run.
# What it pins down is that READY is clear at the end of input while ENDED is set, so a
# loop reading while READY stops on its own instead of taking imaginary bytes forever.
consoleModeTest | testPrograms/consoleModeTest.asm | run | consoleModeTest.in | -
# The console interrupting instead of being asked. The main program never touches the
# console at all, so every byte in that output was delivered by a handler. It also pins
# down the two things that make the feature usable rather than merely present: the end of
# input raises the line once, so an interrupt-driven program is told when to stop, and a
# handler that does not read the byte is not called again, so nothing storms.
consoleInterruptTest | testPrograms/consoleInterruptTest.asm | run | consoleInterruptTest.in | -
# Picking a typed line apart, which is how the shell understands anything. Includes a
# string beginning with a zero: the assembler strips the quotes before deciding what a
# token is, so such a string looked like a malformed literal and was refused.
textTest | testPrograms/textTest.asm | run | - | -
# Strings that spell instructions. The quotes are gone by the time a token is classified, so
# "ADD" in a Data Segment was assembled as the ADD instruction and refused with a message
# about a mistake nobody had made. Mnemonics match without regard to case, so "or" and "and"
# were caught too - ordinary words to want in a message.
stringMnemonic | testPrograms/stringMnemonic.asm | run | - | -
# ---- CosmOS ----
# The system and its shell, driven by a script of commands. This is the first thing that
# uses the machine as a machine rather than exercising one part of it: it boots, mounts a
# disk written by the host tool, reads lines, and walks a directory to answer 'dir'.
cosmos | CosmOS/Source/cosmos.asm | run | cosmos.in | - | disks/sbfs.img
# The same with nothing attached. A shell that only works with a disk in the drive is not
# finished, so the empty machine is a case in its own right rather than an accident.
cosmosNoDisk | CosmOS/Source/cosmos.asm | run | cosmosNoDisk.in | -
# Loading a program and running it, which is the whole machine at once: the filesystem
# finds it, the controller writes it into Program Memory, the vector table carries its
# requests back to the system, and MVDS takes the Stack back when it stops. Every way
# load can refuse is tried first, and run is asked for twice, so the Stack being reclaimed
# rather than merely abandoned is what makes the second one work.
cosmosRun | CosmOS/Source/cosmos.asm | run | cosmosRun.in | - | disks/cosmos.img
# The monitor, which is part of the shell rather than a program: a mode you go into and stay
# in. The targets are chosen to be stable - a loaded program's code and data, and the bank
# table - rather than the system's own code, which would churn whenever any library changed.
#
# Looking at the bank table is worth having on its own. It is the machine describing itself,
# and it shows the disk buffer that sbfsMount registered as bank 3 at boot.
#
# The last part is what the mode is FOR: a program typed in as bytes, run with g, and the
# prompt that comes back is the monitor's own. A program giving the machine back lands where
# it was started from, so looking at something and running it do not interrupt each other.
#
# It also asks to write into bank 2, the controller's own table, which is published read
# only. That used to stop the machine, and the recorded output of this test contained the
# crash without anybody noticing, which is what blessing a result without reading it buys.
cosmosMonitor | CosmOS/Source/cosmos.asm | run | cosmosMonitor.in | - | disks/cosmos.img
# The original hello.asm, brought over as an application. It is not much of a program,
# but it is the one that talks to the hardware directly: it writes to port 0x00 instead
# of calling osPrintString, so it is the case where a program reaches past the system and
# the system has to get control back anyway. run is asked for twice for the same reason
# it is in cosmosRun, and here it also says the Stack comes back from a program that never
# entered a service handler at all.
cosmosHello | CosmOS/Source/cosmos.asm | run | cosmosHello.in | - | disks/cosmos.img
# Life, which is the one program that had no end state to reach. It stops when the board
# settles, and the glider does settle: it crosses the field, hits the dead border, and
# collapses into a block at generation 54. NO CYCLE LIMIT ON PURPOSE. Whether it stops on
# its own is the whole point of the port, and a limit would answer that question for it.
#
# NOTHING IS TYPED AFTER run, and that is load bearing rather than tidy. Life polls the
# console between generations, and a byte sitting in the pipe is a byte waiting on the
# console as far as the machine is concerned - exactly as typing ahead at a terminal
# would be. An "exit" on the next line stops it at generation 1 and gets eaten. The shell
# ends the run by reaching the end of input instead, the way cosmosNoDisk does.
cosmosLife | CosmOS/Source/cosmos.asm | run | cosmosLife.in | - | disks/cosmos.img
# The other half of that: a key IS waiting, so it stops at once instead of at generation
# 54. The two together are what say the poll is reading the console rather than always
# answering the same way.
cosmosLifeKey | CosmOS/Source/cosmos.asm | run | cosmosLifeKey.in | - | disks/cosmos.img
# A whole game of Snake, played by a script. Life is watched; this is steered, and it is
# the first program on this machine that reads the console without ever waiting for it.
# One key is taken per frame, so twelve bytes in the pipe are twelve moves rather than
# twelve moves at once: six turn it down the board and six take it left onto the food. So
# what is recorded is a turn, a meal, a longer snake, and then a wall.
cosmosSnake | CosmOS/Source/cosmos.asm | run | cosmosSnake.in | - | disks/cosmos.img
# A loaded program that brings a vector of its own, which nothing could do until the
# loadable format could carry one. It is run TWICE on purpose: installing has to be exactly
# undone by removing, and the way that fails is asymmetrically, so the second run is the one
# that catches it. The dump afterwards is the proof that the table was put back - the
# console's slot at FE00 is zero again, and CosmOS's own disk handler further along is
# untouched by a program having installed over the top of it.
cosmosKeys | CosmOS/Source/cosmos.asm | run | cosmosKeys.in | - | disks/cosmos.img
# The shell taking things off a disk and calling them something else, which is the last of
# CosmOS's original four verbs to be built and the first time anything has changed a disk
# from the shell. Its own image, because it changes what is on it: a fixture named with a
# directory is used as it stands, so a test that writes to a shared one changes what every
# test after it sees. The refusals are here too - deleting what is not there, renaming with
# a name missing, and renaming something to a name already taken.
cosmosFiles | CosmOS/Source/cosmos.asm | run | cosmosFiles.in | - | disks/files.img
# A program being told what it is for. Nothing loaded before this could be told anything,
# so every app did the same thing however it was started - which is fine for a demo and no
# use at all to a tool that edits a named document. Run three times: with nothing, with a
# name, and with several words, since what arrives is the rest of the line rather than a
# list and it is the program's business what to make of it.
cosmosSay | CosmOS/Source/cosmos.asm | run | cosmosSay.in | - | disks/cosmos.img
# A program stopping itself to be looked at. Two breakpoints, so what is checked is not only
# that one fires but that the SECOND one does - which is the whole difference between this
# design and one that overwrites an instruction, since an overwritten instruction has to be
# put back to continue and putting it back is the same act as disarming the breakpoint.
# Every value shown comes out of the interrupt frame rather than the registers, because by
# the time the handler runs the registers are the handler's.
cosmosBreak | CosmOS/Source/cosmos.asm | run | cosmosBreak.in | - | disks/cosmos.img
# The editor, which is the first program on this machine that makes a file a person typed:
# every byte on every other image here was put there by the host tool. It is run twice in
# one session, and that is the test rather than a flourish - the second run reads back what
# the first one wrote, so the whole path is checked at once: read a name from the argument,
# split a file into lines, edit them, build a file back out of them, and save it over
# something that was already there and is now a different size.
cosmosEdit | CosmOS/Source/cosmos.asm | run | cosmosEdit.in | - | disks/editor.img
# The file services, exercised by a program that includes NOTHING but the service names: no
# filesystem library, no console library. It writes a file, reads it back, says how long it
# was, renames it and deletes it, in 645 bytes - against the editor's 4941, which does less
# with files and carries the filesystem inside it. That difference is the whole case for the
# service layer, and this is where it is checked rather than argued.
cosmosServices | CosmOS/Source/cosmos.asm | run | cosmosFiles2.in | - | disks/services.img
# The programs CosmOS loads, checked on their own so that a failure here reads as "the app
# does not assemble" rather than as a broken disk image.
app-greet | CosmOS/Apps/greet.asm | assemble | - | -
app-hello | CosmOS/Apps/hello.asm | assemble | - | -
app-Life | CosmOS/Apps/Life.asm | assemble | - | -
app-Snake | CosmOS/Apps/Snake.asm | assemble | - | -
app-Keys | CosmOS/Apps/Keys.asm | assemble | - | -
app-Say | CosmOS/Apps/Say.asm | assemble | - | -
app-Break | CosmOS/Apps/Break.asm | assemble | - | -
app-Edit | CosmOS/Apps/Edit.asm | assemble | - | -
app-Files | CosmOS/Apps/Files.asm | assemble | - | -
# ---- Programs driven by console input ----
inputTest | inputTest.asm | run | inputTest.in | -
inputTestOld | testPrograms/inputTest.asm | run | inputTest.in | -
replCalculator | replCalculator.asm | run | replCalculator.in | -
# ---- Programs that run forever by design, bounded by a cycle count ----
# 3,000,000 cycles is about fourteen generations of the glider, which puts
# evolveBoard and its pointer juggling through its paces many times over.
16x16Life | gameOfLife/16x16Life.asm | run | - | 3000000
# The four pointer rewrite, on the same budget so the two can be compared directly.
# Note that this cannot show a speed difference: frameDelay is 255 by 255 and swamps
# the simulation, so both versions render the same fourteen generations and produce
# identical bytes. What it checks is that the rewrite still evolves the board the same
# way, which is what a regression test is for.
16x16LifeModern | gameOfLife/16x16LifeModern.asm | run | - | 3000000
# ---- Libraries: no entry point, so only check that they assemble ----
# The CosmOS libraries assemble on their own, unlike print.asm below, which cannot: it
# begins with a branch to a label only the including program defines. That difference is
# the point of the rewrite, so both halves of it are recorded here.
lib-console | CosmOS/Source/console.asm | assemble | - | -
lib-text | CosmOS/Source/text.asm | assemble | - | -
lib-sbfs | CosmOS/Source/sbfs.asm | assemble | - | -
lib-int8 | Libraries/int8.asm | assemble | - | -
lib-int16 | Libraries/int16.asm | assemble | - | -
lib-int32 | Libraries/int32.asm | assemble | - | -
lib-math | Libraries/math.asm | assemble | - | -
# ---- Deliberately broken, to check the assembler still diagnoses them ----
# These are not programs anyone meant to run. Each one contains a single mistake
# that the assembler used to accept quietly, and is here so that it cannot start
# being accepted quietly again.
diagDuplicateLabel | testPrograms/diagnostics/duplicateLabel.asm | xfail | - | -
diagBareInclude | testPrograms/diagnostics/bareInclude.asm | xfail | - | -
diagUnknownVector | testPrograms/diagnostics/unknownVector.asm | xfail | - | -
diagDuplicateVector | testPrograms/diagnostics/duplicateVector.asm | xfail | - | -
# The two mistakes that pinning a number makes possible. Numbers the assembler hands out
# cannot collide; numbers a person writes down can, and can also be written outside the
# range set aside for them.
# A string in the Program Segment, which used to assemble to nothing at all and say so to
# nobody. Strings only have a case for the Data buffer, so one aimed anywhere else fell
# through and vanished.
diagStringInProgram | testPrograms/diagnostics/stringInProgram.asm | xfail | - | -
diagPinnedRange | testPrograms/diagnostics/pinnedVectorRange.asm | xfail | - | -
diagPinnedTaken | testPrograms/diagnostics/pinnedVectorTaken.asm | xfail | - | -
diagBareSWI | testPrograms/diagnostics/bareSWI.asm | xfail | - | -
diagAlignOutside | testPrograms/diagnostics/alignOutside.asm | xfail | - | -
diagBareAlign | testPrograms/diagnostics/bareAlign.asm | xfail | - | -
# One segment based and the other forgotten, which is how the ported Fib-8 put its data
# on top of the console's variables. It assembled and it ran, so nothing but the assembler
# was ever going to catch it.
diagUnbasedSegment | testPrograms/diagnostics/unbasedSegment.asm | xfail | - | -
# ---- Known breakages, recorded rather than ignored ----
# print.asm branches to 'start', which only the including program defines.
lib-print | Libraries/print.asm | xfail | - | -
# These three call print.asm routines but have no #Include line at all. They are
# only ever pulled in by printTest.asm, so they are not standalone programs.
printDecimalTest | testPrograms/printDecimalTest.asm | xfail | - | -
printDigitTest | testPrograms/printDigitTest.asm | xfail | - | -
printHexTest | testPrograms/printHexTest.asm | xfail | - | -
# shiftTest calls printDecimal, which print.asm does not have. It looks like the
# routine was renamed and this caller was never updated.
shiftTest | testPrograms/shiftTest.asm | xfail | - | -