Files
SplitBit-Emulator/Tests/manifest
T
AnachronautandClaude Opus 5 d388cd3122 Give the machine a sound device
Four channels on ports 0x40 to 0x4F, each one a whole soundThing voice:
two oscillators, two envelopes, a filter and the routing between them. A
channel keeps its patch between notes, so a program sets an instrument up
once and then plays it.

Six ports rather than forty, because a voice has around forty settings and
four of them would spend more than half the port space on one device.
There is a selector and a value instead: say which channel, say which
setting, write it. That is three writes to change a setting and two to
play a note, which is the right way round - patches are loaded, notes are
played in an inner loop.

Samples come from the machine's clock and not the host's: 48,000 a second
of emulated time, worked out in whole numbers so it never drifts. A
million cycles is exactly 48,000 samples on any host at any speed, which
is what makes a sound something a test can compare. --sound writes them
out, the way --screen writes a picture, for the same reason: the suite has
no speaker.

Tests/sound.sh is 22 checks and found three real defects the first time it
ran, all the same shape - a synthesizer written for a patch editor, wired
up as hardware and inheriting the editor's assumptions:

  - Only one voice had an oscillator switched on, so three of the four
    channels could not make a sound whatever was written to them.
  - That voice's oscillator arrived at full gain and every other one
    arrived at nothing, an asymmetry with no reason behind it.
  - A note with no sustain is silent but not over, so the obvious way to
    wait for a sound to finish waits for ever.

The first two are fixed by the device defining its own power-on state
rather than inheriting synthInit's: every channel arrives able to make a
sound, so writing a note number is the whole of playing a note. The third
was already written into the manual as advice, an hour before the check
existed. The check disagreed with the documentation and the check was
right; the manual now says the one rule, which is that a note sounds until
the gate is dropped.

Programs/Examples/tune.asm plays eight notes, taking its tempo from the
screen's frame interrupt because that is the only regular beat this
machine has. It spends 99.8% of its cycles asleep in WAIT.

Voyager has no speaker yet - this is the device and its tests. Playing the
samples out of the window is the next commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-29 20:59:17 -04:00

759 lines
55 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 | keys
#
# 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, and the assembler's diagnostic is recorded
# and compared like any other output. Catches a known breakage that stops
# being detected, an accidental new one, and a message that changes
# without anybody meaning it to
#
# 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.
#
# keys names a file in Tests/input to be fed to the console as a KEYBOARD rather than as
# standard input, and the difference is the point. Standard input reaches a console that
# believes a terminal is handling the line editing, which is true when one is. A keyboard
# reaches a console that knows it has to do the editing itself - gathering a line, rubbing
# out a backspace, handing it over only at Return - which is what happens behind a window,
# where there is no terminal to do any of it.
#
# 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 | Examples/hello.asm | run | - | -
printHello | Examples/printHello.asm | run | - | -
8bitFibonacci | Examples/Fibonacci/8bitFibonacci.asm | run | - | -
16bitFibonacci | Examples/Fibonacci/16bitFibonacci.asm | run | - | -
32bitFibonacci | Examples/Fibonacci/32bitFibonacci.asm | run | - | -
colours | Examples/colours.asm | run | - | -
# frames waits for the screen sixty times, which is one second at the emulated rate. It is
# here because the count is exact: a frame is counted in the machine's own cycles, so the
# same program sees the same sixty however fast the host really went.
frames | Examples/frames.asm | run | - | -
# picture draws a whole 320 by 200 bitmap and prints nothing, so what is recorded is only
# that it ran and what it cost. Tests/video.sh is where the pixels are checked.
picture | Examples/picture.asm | run | - | -
# tune plays eight notes and prints nothing, so what is recorded is that it ran, that it
# finished on its own, and how much of its time it spent asleep - which is nearly all of it,
# because the tempo is the screen's frame and a program waiting on a frame is not using the
# bus. Tests/sound.sh is where the samples are checked.
tune | Examples/tune.asm | run | - | 5000000
8bitSieve | Examples/primeSieve/8bitSieve.asm | run | - | -
16bitSegmentedSieve | Examples/primeSieve/16bitSieve.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 | Examples/primeSieve/16bitSieveModern.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/controllerWrite.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/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/consoleInterrupt.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.
#
# It ends with a whole program typed in: a string poked into Data Memory, instructions
# assembled into Program Memory, and the result run - which prints something no assembler
# ever saw. The mnemonics deliberately include a lower case one, both selector forms, an
# instruction that does not exist and one missing its value, so that the refusals are
# recorded next to the successes and a failed line is shown writing nothing.
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. Break sets all three of the
# pointers it owns and rotates them between the two stops, so every value recorded here is
# one something chose - it used to leave two of them holding whatever the shell had left,
# which tied this record to where CosmOS's code sits and made it churn on changes that had
# nothing to do with breakpoints. DP3 is left as the system set it, being where the program
# was entered.
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
# Streaming, which is what a file bigger than memory needs. The disk here holds 84000
# bytes in one file and the machine has 64K of Data Memory, so there is no arrangement of
# osFileRead that could get at it: the program reads it through a buffer of one block.
#
# THE CHECK THAT MATTERS IS THE SECOND ONE. A small file is read both ways - whole with
# osFileRead, and streamed - and the two checksums have to agree, so streaming is measured
# against the path that already worked rather than against a number somebody wrote down.
# The checksum is Fletcher's rather than a sum, because a sum is the same whatever order
# the bytes arrived in and the order is exactly what streaming has to get right.
#
# The rest of it is the lookup the system keeps so that reading four hundred blocks of one
# file does not search the directory four hundred times. Two files read alternately catch a
# memory that does not notice the name changed, and a rename catches one that does not
# notice the file moved - and that one would otherwise pass, since the blocks are still
# there and still hold the same bytes.
cosmosStream | CosmOS/Source/cosmos.asm | run | cosmosStream.in | - | disks/stream.img
# A useful application made out of the streaming primitive: print a named text file without
# requiring it to fit in Data Memory. The fixture crosses block boundaries, includes a full
# 256-byte block, and also checks empty, missing, and absent filename cases.
cosmosType | CosmOS/Source/cosmos.asm | run | cosmosType.in | - | disks/type.img
# The same stream with interaction layered on it. Three runs exercise q, a whole screen
# with Space, and one line with Return. The input is intentionally packed so that the one
# byte the pager consumes leaves the next shell command immediately behind it.
cosmosMore | CosmOS/Source/cosmos.asm | run | cosmosMore.in | - | disks/type.img
# Copy and Compare exercise both halves of block streaming together. Empty, exact-block,
# part-block and 84,000-byte files are copied through one buffer; Compare checks the copies
# and a same-sized file whose only difference is deep into the input.
cosmosCopyCompare | CosmOS/Source/cosmos.asm | run | cosmosCopyCompare.in | - | disks/copycompare.img
# A working directory deeper than the prompt can print. The prompt is built BACKWARDS into
# 127 bytes, and nothing bounds how deep the directories go - a path is capped at what one
# operation can name, but "cd" a level at a time is well inside that and can be repeated.
# So the walk wrote down past the front of its own buffer and into what the assembler put
# below it, which was the shell's command names: at six directories of twenty two
# characters the word "exit" was gone and the shell no longer knew how to stop.
#
# WHAT IS RECORDED IS THAT THE COMMANDS STILL WORK, and that is the whole point of running
# help and cd and exit from down there rather than just looking at the prompt. A prompt
# that is merely wrong is a cosmetic fault; this one was writing into other variables.
cosmosDeep | CosmOS/Source/cosmos.asm | run | cosmosDeep.in | - | disks/deep.img
# A disk claiming a directory of 8192 blocks, which is 65536 entries. The last of those is
# entry 65535, and the parent field is an index PLUS ONE in two bytes - so it wraps to
# zero, which means the root. Anything created in such a directory goes into the root
# while the caller is told it went where it asked, and looking there afterwards finds
# nothing, so the same create works again and again and fills the root with entries of one
# name. Refused at mount, which is the only place it can be refused once and for all.
cosmosBigDir | CosmOS/Source/cosmos.asm | run | cosmosBigDir.in | - | disks/bigdir.img
# ---- Starting the machine off a disk ----
#
# Stage one is what will one day be in ROM. It knows the disk's ports, that a SplitBit disk
# begins with its own name, and where two numbers sit in that first block - and nothing
# else. It reads the live boot slot into Program Memory and jumps to the first byte.
#
# Two disks, differing only in which slot the superblock names, and the payloads say
# different things. That is what makes this a test of CHOOSING a slot rather than a test
# that some bytes were read: one prints "booted" and the other does not.
bootChain | Boot/stage1.asm | run | - | - | disks/chain.img
bootChainAlt | Boot/stage1.asm | run | - | - | disks/chainAlt.img
# A disk with no boot area cannot be started, and says so in the one character a ROM has
# room to say anything in.
bootNoArea | Boot/stage1.asm | run | - | - | disks/sbfs.img
# A payload that arranges its own Data Segment, by copying it down from just past its own
# code. Stage one places Program Memory and nothing else - knowing where a payload's data
# ended would mean knowing a format - so this is how the real second stage will get the
# variables and the string that sbfs.asm needs.
bootData | Boot/slotData.asm | assemble | - | -
bootDataRuns | Boot/stage1.asm | run | - | - | disks/chainData.img
# ---- The machine starting itself ----
#
# The whole chain: the emulator hands over stage one, stage one reads the live boot slot
# into Program Memory and jumps, stage two mounts the filesystem, finds /System/cosmos.bin,
# takes the image apart and places its code, its data and its vector table, and jumps to
# the entry point the vectors named.
#
# WHAT IS RECORDED IS THAT THE SYSTEM WORKS AFTERWARDS, not that it started. A loaded
# program running is what says the vector table arrived, because a program reaches the
# system through SWI and nothing else; the file written and the directory entered say the
# filesystem and the console came up with it.
selfBoot | Boot/stage1.asm | run | selfBoot.in | 90000000 | disks/selfboot.img
# And a disk with a boot slot but nothing to start, which says so rather than jumping.
selfBootNoSystem | Boot/stage1.asm | run | - | 90000000 | disks/nosystem.img
# ---- And with no image named at all ----
#
# The mode is "rom": the emulator is handed a disk and nothing else, so it shadows its own
# built-in stage one into Program Memory and starts there, which is what a machine with no
# debugger attached does. Naming an image is the debugger, and every other test here is
# using it.
#
# The source column still names stage1.asm even though the binary is thrown away, because
# that is what is IN the ROM: the makefile builds rom.c from that file, so assembling it
# here says the thing the emulator carries is a thing that still assembles.
romBoot | Boot/stage1.asm | rom | selfBoot.in | 90000000 | disks/selfboot.img
# ---- What starts is a setting, not a name compiled into the loader ----
#
# Three disks differing only in /System/Boot/boot.cfg, so each is a test of the file rather
# than of the machinery under it.
#
# cfgBare starts an image with NO OPERATING SYSTEM IN IT, which is what loading an ordinary
# boot image buys: a program wanting the whole machine is a file like any other, and is
# chosen the same way the system is.
cfgBare | Boot/stage1.asm | rom | - | 90000000 | disks/cfgbare.img
# cfgBroken has every kind of mistake in it and a good setting underneath them. The machine
# STILL STARTS - configuration is advice - and says what it could not use on the way, so a
# setting somebody meant which did not happen is not silent.
cfgBroken | Boot/stage1.asm | rom | - | 90000000 | disks/cfgbroken.img
# cfgFallback names a system that is not there. What was tried, and what was tried next.
cfgFallback | Boot/stage1.asm | rom | selfBoot.in | 90000000 | disks/cfgfallback.img
# And the bare image on its own, which has to keep assembling.
bareMetal | Boot/bare.asm | assemble | - | -
# ---- A system that never reaches a prompt ----
#
# Three disks that differ only in the boot state on them, so the three tests read as three
# consecutive starts of one machine while none of them depends on another having run.
#
# wedgeFirst tries it and marks the disk. wedgeSecond finds the mark still set - nothing
# came back to say the last start arrived - and uses the fallback. wedgeThird finds that
# already happened and does the same again WITHOUT retrying, which is the part worth
# pinning down: a system known not to start should not be tried every other boot for ever.
wedgeFirst | Boot/stage1.asm | rom | - | 90000000 | disks/wedgefirst.img
wedgeSecond | Boot/stage1.asm | rom | - | 90000000 | disks/wedgesecond.img
wedgeThird | Boot/stage1.asm | rom | - | 90000000 | disks/wedgethird.img
wedgedImage | Boot/wedged.asm | assemble | - | -
# ---- Closing the loop from inside the machine ----
#
# A machine that says "settle it to try again" and gives you no way to do so has told you
# about a problem it will not let you fix. Settle is a program rather than a shell word:
# one job, reached through SWI, replaceable, and callable by anything that comes to call
# programs in sequence.
# ---- The console doing a terminal's job ----
#
# Behind a window nothing is handling the line editing, so the console does it. Typing "halp",
# backing over it, and arriving at "help" has to reach the shell as "help" - it used to arrive
# with the backspaces still in it, which made a corrected line unrecognisable while looking
# perfectly right on the screen.
cosmosTyped | CosmOS/Source/cosmos.asm | run | - | - | disks/cosmos.img | cosmosTyped.keys
cosmosSettle | CosmOS/Source/cosmos.asm | run | settle.in | 90000000 | disks/settle.img
# Saying so when there is nothing to settle is as much a part of it as doing it.
cosmosSettled | CosmOS/Source/cosmos.asm | run | settle.in | 90000000 | disks/settled.img
settleApp | CosmOS/Apps/Settle.asm | assemble | - | -
# ---- What a program made of it ----
#
# Three programs in a row, each asked after. A status that did not survive the program that
# set it would be no use to the thing this exists for - whatever comes to run programs in
# sequence and has to decide whether to run the next one.
#
# 0 it did what it was asked, 1 it did not, 2 it was asked wrongly.
cosmosStatus | CosmOS/Source/cosmos.asm | run | status.in | 90000000 | disks/status.img
statusApp | CosmOS/Apps/Status.asm | assemble | - | -
# ---- Reading a file with long lines into the editor ----
#
# Opened twice, because once was not enough to see it: the first open corrupted the head of
# the document and the allocator's pointer, and the second walked a list that led back into
# itself and never came back. The emulator kept running and the machine never answered.
#
# Then a file with a line past what the editor can hold, which is REFUSED rather than
# shortened - a line cut here would be written back cut, and the file damaged by having
# been looked at.
cosmosEditLong | CosmOS/Source/cosmos.asm | run | editLong.in | 200000000 | disks/editlong.img
# ---- Starting something else just this once ----
#
# A bare metal program has nowhere to run: starting it from the shell means there is no
# shell, and pointing boot.cfg at it means a machine that keeps starting it. Once writes a
# request the loader reads before boot.cfg and DELETES BEFORE IT JUMPS, so the image runs on
# the next start and on no other, whatever becomes of it.
#
# Three disks, each a start further on, so none of them depends on another having run.
onceAsked | CosmOS/Source/cosmos.asm | run | once.in | 200000000 | disks/onceasked.img
# The start the request was for. It starts an image with no operating system in it at all,
# and THE BOOT STATE IS NOT TOUCHED: a one shot is already self limiting, and marking it
# would report every successful bare metal boot as a start that never arrived.
onceDue | Boot/stage1.asm | rom | - | 200000000 | disks/oncedue.img
# And the start after, which is an ordinary one again.
onceAfter | Boot/stage1.asm | rom | - | 200000000 | disks/onceafter.img
onceApp | CosmOS/Apps/Once.asm | assemble | - | -
# ---- Starting again ----
#
# The loop end to end and inside one session: ask for a one shot, restart, and watch an
# image with no operating system in it take the machine. Before this the only way to
# restart was to stop the emulator, which meant the one thing the machine could not do was
# the thing Once exists for.
#
# A reset repeats HOW THIS MACHINE STARTED. Here no image is named, so the ROM is shadowed
# again and reads the disk.
rebootOnce | Boot/stage1.asm | rom | reboot.in | 300000000 | disks/reboot.img
# And here one IS named, so the reset places it again rather than reading the disk - a
# reset that changed what the machine is would be the one thing a reset must not do.
rebootDirect | CosmOS/Source/cosmos.asm | run | rebootDirect.in | 200000000 | disks/reboot.img
rebootApp | CosmOS/Apps/Reboot.asm | assemble | - | -
# A failed start with NOTHING to fall back to. The mark must not become a reason to refuse
# to start at all - a failure that was passing recovers here, and one that is not leaves
# the machine exactly where it would have been without any of this.
noFallback | Boot/stage1.asm | rom | - | 90000000 | disks/nofallback.img
# Reading a disk that has directories on it. The machine can walk a path at this point but
# cannot make a directory, so the disk is built by the host tool and read here - which is
# the two implementations checking each other rather than either checking itself.
#
# The two Say.sbx are DIFFERENT PROGRAMS under one name - Say echoes its argument, hello
# prints one fixed line - so which one a path reached is written in the output. Two copies
# of the same program would have passed this test with the parent comparison removed
# altogether, which is the one thing it exists to check.
# Then the ways a path does not resolve: through a file, into a directory that is not
# there, onto a directory rather than a program, a component too long to be a name, and
# the root itself. Last, that delete refuses a directory - the refusal that keeps a freed
# entry index from being handed out with children still pointing at it.
cosmosTree | CosmOS/Source/cosmos.asm | run | cosmosTree.in | - | disks/tree.img
# Writing a file onto a disk that has directories on it. The document goes in the root,
# because nothing on the machine can put one anywhere else yet, and the point is that it
# gets there at all: making a file walks the allocator over two directory entries, which
# hold no blocks and must therefore be in nobody's way when a run of free ones is wanted.
# The listing afterwards says where it landed and how big it is.
cosmosTreeWrite | CosmOS/Source/cosmos.asm | run | cosmosTreeWrite.in | - | disks/treewrite.img
# Committing a streamed file bigger than the room reserved for it, which must be refused.
#
# osFileStart sets an extent aside and osFileWrite already refuses a block index outside it,
# so writing off the end is barred. This is the same neighbour reached the other way: claim
# the extra block at commit rather than write into it. A directory entry is the only record
# of what a file owns, so an entry claiming a block it was never given simply owns it, and
# so does whatever owned it before - both files then look perfectly well formed.
#
# The size is compared and not the room it takes up, because those differ: one block and a
# tail occupies what two whole blocks occupy, so bounding the blocks alone would allow a
# file to report two hundred and forty six bytes that were never written to it. The
# reservation here is one block and a tail of ten, and the claim is two whole blocks - the
# same two blocks, and a bigger lie.
#
# The listing at the end is the point: 266 bytes, which is what was reserved and what was
# written, after the refusal and the honest commit that follows it.
cosmosClaim | CosmOS/Source/cosmos.asm | run | cosmosClaim.in | - | disks/claim.img
# The filesystem waiting for a disk that takes time. Every other test here runs with the
# disk finishing before the next instruction starts, which is what it has always done and
# what hides whether anything honours the busy bit. This one gives it a latency.
#
# It is the same directory listing as any other; the point is that it is the SAME. A
# filesystem that did not wait would read the block before the one it asked for, which is
# not an error anywhere - just quietly the wrong bytes - and this listing would be nonsense
# rather than a failure with a message.
cosmosSlowDisk | CosmOS/Source/cosmos.asm | run | cosmosSlowDisk.in | - | disks/cosmos.img@2000
# The machine building its own tree. It starts with a blank version one disk and makes
# every directory on it, which is the half of the filesystem the machine could only read
# until now.
#
# The refusals are most of the test. rmdir will not take a file and delete will not take a
# directory, so neither can be the one that removed more than was asked for; a directory
# with anything in it is refused outright, because a parent is an entry INDEX and a freed
# index goes to the next thing created - the children would turn up inside whatever took
# its place, with nothing pointing downward to find them by. A name already taken in that
# directory is refused, and the same name in a different directory is not, which is the
# whole point of the exercise.
#
# The last dir is there to show the disk still adds up: every entry made and unmade, and
# nothing left over.
cosmosBuild | CosmOS/Source/cosmos.asm | run | cosmosBuild.in | - | disks/build.img
# The working directory. cd moves the machine, the prompt says where it is once that is
# not the root, and dir lists one directory rather than the whole disk.
#
# The two notes.txt say different things ON PURPOSE, and that is the whole test: reading
# "notes.txt" from /A and then from /B has to give two different files. If the remembered
# file were not dropped when the machine moves, the second read would hand back the first
# one - same key, same entry, nothing about it looking wrong.
#
# Then that a program is looked for where you are BEFORE /Apps: /A holds a Say.sbx that is
# really hello, so "Say" from /A prints the wrong thing on purpose, and "Say" from /B has
# to reach the real one in /Apps.
#
# And the two things only a program moving the machine itself can check. Wander calls
# osChangeDir, so after "Wander /A" from /B the prompt has to say /B again - the shell puts
# the working directory back the way it puts the Stack and the vector table back. Reading
# notes.txt straight afterwards has to give B's copy, which is the remembered file being
# dropped when the machine moved. Without Wander neither of those could be made to fail,
# because nothing else on the machine can move it.
cosmosCwd | CosmOS/Source/cosmos.asm | run | cosmosCwd.in | - | disks/cwd.img
# Typing a program's name starts it. The disk is built so that each line of the input asks
# a different question of the one rule: "Say hello there" is a bare name with an argument,
# "Say.sbx" is the same file spelled out in full, and "run once more" says the program that
# was invoked really did land in the loaded slot and can be started again from it.
#
# Then the four ways a word does not become a running program. "dir" is a real program on
# this disk and must still list the disk, because the built-ins are tried first. "notes"
# finds notes.sbx and says it is not a program, which is the answer that has to differ from
# "I do not know" - the file is there. "notes.txt" looks for notes.txt.sbx and finds
# nothing, which is how a text file stays unreachable by name. And the last two are the
# length boundary: eighteen characters is the longest bare name that leaves room for the
# extension, and nineteen cannot be a file name at all, so both are unknown words.
cosmosInvoke | CosmOS/Source/cosmos.asm | run | cosmosInvoke.in | - | disks/invoke.img
# The assembler's front end, checked in two pieces before anything is built on it.
#
# cosmosSource reads a source file and prints it back. The file crosses a block boundary,
# so the seam is exercised rather than assumed, and the line count at the end says the
# reader knows where it is as well as what it holds.
#
# cosmosTokens says what each token IS. That is the part worth checking here rather than at
# the far end: a wrong classification does not produce a wrong byte somewhere obvious, it
# produces a right looking program of the wrong length with everything after it shifted,
# and by then the only symptom is a label pointing into the middle of an instruction.
cosmosSource | CosmOS/Source/cosmos.asm | run | cosmosSource.in | - | disks/asm.img
cosmosTokens | CosmOS/Source/cosmos.asm | run | cosmosTokens.in | - | disks/asm.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 | - | -
app-Stream | CosmOS/Apps/Stream.asm | assemble | - | -
app-Type | CosmOS/Apps/Type.asm | assemble | - | -
app-Wander | CosmOS/Apps/Wander.asm | assemble | - | -
app-Pour | CosmOS/Apps/Pour.asm | assemble | - | -
app-Claim | CosmOS/Apps/Claim.asm | assemble | - | -
app-More | CosmOS/Apps/More.asm | assemble | - | -
app-Copy | CosmOS/Apps/Copy.asm | assemble | - | -
app-Compare | CosmOS/Apps/Compare.asm | assemble | - | -
# The assembler that runs on the machine, and its parts. Checked on their own so that a
# failure reads as "it does not assemble" rather than as a broken disk image.
asm-Asm | CosmOS/Assembler/Asm.asm | assemble | - | -
asm-readTest | CosmOS/Assembler/readTest.asm | assemble | - | -
asm-tokenTest | CosmOS/Assembler/tokenTest.asm | assemble | - | -
asm-scratch | CosmOS/Assembler/scratch.asm | assemble | - | -
# ---- Programs driven by console input ----
inputTest | Examples/inputTest.asm | run | inputTest.in | -
inputTestOld | testPrograms/inputTest.asm | run | inputTest.in | -
replCalculator | Examples/replCalculator.asm | run | replCalculator.in | -
# ---- Programs that run forever by design, bounded by a cycle count ----
# 11,200,000 cycles is about fourteen generations of the glider, which puts evolveBoard and
# its pointer juggling through its paces many times over.
#
# It was 3,000,000, and the number changed rather than the amount of work: a cycle used to
# be an instruction and is now a memory access, and the average SplitBit instruction makes
# 3.72 of those. Nothing about these two programs changed at all.
16x16Life | Examples/gameOfLife/16x16Life.asm | run | - | 11200000
# 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 | Examples/gameOfLife/16x16LifeModern.asm | run | - | 11200000
# ---- 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.
# A string that spells a directive is a string. The quotes are gone by the time a token is
# looked at, so "#Program" in a program's data was read as the directive: the segment
# changed in the middle of the Data Segment and every label after it came out nine bytes
# wrong, in a file that still had a valid header and a plausible length.
stringKeyword | testPrograms/stringKeyword.asm | run | - | -
# The six instructions added after the first sixty four. Four of them move a Data Pointer
# by a register rather than by a byte written into the program, and two are a call that
# puts nothing back.
#
# What is recorded is the DIFFERENCE between the two kinds of call, twice over: the same
# callee sets A, and after the raw one that is what A holds while after the safe one it is
# not; and each reports how far the Stack came down, which is two bytes against ten. A
# version of RCAL that quietly did what CALL does would pass a test that only checked it
# returned to the right place.
rawCallAndOffsets | testPrograms/rawCallAndOffsets.asm | run | - | -
# Falling off the end of a program into Program Memory nobody wrote. It faults on the byte
# after the last instruction rather than executing a run of additions and going wrong
# somewhere else, which is what 0x00 through 0x0F being empty buys.
runOffTest | testPrograms/runOffTest.asm | run | - | -
# WAIT, the seventy first instruction, on a disk given ten thousand cycles of latency. Two
# reads with interrupts masked and no handler anywhere: the machine sleeps through both and
# reads the status port when it wakes. What is recorded here is that it FINISHES - a WAIT
# that never woke would hang, and a hang is what this catches. Whether it slept or spun is
# a question about cycle counts, which settle() strips, so Tests/terminal.sh asks that one.
waitTest | testPrograms/waitTest.asm | run | - | 5000000 | disks/sbfs.img@10000
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 | - | -