Block device peripheral and SBFS file system implemented.

This commit is contained in:
Anachronaut
2026-08-16 14:03:29 -04:00
parent 04dfcd707b
commit eff6902bcf
36 changed files with 3251 additions and 31 deletions
+117
View File
@@ -0,0 +1,117 @@
; The negative sense branches.
;
; A quarter of every conditional branch in the corpus used to be a branch over an
; unconditional one, because only "branch if zero" existed. Each of those needed a label
; invented purely to be jumped past, which is a cost in names as much as in bytes.
;
; These test the register directly, the way the positive ones do. A branch on this machine
; never depends on which instruction ran last, except for the two that read the Carry Flag
; and say so in their names.
;
; Each case is checked both ways round, so a branch that always went the same way would
; be caught rather than looking correct half the time.
;
; Correct output is:
; QAB C qab c
; 9876543210
#Include print.asm
#Program
start:
; ---- Each one taken when it should be. ----
RSTA
INIB 0d1
CCF
ADD ; Q is 1, so not zero.
BNQ qTaken
BRI wrong
qTaken:
INIA 0d81 ; 'Q'
OUTA 0x00
INIA 0d5
BNA aTaken
BRI wrong
aTaken:
INIA 0d65 ; 'A'
OUTA 0x00
INIB 0d5
BNB bTaken
BRI wrong
bTaken:
INIA 0d66 ; 'B'
OUTA 0x00
CCF
BNC cTaken
BRI wrong
cTaken:
CALL blankSpace
INIA 0d67 ; 'C'
OUTA 0x00
CALL blankSpace
; ---- And each one not taken when it should not be. ----
RSTA
RSTB
CCF
ADD ; Q is zero.
BNQ wrong
INIA 0d113 ; 'q'
OUTA 0x00
RSTA
BNA wrong
INIA 0d97 ; 'a'
OUTA 0x00
RSTB
BNB wrong
INIA 0d98 ; 'b'
OUTA 0x00
; Set the carry by overflowing, then check BNC does not take it.
INIA 0xFF
INIB 0x01
CCF
ADD
BNC wrong
CALL blankSpace
INIA 0d99 ; 'c'
OUTA 0x00
CALL lineFeed
; ---- A countdown, which is what the missing sense was mostly wanted for. ----
; Before these existed this loop needed a label to jump over, and now it does not.
INIA 0d10
SETD.0 Count
STA.0
countLoop:
LDA.0
DECA ; Ten down to one becomes nine down to zero.
INIB 0d48 ; '0'
CCF
ADD
MVQA
OUTA 0x00
LDA.0
DECA
STA.0
BNA countLoop ; One instruction where it used to take two and a label.
CALL lineFeed
HALT
wrong:
SETD.0 Wrong
CALL printString
CALL lineFeed
HALT
#Data
Wrong:
"a branch went the wrong way"
Count:
0x00
+42
View File
@@ -0,0 +1,42 @@
; A write protected disk.
;
; The bar is in the device, not in the filesystem. A flag in a superblock can be got
; around by writing blocks directly; this cannot be got around at all. It is the tab on
; the side of a floppy rather than a note asking politely.
;
; A disk is read only if it was attached that way, or if the host will not let its image
; be written. The machine cannot tell the two apart and does not need to.
;
; Status bit 2 says the disk is protected. Unlike the busy and error bits it describes the
; medium rather than the last operation, so it reads true before anything has been asked
; of the disk at all.
;
; Correct output is:
; 04 protected, before anything has been attempted
; 04 a read is allowed and does not disturb the bit
; 06 a write is barred: protected, and the operation failed
#Include print.asm
#Program
start:
INA 0x23
CALL printByteHex
CALL lineFeed
RSTA
OUTA 0x20
OUTA 0x21 ; Block 0.
INIA 0x01
OUTA 0x22 ; Read. Reading a protected disk is ordinary.
INA 0x23
CALL printByteHex
CALL lineFeed
INIA 0x02
OUTA 0x22 ; Write. Barred by the device.
INA 0x23
CALL printByteHex
CALL lineFeed
HALT
+143
View File
@@ -0,0 +1,143 @@
; Storage.
;
; The disk is a block device and nothing more. It knows numbered blocks of 256 bytes and
; has never heard of a file, which is deliberate: a filesystem is software this machine
; will run rather than something the host does on its behalf. A disk that understood
; filenames would be the emulator doing the work while the machine pretended it had.
;
; Its buffer is one block, and like any memory a device brings, it is unreachable until
; it is registered as a bank and gone through with the controller.
;
; Between writing and reading the buffer is scrubbed, so a stale copy of the message
; sitting in it cannot make a broken read look like a working one.
;
; The image is made fresh for every run of the suite, so block 3 is zeroes before this
; program touches it.
;
; Correct output is:
; 00 the write worked
; from the disk block 3 came back
; 02 a block past the end of the image is an error, not a fault
#Include print.asm
#Program
start:
; The disk's buffer becomes bank 5.
INIA 0d5
OUTA 0xE3
INIA 0x20
OUTA 0xE2
INIA 0x03
OUTA 0xE8
; Put the message into the buffer and write it to block 3.
SETD.0 Message
CALL aimSourceData
CALL aimDestBuffer
INIA 0d16
CALL setLength
INIA 0x01
OUTA 0xE8
CALL selectBlockThree
INIA 0x02
OUTA 0x22 ; Write.
INA 0x23
CALL printByteHex
CALL lineFeed
; Scrub the buffer, so what comes back has to have come off the disk.
CALL aimDestBuffer
INIA 0x2D ; '-' as the fill byte.
OUTA 0xE2
INIA 0d16
CALL setLength
INIA 0x02
OUTA 0xE8 ; Fill.
CALL selectBlockThree
INIA 0x01
OUTA 0x22 ; Read.
; Bring the block into Data Memory and print it.
INIA 0d5
OUTA 0xE0
RSTA
OUTA 0xE1
OUTA 0xE2
SETD.0 Landing
CALL aimDestData
INIA 0d16
CALL setLength
INIA 0x01
OUTA 0xE8
SETD.0 Landing
CALL printString
CALL lineFeed
; A block past the end of the image says so in Status rather than stopping the machine.
; A disk that cannot read a block is an ordinary thing that happens to working programs.
INIA 0xFF
OUTA 0x20
INIA 0xFF
OUTA 0x21
INIA 0x01
OUTA 0x22
INA 0x23
CALL printByteHex
CALL lineFeed
HALT
selectBlockThree:
RSTA
OUTA 0x20
INIA 0d3
OUTA 0x21
RET
aimSourceData:
INIA 0d1
OUTA 0xE0
PSHD.0
POPA
POPB
OUTB 0xE1
OUTA 0xE2
RET
aimDestData:
INIA 0d1
OUTA 0xE3
PSHD.0
POPA
POPB
OUTB 0xE4
OUTA 0xE5
RET
aimDestBuffer:
INIA 0d5
OUTA 0xE3
RSTA
OUTA 0xE4
OUTA 0xE5
RET
setLength:
PSHA
RSTA
OUTA 0xE6
POPA
OUTA 0xE7
RET
#Data
Message:
"from the disk"
Landing:
#Reserve 0d20
+156
View File
@@ -0,0 +1,156 @@
; Reading the SplitBit Filesystem.
;
; The disk this reads was made by SplitDisk, which is the other implementation of the same
; format. That is what makes this worth running: the library is being checked against
; something written by different code working from the same written specification, rather
; than against itself. If the two ever drift, this is where it shows.
;
; The cases here are the ones most likely to be wrong. across.txt is longer than a block,
; so reading it has to carry on from one to the next. Its entry, and the two after it, are
; in the second directory block, so finding it has to walk past the end of the first.
; aName22CharactersLong! fills the name field exactly, so there is no zero on the end of
; it to stop a comparison. empty.txt has no blocks at all.
;
; Correct output is:
; greeting.txt 0000 11 hello from a file
; across.txt 0002 BC ABCDEFGH...
; aName22CharactersLong! 0000 16 exactly twenty two!!!!
; empty.txt 0000 00
; absent.txt missing
#Include print.asm
#Include sbfs.asm
#Program
start:
CALL sbfsMount
BRQ mounted
SETD.0 NoMount
CALL printString
CALL lineFeed
HALT
mounted:
SETD.0 WantGreeting
CALL showFile
SETD.0 WantAcross
CALL showFile
SETD.0 WantLongName
CALL showFile
SETD.0 WantEmpty
CALL showFile
SETD.0 WantAbsent
CALL showFile
HALT
; DP0 names a file. Prints its name, what the directory says about it, and then the file
; itself. DP3 keeps the name across the calls, because it is the pointer a CALL does not
; put back.
showFile:
PSHD.0
POPD.3
CALL printString
CALL blankSpace
PSHD.3
POPD.0
CALL sbfsFind
BRQ showFound
SETD.0 Missing
CALL printString
CALL lineFeed
RET
showFound:
SETD.0 SbfsFileBlocks
LDA.0
CALL printByteHex
INCD.0
LDA.0
CALL printByteHex
CALL blankSpace
SETD.0 SbfsFileTail
LDA.0
CALL printByteHex
CALL blankSpace
SETD.1 Landing
CALL sbfsRead
BRQ showContents
SETD.0 Failed
CALL printString
CALL lineFeed
RET
; Writes out exactly as many bytes as the file has, and no more. The length is the block
; count and the tail side by side: blocks times 256 plus the tail is the same as putting
; the count in the high byte and the tail in the low one.
showContents:
SETD.0 Landing
SETD.1 SbfsFileBlocks
DPUP.1 0d01
LDA.1
SETD.1 LeftHigh
STA.1
SETD.1 SbfsFileTail
LDA.1
SETD.1 LeftLow
STA.1
showLoop:
SETD.1 LeftHigh
LDA.1
SETD.2 LeftLow
LDB.2
OR
BRQ showEnd ; Nothing left of it.
LDA.0
OUTA 0x00
INCD.0
SETD.1 LeftLow
LDA.1
DECA
STA.1
BRC showBorrow ; It went under, so the high byte owes one.
BRI showLoop
showBorrow:
SETD.1 LeftHigh
LDA.1
DECA
STA.1
BRI showLoop
showEnd:
CALL lineFeed
RET
#Data
WantGreeting:
"greeting.txt"
WantAcross:
"across.txt"
WantLongName:
"aName22CharactersLong!"
WantEmpty:
"empty.txt"
WantAbsent:
"absent.txt"
NoMount:
"no mount"
Missing:
"missing"
Failed:
"failed"
LeftHigh:
0x00
LeftLow:
0x00
Landing:
#Reserve 0d1024
+157
View File
@@ -0,0 +1,157 @@
; Writing the SplitBit Filesystem.
;
; A file's size is settled when it is made, because nothing here can grow one afterwards:
; files are laid down contiguously, so the block after a file usually belongs to somebody
; else. That is the bargain the format makes, and it is why the size comes first.
;
; The disk already has a file on it, so the second one has to be put somewhere that does
; not tread on it. There is no allocation table to consult: with contiguous files the
; directory already says which blocks are spoken for, so finding room is a walk through
; the entries rather than a lookup.
;
; What is written is read straight back, through the same directory, so a file that went
; down in the wrong place would come back wrong rather than looking plausible.
;
; Correct output is:
; here.txt 0002
; first.txt 0003 written by SplitBit itself
; second.txt 0004 and a second one after it
#Include print.asm
#Include sbfs.asm
#Program
start:
CALL sbfsMount
BNQ failed
; What was already there, and where.
SETD.0 Existing
CALL report
BNQ failed
SETD.0 FirstName
SETD.1 FirstText
INIA 0d26
CALL makeAndWrite
BNQ failed
SETD.0 SecondName
SETD.1 SecondText
INIA 0d25
CALL makeAndWrite
BNQ failed
; And read both of them back off the disk.
SETD.0 FirstName
CALL report
BNQ failed
SETD.0 SecondName
CALL report
BNQ failed
HALT
failed:
SETD.0 Failed
CALL printString
CALL lineFeed
HALT
; DP0 names the file, DP1 is its text, and A is how many bytes of it there are. Nothing
; here is longer than a block, so the whole size is the tail.
makeAndWrite:
; The text goes on the Stack, not into DP3. DP3 is the pointer a call does not put back,
; which cuts both ways: it is how a routine hands one out, and it is therefore not a
; safe place to leave anything across a call to a routine that might use it. sbfsCreate
; does use it.
PSHD.1
SETD.1 SbfsFileTail
STA.1
RSTA
SETD.1 SbfsFileBlocks
STA.1
INCD.1
STA.1
CALL sbfsCreate
POPD.1 ; Back off the Stack whether it worked or not.
BNQ makeFailed
CALL sbfsWriteFile
RET
makeFailed:
RET
; Prints a file's name, where it begins, and what is in it.
report:
PSHD.0
POPD.3
CALL printString
CALL blankSpace
PSHD.3
POPD.0
CALL sbfsFind
BNQ reportFailed
SETD.0 SbfsFileStart
LDA.0
CALL printByteHex
INCD.0
LDA.0
CALL printByteHex
CALL blankSpace
SETD.1 Landing
CALL sbfsRead
BNQ reportFailed
; Everything here fits in one block, so the tail is the whole length.
SETD.0 Landing
SETD.1 SbfsFileTail
LDA.1
SETD.1 LeftOver
STA.1
BRA reportEnd
reportLoop:
LDA.0
OUTA 0x00
INCD.0
SETD.1 LeftOver
LDA.1
DECA
STA.1
BNA reportLoop
reportEnd:
CALL lineFeed
RSTA
RSTB
CCF
ADD
RET
reportFailed:
RSTA
INIB 0d1
CCF
ADD
RET
#Data
Existing:
"here.txt"
FirstName:
"first.txt"
SecondName:
"second.txt"
FirstText:
"written by SplitBit itself"
SecondText:
"and a second one after it"
Failed:
"failed"
LeftOver:
0x00
Landing:
#Reserve 0d512