CosmOS pre-alpha and launchable application versions of old programs.

This commit is contained in:
Anachronaut
2026-08-17 15:31:49 -04:00
parent eff6902bcf
commit 91c9d49d1b
66 changed files with 5612 additions and 160 deletions
+84
View File
@@ -0,0 +1,84 @@
; A Fibonacci number generating program that uses two bytes to store the value.
#Include services.asm
#Program
#Base 0x2000
start:
; Swap ValueB and ValueA.
; First, store ValueA on the stack.
SETD ValueA
LDA
PSHA
INCD
LDA
PSHA
; Now copy ValueB into AB.
SETD ValueB
LDA ; High byte
INCD
LDB ; Low byte
; Now save it back to ValueA
SETD ValueA
STA ; High byte
INCD
STB ; Low byte.
; Now retrieve value A from the stack and store it in ValueB.
POPB
POPA
SETD ValueB
STA
INCD
STB
; Print ValueA.
SETD ValueA
LDA
CALL printByteHex
INCD
LDA
CALL printByteHex
CALL blankSpace
; Now add ValueA and ValueB, and store the result in ValueA.
; Add the low bytes of ValueA and ValueB
SETD ValueB
INCD
LDA
SETD ValueA
INCD
LDB
CCF
ADD
; Store the result in ValueA.
STQ
; Now add the high bytes of ValueA and ValueB.
DECD
LDB
SETD ValueB
LDA
ADD
; If this addition overflows, we're done.
BRC end
; Otherwise, store the result in ValueA.
SETD ValueA
STQ
; And branch back to the beginning of the loop.
BRI start
end:
CALL lineFeed
SWI osExit
#Data
#Base 0x1000
ValueA:
; Low byte, high byte.
0x00 0x01
ValueB:
; Low byte, high byte.
0x00 0x00
#Include print.asm
+140
View File
@@ -0,0 +1,140 @@
; A Fibonacci number generating program that uses four bytes to store the value.
#Include services.asm
#Program
#Base 0x2000
start:
; Swap ValueB and ValueA.
; First, store ValueA on the stack.
SETD ValueA
LDA
PSHA
INCD
LDA
PSHA
INCD
LDA
PSHA
INCD
LDA
PSHA
; Next, store ValueB on the stack.
SETD ValueB
LDA
PSHA
INCD
LDA
PSHA
INCD
LDA
PSHA
INCD
LDA
PSHA
; Then pop ValueB into ValueA.
SETD ValueA
INCD INCD INCD
POPA
STA
DECD
POPA
STA
DECD
POPA
STA
DECD
POPA
STA
; Then pop ValueA into ValueB.
SETD ValueB
INCD INCD INCD
POPA
STA
DECD
POPA
STA
DECD
POPA
STA
DECD
POPA
STA
; Print ValueA.
SETD ValueA
INCD INCD INCD
LDA
CALL printByteHex
DECD
LDA
CALL printByteHex
DECD
LDA
CALL printByteHex
DECD
LDA
CALL printByteHex
CALL blankSpace
; Now add ValueA and ValueB, and store the result in ValueA.
; Add the lowest bytes of ValueA and ValueB.
SETD ValueB
LDB
SETD ValueA
LDA
ADD
; Store it in ValueA's lowest byte.
STQ
; Add the second lowest bytes of ValueA and ValueB.
SETD ValueB
INCD
LDB
SETD ValueA
INCD
LDA
ADD
; Store it in ValueA's second lowest byte.
STQ
; Add the second highest bytes of ValueA and ValueB.
SETD ValueB
INCD INCD
LDB
SETD ValueA
INCD INCD
LDA
ADD
; Store it in ValueA's third lowest byte.
STQ
; Add the highest bytes of ValueA and ValueB.
SETD ValueB
INCD INCD INCD
LDB
SETD ValueA
INCD INCD INCD
LDA
ADD
; If this addition overflows, we're done.
BRC end
; Otherwise, store the result in ValueA's highest byte.
STQ
; And branch back to the beginning of the loop.
BRI start
end:
CALL lineFeed
;HALT
SWI osExit
#Data
#Base 0x1000
ValueA:
; Lowest byte ... Highest byte.
0x01 0x00 0x00 0x00
ValueB:
; Lowest byte ... Highest byte.
0x00 0x00 0x00 0x00
#Include print.asm
+43
View File
@@ -0,0 +1,43 @@
; A Fibonacci number generating program that uses only one byte to store the value.
#Include services.asm
#Program
#Base 0x2000
start:
; Load our initial values into A and B.
INIA 0x00
CALL printByteDecimal
CALL blankSpace
; Move the value into B.
PSHA
POPB
; Load the next starting value into A.
INIA 0x01
CALL printByteDecimal
CALL blankSpace
loop:
ADD ; Add the values together.
BRC end ; If the value overflows, we're done.
; Copy A into B
PSHA
POPB
; Copy Q into A
PSHQ
POPA
; Print A.
CALL printByteDecimal
CALL blankSpace
BRI loop ; Loop again.
end:
CALL lineFeed
;HALT
SWI osExit ; Return to CosmOS.
#Data
#Base 0x1000
#Include print.asm
+388
View File
@@ -0,0 +1,388 @@
; Conway's Game of Life, as an application CosmOS can load and run.
;
; Ported from gameOfLife/16x16LifeModern.asm. The simulation is unchanged: the same
; interleaved 18 by 18 board with a dead border, the same four-Data-Pointer rewrite of
; the neighbor count, and the same glider.
;
; What had to change is when it stops. On the bare machine this program never stopped,
; because Life has no end state to reach and nothing was waiting for the machine back.
; Under CosmOS a program owns the console until it returns, so a program that never ends
; takes the shell with it. There is no HALT to fall into and no key that can interrupt
; it: console input is a blocking read, so a running program cannot ask whether anybody
; has pressed anything.
;
; So it stops on its own, two ways:
;
; IT SETTLES. commitBoard already walks the current and next state of every cell side
; by side, so it can notice for free whether any of them differed. When none did, the
; board has reached a state it will stay in forever and there is nothing left to show.
; The glider does reach one: it crosses the field, runs into the dead border, and
; collapses into a block in the corner at generation 54.
;
; IT RUNS OUT. Settling catches still lifes and extinction. It does not catch an
; oscillator - a blinker would flip back and forth forever and never be "unchanged" -
; so there is a generation limit behind it. It is not meant to be the answer. It is
; there so that no seed anybody tries later can take the shell down with it.
;
; Note that #Include print.asm comes at the END of this file rather than the beginning.
; print.asm opens with a branch to start, which is what a boot image wants at address
; zero; a loadable program wants its own first instruction at its code base instead.
#Include services.asm
#Program
#Base 0x2000
start:
CALL seedGlider
SETD.0 ClearScreen
CALL printString
SETD.3 GenerationsLeft
INIA 0xFF
STA.3
; Key mode, so that one keypress is one byte and stops it. In line mode the terminal
; holds what is typed until Return, so nothing arrives until then and "press any key"
; would really mean "press any key and then Return". It is put back before this program
; returns; CosmOS puts it back too, in case a program stops without doing so.
INIA 0x01
OUTA 0x02
generationLoop:
CALL renderBoard
CALL evolveBoard
CALL commitBoard
; Has anybody asked it to stop? The status port answers without waiting, which is the
; whole reason it exists: reading the data port here would stop the simulation dead
; until somebody typed something, which is the opposite of what is wanted.
;
; READY is clear at the end of input as well as when nothing has been typed, so running
; with input from a file - which is how the tests run it - never stops here. It runs to
; the still life instead, and that is the right answer in both places.
INA 0x01
INIB 0x01 ; READY
AND
BRQ lifeNoKey
INA 0x00 ; Take the key, so it is not left waiting for the shell.
BRI lifeStopped
lifeNoKey:
; commitBoard leaves the flag set if any cell differed from what replaced it. DP3 is
; pointed at it again rather than trusting what the call left behind: RET does not put
; DP3 back, so its value after a call is the callee's business and not a promise.
SETD.3 BoardChanged
LDA.3
BRA lifeSettled
SETD.3 GenerationsLeft
LDA.3
DECA
STA.3
BRA lifeRanOut
CALL frameDelay
BRI generationLoop
; The three ways it can be over. Each one only picks the words; the tidying up is the same
; for all of them and is written once, which is also how the console cannot be left in key
; mode down one path and not another.
lifeStopped:
SETD.0 StoppedText
BRI lifeEnd
lifeSettled:
SETD.0 SettledText
BRI lifeEnd
lifeRanOut:
SETD.0 RanOutText
lifeEnd:
RSTA
OUTA 0x02 ; Line mode, the way it was found.
CALL lineFeed
CALL printString ; DP0 still holds the words: CALL puts DP0 back.
CALL lineFeed
SWI osExit
seedGlider:
SETD.0 Board
DPUP.0 0d42
INIA 0x01
STA.0
SETD.0 Board
DPUP.0 0d80
STA.0
SETD.0 Board
DPUP.0 0d112
STA.0
DPUP.0 0d02
STA.0
DPUP.0 0d02
STA.0
RET
renderBoard:
SETD.0 CursorHome
CALL printString
SETD.1 RowCount
SETD.2 ColCount
INIA 0d16
STA.1
SETD.0 Board
DPUP.0 0d38
renderRow:
INIA 0d16
STA.2
renderCell:
LDA.0
BRA renderDead
INIB 0x23
OUTB 0x00
BRI renderCellDone
renderDead:
INIB 0x20
OUTB 0x00
renderCellDone:
DPUP.0 0d02
LDA.2
DECA
STA.2
BRA renderRowDone
BRI renderCell
renderRowDone:
CALL lineFeed
DPUP.0 0d04
LDA.1
DECA
STA.1
BRA renderDone
BRI renderRow
renderDone:
RET
evolveBoard:
SETD.1 RowCount
SETD.2 ColCount
INIA 0d16
STA.1
SETD.0 Board
DPUP.0 0d38
evolveRow:
INIA 0d16
STA.2
evolveCellLoop:
CALL evolveCell
DPUP.0 0d02
LDA.2
DECA
STA.2
BRA evolveRowDone
BRI evolveCellLoop
evolveRowDone:
DPUP.0 0d04
LDA.1
DECA
STA.1
BRA evolveDone
BRI evolveRow
evolveDone:
RET
evolveCell:
CALL countNeighbors
MVQB ; B is the neighbor count from here down.
; Three neighbors always produces a live cell.
INIA 0d03
CCF
SUB
BRQ makeAlive
; Two neighbors preserve the current state.
INIA 0d02
CCF
SUB
BRQ preserveCell
makeDead:
RSTA
INCD.0
STA.0
DECD.0
RET
preserveCell:
LDA.0
BRA makeDead
makeAlive:
INIA 0x01
INCD.0
STA.0
DECD.0
RET
; Return the eight-neighbor sum in Q. One Stack round-trip copies DP0 into
; volatile DP3; MVQA then keeps the running total entirely in registers.
countNeighbors:
PSHD.0
POPD.3
RSTA
DPDN.3 0d38
LDB.3
CCF
ADD
MVQA
DPUP.3 0d02
LDB.3
CCF
ADD
MVQA
DPUP.3 0d02
LDB.3
CCF
ADD
MVQA
DPUP.3 0d32
LDB.3
CCF
ADD
MVQA
DPUP.3 0d04
LDB.3
CCF
ADD
MVQA
DPUP.3 0d32
LDB.3
CCF
ADD
MVQA
DPUP.3 0d02
LDB.3
CCF
ADD
MVQA
DPUP.3 0d02
LDB.3
CCF
ADD
RET
; Copies each cell's next state over its current one, and says whether any of them
; differed. The comparison is what the bare metal version did not need: it is one XOR
; on two bytes that are already in registers, in a loop that was already visiting every
; cell, which is why "has it settled" costs almost nothing to ask.
;
; DP3 holds the flag for the whole walk. commitBoard calls nothing, so nothing else can
; want DP3 while it works.
commitBoard:
SETD.3 BoardChanged
RSTA
STA.3
SETD.1 RowCount
SETD.2 ColCount
INIA 0d18
STA.1
SETD.0 Board
commitRow:
INIA 0d18
STA.2
commitCell:
LDB.0 ; The cell as it stands.
INCD.0
LDA.0 ; The cell as it is about to stand.
DECD.0
STA.0
XOR ; Q is zero only if those two were the same.
BRQ commitSame
INIA 0x01
STA.3 ; Something moved, so this is not the last generation.
commitSame:
DPUP.0 0d02
LDA.2
DECA
STA.2
BRA commitRowDone
BRI commitCell
commitRowDone:
LDA.1
DECA
STA.1
BRA commitDone
BRI commitRow
commitDone:
RET
frameDelay:
INIA 0xFF
delayOuter:
INIB 0xFF
delayInner:
DECB
BRB delayInnerDone
BRI delayInner
delayInnerDone:
DECA
BRA delayDone
BRI delayOuter
delayDone:
RET
#Data
#Base 0x1000
RowCount:
0x00
ColCount:
0x00
; Cleared at the top of every commitBoard and set by any cell that changed, so after a
; commit it describes that generation and no other.
BoardChanged:
0x00
; Counts down. One byte is enough for a limit that is not meant to be reached.
GenerationsLeft:
0x00
SettledText:
"the board has settled"
RanOutText:
"stopped: still changing after 255 generations"
StoppedText:
"stopped"
ClearScreen:
0x1B
"[2J"
CursorHome:
0x1B
"[H"
; 18 by 18 cells with the current and next states interleaved, so 648 bytes. The
; original leaves this implicit and leans on Data Memory being zero, which works but
; means the assembler believes the board is one byte long: anything placed after it
; would land inside it, and nothing would say so. Reserving the region states how far
; it reaches, so a label added below here is safe.
Board:
#Reserve 0d648
#Include print.asm
+230
View File
@@ -0,0 +1,230 @@
; The 16-bit segmented sieve rewritten for SplitBit's four-Data-Pointer ISA.
;
; This deliberately implements the same algorithm and emits the same text as
; 16bitSegmentedSieve.asm, making the two versions useful as a direct comparison.
; DP0 walks PrimeStates, DP1 holds Page, DP2 walks Segment, and volatile DP3
; marks multiples. CALL preserves the first three pointers automatically.
#Include services.asm
#Program
#Base 0x2000
start:
RSTA
SETD.1 Page
STA.1
nextPage:
SETD.2 Segment
RSTA
RSTB
clearSegment:
STB.2
INCD.2
INCA
BRA segmentCleared
BRI clearSegment
segmentCleared:
; Zero and one are not prime.
LDA.1
BRA excludeZeroAndOne
BRI markSegment
excludeZeroAndOne:
SETD.2 Segment
INIA 0x01
STA.2
INCD.2
STA.2
markSegment:
SETD.0 PrimeStates
INIA 0d54
primeLoop:
CALL processPrime
DPUP.0 0d03
DECA
BRA scanSegment
BRI primeLoop
scanSegment:
SETD.2 Segment
RSTA
scanLoop:
LDB.2
BRB emitPrime
scanNext:
INCD.2
INCA
BRA advancePage
BRI scanLoop
emitPrime:
CALL printCandidateHex
BRI scanNext
advancePage:
LDA.1
INCA
STA.1
BRA finished
BRI nextPage
finished:
CALL lineFeed
SWI osExit ; Return to CosmOS.
; DP0 points at a PrimeStates entry. CALL restores it on return.
processPrime:
INCD.0
LDA.0
LDB.1
XOR
BRQ primeIsActive
RET
primeIsActive:
; B is the prime and A its current offset.
DECD.0
LDB.0
DPUP.0 0d02
LDA.0
; DP3 = Segment + offset. Only this one initial pointer copy needs the Stack.
SETD.3 Segment
PSHB
PSHD.3
POPB
CCF
ADD
PSHQ
POPD.3
POPB
markPrimeLoop:
INIA 0x01
STA.3
; Add the prime to DP3's low byte. A carry crosses into the next window.
PSHD.3
POPA
CCF
ADD
PSHQ
POPD.3
BRC primeFinished
BRI markPrimeLoop
primeFinished:
; DP0 is on the offset byte; advance the saved high byte and save Q as
; the wrapped offset for the following page.
DECD.0
LDA.0
INCA
STA.0
INCD.0
STQ.0
RET
printCandidateHex:
PSHA
LDA.1
CALL printByteHex
POPA
CALL printByteHex
CALL blankSpace
RET
#Data
#Base 0x1000
; Segment has to begin on a page boundary, and now says so itself rather than relying on
; whatever happens to have been assembled before it. The marking loop adds the prime to
; the low byte of DP3 and treats the carry out as the end of the page, so it only finds
; the right boundary if the window starts on one.
;
; The whole window is written out here so that Page and PrimeStates begin after it.
#Align 0x100
Segment:
0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
Page:
0x00
PrimeStates:
0x02 0x00 0x04
0x03 0x00 0x09
0x05 0x00 0x19
0x07 0x00 0x31
0x0B 0x00 0x79
0x0D 0x00 0xA9
0x11 0x01 0x21
0x13 0x01 0x69
0x17 0x02 0x11
0x1D 0x03 0x49
0x1F 0x03 0xC1
0x25 0x05 0x59
0x29 0x06 0x91
0x2B 0x07 0x39
0x2F 0x08 0xA1
0x35 0x0A 0xF9
0x3B 0x0D 0x99
0x3D 0x0E 0x89
0x43 0x11 0x89
0x47 0x13 0xB1
0x49 0x14 0xD1
0x4F 0x18 0x61
0x53 0x1A 0xE9
0x59 0x1E 0xF1
0x61 0x24 0xC1
0x65 0x27 0xD9
0x67 0x29 0x71
0x6B 0x2C 0xB9
0x6D 0x2E 0x69
0x71 0x31 0xE1
0x7F 0x3F 0x01
0x83 0x43 0x09
0x89 0x49 0x51
0x8B 0x4B 0x79
0x95 0x56 0xB9
0x97 0x59 0x11
0x9D 0x60 0x49
0xA3 0x67 0xC9
0xA7 0x6C 0xF1
0xAD 0x74 0xE9
0xB3 0x7D 0x29
0xB5 0x7F 0xF9
0xBF 0x8E 0x81
0xC1 0x91 0x81
0xC5 0x97 0x99
0xC7 0x9A 0xB1
0xD3 0xAD 0xE9
0xDF 0xC2 0x41
0xE3 0xC9 0x49
0xE5 0xCC 0xD9
0xE9 0xD4 0x11
0xEF 0xDF 0x21
0xF1 0xE2 0xE1
0xFB 0xF6 0x19
#Include print.asm
+74
View File
@@ -0,0 +1,74 @@
; This is an implementation of The Sieve of Eratosthenes that finds all the primes between 2 and 255.
#Include services.asm
#Program
#Base 0x2000
start:
; Search the list until we find a prime.
SETD DataTop
CCF ; Clear the carry flag. In later cycles, the carry flag will be set at the end of the next loop. We'll want it cleared.
RSTA
RSTB
findPrimeLoop:
LDB ; Load an element into B.
BRB foundPrime ; If it's zero, it's a prime.
INCA ; Increment A, our index.
INCD ; Increment the Data Pointer.
BRA end ; If A becomes zero, we've looked through the whole list without finding another prime.
BRI findPrimeLoop ; Keep searching for the next prime.
foundPrime:
; If we've found a prime, we should print it and mark it off the list so we don't print it again.
CALL printByteDecimal ; A contains our prime, so we can just call the print subroutine.
CALL blankSpace ; Put a space afterward to keep things easy to read.
INIB 0x01 ; Set B to 1.
STB ; Mark this prime off the list.
markMultiples:
; Now, we mark each multiple of this prime as nonprime until we reach the end of the list.
PSHD ; Save the Data Pointer to the stack.
POPB ; Pop its low byte into B.
ADD ; Add them together.
PSHQ ; Store the result back onto the stack.
POPD ; Pop the modified address into the Data Pointer.
INIB 0x01 ; Set B to 1.
STB ; Store B to mark the value as nonprime.
BRC start ; If the previous add overflowed, the next nonprime is outside the range of our list, so start over with a new prime.
BRI markMultiples ; Otherwise, loop again to mark the next multiple as nonprime.
end:
CALL lineFeed ; Print a linefeed to make it look nice.
SWI osExit ; The program is done, we found all the primes!
#Data
#Base 0x1000
; The table of our prime candidates. It has to begin on a page boundary: marking walks
; the pointer's low byte and treats the carry out as running off the end of the table,
; which only finds the right end if the table starts on one.
#Align 0x100
DataTop:
0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
#Include print.asm
+61
View File
@@ -0,0 +1,61 @@
; A program for CosmOS to load and run.
;
; It carries no library of its own and no vector table. Everything it can do it asks the
; system for, by name, through services.asm, which the system includes too. The order of
; the names in that one file is what gives them their numbers, so neither side has a
; number written down anywhere and the two cannot disagree about them.
;
; Compare it with Programs/loadable/hello.asm, which is the same idea one step earlier:
; that one talks to the console port itself and stops with HALT, because when it was
; written there was no system to ask and nowhere to give the machine back to.
;
; It is assembled for where it will live. #Base says so, and that makes the assembler
; write it out as a loadable program rather than as a boot image. Nothing relocates
; anything, so those addresses have to be the ones CosmOS puts it at.
#Include services.asm
#Program
#Base 0x2000 ; Above the system, which keeps below here.
greet:
SETD.0 Opening
SWI osPrintString
SETD.0 Question
SWI osPrintString
SETD.0 Answer
INIB 0d31
SWI osReadLine
SETD.0 Hello
SWI osPrintString
SETD.0 Answer
SWI osPrintString
SETD.0 Ending
SWI osPrintString
; Give the machine back. The system takes its Stack back at this point, so everything
; this program pushed goes with it.
SWI osExit
#Data
#Base 0x1000 ; And its data above the system's.
Opening:
"a program, loaded off a disk, running on the system that loaded it
"
Question:
"what should I call you? "
Hello:
"hello, "
Ending:
". that is all I do.
"
; Thirty one characters and the zero byte that ends them.
Answer:
#Reserve 0d32
+30
View File
@@ -0,0 +1,30 @@
; This is a basic hello world program for the SplitBit CPU.
; We'll create a loop that outputs each byte of our string to Output 0, the text console.
; Include the system services so we can return.
#Include services.asm
#Program
#Base 0x2000 ; Change two:
SETD hello ; Change three
Start:
LDA ; Load a byte of the string into A.
BRA End ; If A is zero, branch out of the loop.
OUTA 0x00 ; Output the value in A to Port 0, the text console.
INCD ; Increment the Data Pointer to the next byte of the string.
BRI Start ; Branch immediately to the start of the loop.
End:
INIA 0x0A ; We'll load a linefeed into A and output it to make it look nice.
OUTA 0x00 ; Output it to the text console.
;HALT ; Terminate the program.
; Instead, let's call osExit to return the system nicely. Fourth change.
SWI osExit
#Data
#Base 0x1000 ; Five, adjust the base of the data segment.
hello: ; Throw a label here so we can explicitly point at this data. Six, actually.
"Hello, World!"
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
Hello, SplitBit!
+388
View File
@@ -0,0 +1,388 @@
; console.asm
; Talking to the console.
;
; This is the modern replacement for print.asm, which was written for a machine with one
; Data Pointer and no vector table. The old one is left where it is, because the programs
; that include it still work and are meant to keep working.
;
; Two things are different here, and both are deliberate.
;
; There is no branch at the top. print.asm begins with BRI start, so that a program
; including it arrives at its own entry point instead of falling into the library. That
; was the only way to do it before the Boot vector existed. A program including this file
; says where it begins in its own Vector Segment:
;
; #Vectors
; Boot start
;
; And every routine here names the Data Pointer it works through rather than assuming the
; only one. A pointer handed in is DP0. Nothing here disturbs what the caller left in
; DP3, which is the one a return survives in.
;
; What a routine gives back is in Q, because Q and DP3 are the only things a RET does not
; put back the way it found them.
;
; Written by Anachronaut
#Program
; ---- Characters and strings ----
; A line feed.
newLine:
INIA 0x0A
OUTA 0x00
RET
; DP0 names a string ending in a zero byte. Prints it.
printString:
LDA.0
BRA printStringDone
OUTA 0x00
INCD.0
BRI printString
printStringDone:
RET
; A holds how many spaces to print. None is a fair answer, and prints nothing.
printSpaces:
BRA printSpacesDone
INIB 0x20
printSpacesLoop:
OUTB 0x00
DECA
BNA printSpacesLoop
printSpacesDone:
RET
; ---- Hexadecimal ----
; A holds a byte. Prints it as two hexadecimal digits, high one first.
;
; A and B are a circular shift register sixteen bits long, so rotating right four times
; with B empty walks the high nybble down into place and parks the low one in B. The call
; between the two halves puts A and B back as they were, which is what lets the second
; rotation find the low nybble still waiting.
printByteHex:
RSTB
SHR SHR SHR SHR
CALL printHexDigit
RSTA
SHL SHL SHL SHL
CALL printHexDigit
RET
; DP0 names two bytes, most significant first, the way every number on a SplitBit disk is
; stored. Prints them as four hexadecimal digits.
printWordHex:
LDA.0
CALL printByteHex
INCD.0
LDA.0
CALL printByteHex
RET
; A holds a nybble. Prints the one character that stands for it.
printHexDigit:
INIB 0d10
CCF
SUB
BRC printDecimalDigit ; Under ten, so it is a plain digit.
INIB 0x37 ; 'A' is ten, so this is the offset that gets there.
CCF
ADD
OUTQ 0x00
RET
; A holds a digit from zero to nine. Prints it.
printDecimalDigit:
INIB 0x30
CCF
ADD
OUTQ 0x00
RET
; ---- Decimal ----
; A holds a byte. Prints it in decimal, without leading zeroes.
printByteDecimal:
SETD.0 ConsoleValue
STA.0
SETD.1 ConsoleLeading
RSTA
STA.1 ; Nothing has been printed yet.
INIA 0d100
CALL printBytePlace
INIA 0d10
CALL printBytePlace
; Whatever is left is the ones, and it prints whether or not it is a zero, because a
; number has to show at least one digit.
SETD.0 ConsoleValue
LDA.0
CALL printDecimalDigit
RET
; A holds a power of ten. Counts how many times it comes out of ConsoleValue, prints that
; as a digit, and leaves the remainder behind. A leading zero prints nothing.
printBytePlace:
SETD.2 ConsoleBytePower
STA.2
SETD.0 ConsoleValue
SETD.1 ConsoleCount
RSTA
STA.1
printBytePlaceLoop:
LDA.0
LDB.2
CCF
SUB
BRC printBytePlaceDone ; It went below zero, so it does not come out again.
STQ.0
LDA.1
INCA
STA.1
BRI printBytePlaceLoop
printBytePlaceDone:
LDA.1
BNA printBytePlaceShow
; The digit is a zero, which only prints if something has been printed before it.
SETD.2 ConsoleLeading
LDA.2
BRA printBytePlaceQuiet
RSTA
printBytePlaceShow:
CALL printDecimalDigit
SETD.2 ConsoleLeading
INIA 0x01
STA.2
printBytePlaceQuiet:
RET
; DP0 names a two byte number, most significant byte first. Prints it in decimal, without
; leading zeroes. Sixty five thousand five hundred and thirty five is the largest thing it
; can be handed, which is the whole of an address, so nothing overflows this.
printWordDecimal:
SETD.1 ConsoleWord
CALL consoleCopyWord
SETD.1 ConsoleLeading
RSTA
STA.1
SETD.0 ConsoleTenThousand
CALL printWordPlace
SETD.0 ConsoleThousand
CALL printWordPlace
SETD.0 ConsoleHundred
CALL printWordPlace
SETD.0 ConsoleTen
CALL printWordPlace
; What is left is under ten, so it is in the low byte and it is the last digit.
SETD.0 ConsoleWord
INCD.0
LDA.0
CALL printDecimalDigit
RET
; DP0 names a power of ten, two bytes of it. The same counting as printBytePlace, done
; sixteen bits wide.
printWordPlace:
SETD.1 ConsolePower
CALL consoleCopyWord
SETD.1 ConsoleCount
RSTA
STA.1
printWordPlaceLoop:
CALL consoleTakePower
BNQ printWordPlaceDone
SETD.1 ConsoleCount
LDA.1
INCA
STA.1
BRI printWordPlaceLoop
printWordPlaceDone:
SETD.1 ConsoleCount
LDA.1
BNA printWordPlaceShow
SETD.1 ConsoleLeading
LDA.1
BRA printWordPlaceQuiet
RSTA
printWordPlaceShow:
CALL printDecimalDigit
SETD.1 ConsoleLeading
INIA 0x01
STA.1
printWordPlaceQuiet:
RET
; Takes ConsolePower out of ConsoleWord, if it comes out at all. Q is zero if it did, and
; then ConsoleWord is the smaller for it. If it did not, ConsoleWord is left alone.
;
; The subtraction is done into a spare word rather than in place, because whether it fits
; is not known until the high half is done, and by then an in place low half would already
; have been spent.
;
; The low half clears the Carry Flag first and the high half does not: the borrow the low
; half leaves behind is exactly what the high half has to subtract as well. Nothing
; between them disturbs it, since only the arithmetic instructions and CCF touch it.
consoleTakePower:
SETD.0 ConsoleWord
INCD.0
SETD.1 ConsolePower
INCD.1
LDA.0
LDB.1
CCF
SUB
SETD.2 ConsoleSpare
INCD.2
STQ.2
SETD.0 ConsoleWord
SETD.1 ConsolePower
LDA.0
LDB.1
SUB
BRC consoleTakeNothing
SETD.2 ConsoleSpare
STQ.2
SETD.0 ConsoleSpare
SETD.1 ConsoleWord
CALL consoleCopyWord
RSTA
RSTB
CCF
ADD ; Q is zero: it came out.
RET
consoleTakeNothing:
RSTA
INIB 0x01
CCF
ADD ; Q is one: it did not.
RET
; Two bytes from DP0 to DP1, most significant first.
consoleCopyWord:
LDA.0
STA.1
INCD.0
INCD.1
LDA.0
STA.1
RET
; ---- Reading ----
; DP0 names a buffer and B says how many characters it holds, not counting the zero byte
; that ends it. Reads a line from the console into it. Q is how long the line turned out
; to be.
;
; A line longer than the buffer is cut short, and the rest of it is read and thrown away
; rather than left to turn up as the next line.
;
; ConsoleEndOfInput is set if the console ran out instead of ending a line. That is a
; different thing from an empty line, and a program that reads until there is no more has
; to be able to tell them apart.
readLine:
SETD.1 ConsoleRoom
STB.1
SETD.1 ConsoleLength
RSTA
STA.1
SETD.1 ConsoleEndOfInput
STA.1
readLineNext:
INA 0x00
INIB 0x0A
CCF
SUB
BRQ readLineDone ; A line feed ends the line. A is still the character.
INIB 0xFF
CCF
SUB
BRQ readLineEnd ; There is no more to be had.
; Is there room for it? A still holds the character, so it is put somewhere safe while
; the counting is done.
SETD.1 ConsoleChar
STA.1
SETD.1 ConsoleLength
LDA.1
SETD.1 ConsoleRoom
LDB.1
CCF
SUB
BRQ readLineNext ; Full. Read on, and drop what comes.
SETD.1 ConsoleChar
LDA.1
STA.0
INCD.0
SETD.1 ConsoleLength
LDA.1
INCA
STA.1
BRI readLineNext
readLineEnd:
SETD.1 ConsoleEndOfInput
INIA 0x01
STA.1
readLineDone:
RSTA
STA.0 ; The zero byte that ends it.
SETD.1 ConsoleLength
LDA.1
RSTB
CCF
ADD ; Q is how long the line is.
RET
#Data
; ---- What readLine keeps while it works ----
ConsoleRoom:
0x00
ConsoleLength:
0x00
ConsoleChar:
0x00
; Set when the console ran out rather than ending a line. Cleared at the start of every
; readLine, so it always describes the last line read.
ConsoleEndOfInput:
0x00
; ---- What the number routines keep while they work ----
; Whether any digit has been printed yet, which is what decides if a zero is a leading
; one or a real one.
ConsoleLeading:
0x00
ConsoleCount:
0x00
ConsoleValue:
0x00
ConsoleBytePower:
0x00
ConsolePower:
0x00 0x00
ConsoleWord:
0x00 0x00
ConsoleSpare:
0x00 0x00
; The powers of ten, written the way every number here is written: most significant byte
; first.
ConsoleTenThousand:
0x27 0x10
ConsoleThousand:
0x03 0xE8
ConsoleHundred:
0x00 0x64
ConsoleTen:
0x00 0x0A
+781
View File
@@ -0,0 +1,781 @@
; cosmos.asm
; CosmOS, and the shell that is most of it.
;
; The machine boots into this. It registers what the hardware brought, mounts whatever
; disk is attached, and then reads lines and does what they say until there is no more
; typing to be had.
;
; ---- Where things live ----
;
; The system keeps to the bottom of both memories, and everything above is for whatever
; it is running:
;
; Program Memory 0x0000 - 0x1FFF the system
; 0x2000 - a loaded program's code
; Data Memory 0x0000 - 0x0FFF the system
; 0x1000 - a loaded program's data
;
; Nothing enforces that. Nothing can: the fence guards a range, and this is a convention
; about which range belongs to whom rather than a rule about what may be touched. The
; assembler prints both segment sizes, and they are what to watch.
;
; A program is staged at 0x8000 while it is being loaded, which is inside the region a
; loaded program will own. That is safe because nothing is running during a load, and it
; is where a big program can be read without the system reserving the room for good.
;
; ---- What it can do ----
;
; dir List what is on the disk.
; load Read a program off the disk and put it where it asks to go.
; run Start the program that was loaded.
; help Say what these are.
; exit Stop.
;
; dump is next. The dispatch below is a chain of comparisons, which is the right shape for
; five commands and the wrong shape for twenty; when it grows, the table that
; dispatchTest.asm demonstrates is where it should go.
;
; Written by Anachronaut
#Include console.asm
#Include text.asm
#Include sbfs.asm
#Include services.asm
#Program
boot:
SETD.0 Banner
CALL printString
CALL newLine
; Find out whether there is a filesystem to talk to. Doing this once at boot rather than
; once per command means a disk swapped underneath us is not noticed, which is honest
; for a machine whose disk is a file named on the command line.
CALL sbfsMount
SETD.0 DiskReady
BNQ bootNoDisk
INIA 0x01
STA.0
BRI prompt
bootNoDisk:
RSTA
STA.0
SETD.0 NoDisk
CALL printString
CALL newLine
; ---- The loop ----
prompt:
SETD.0 PromptText
CALL printString
SETD.0 CommandLine
INIB 0d63
CALL readLine
; Running out of typing is how this ends. It is not the same as an empty line, which is
; just somebody pressing return, and the shell should sit there when that happens.
SETD.0 ConsoleEndOfInput
LDA.0
BNA quitRanOut
SETD.0 CommandLine
CALL textSplit
; An empty line asks for nothing.
SETD.0 CommandLine
LDA.0
BRA prompt
SETD.0 CommandLine
SETD.1 DirName
CALL textSame
BRQ doDir
SETD.0 CommandLine
SETD.1 LoadName
CALL textSame
BRQ doLoad
SETD.0 CommandLine
SETD.1 RunName
CALL textSame
BRQ doRun
SETD.0 CommandLine
SETD.1 DumpName
CALL textSame
BRQ doDump
SETD.0 CommandLine
SETD.1 HelpName
CALL textSame
BRQ doHelp
SETD.0 CommandLine
SETD.1 ExitName
CALL textSame
BRQ quit
; Nothing matched. Saying which word was not understood is worth the four instructions:
; it tells somebody who mistyped what they actually typed.
SETD.0 Unknown
CALL printString
SETD.0 CommandLine
CALL printString
CALL newLine
BRI prompt
; Running out of console leaves the cursor part way along a line, because there was no
; return at the end to move it on. Somebody who typed "exit" has already pressed one, and
; a second would only leave a blank line behind.
quitRanOut:
CALL newLine
quit:
SETD.0 Farewell
CALL printString
CALL newLine
HALT
; ---- dir ----
;
; Walks the directory and prints what is in it. A free entry in the middle of a directory
; is stepped over by the walk, so what comes out is the files and nothing else.
doDir:
SETD.0 DiskReady
LDA.0
BRA dirNoDisk
RSTA
SETD.0 DirSeen
STA.0
CALL sbfsFirst
BRI dirCheck
dirStep:
CALL sbfsNext
dirCheck:
BNQ dirDone
SETD.0 DirSeen
LDA.0
INCA
STA.0
SETD.0 SbfsName
CALL printString
SETD.0 SbfsName
CALL nameWidth
MVQA
CALL printSpaces
; A file's length is its block count times 256 plus its tail, which is the block count
; in the high byte and the tail in the low one. Nothing has to multiply anything.
SETD.0 SbfsFileBlocks
DPUP.0 0d01
LDA.0
SETD.1 DirSize
STA.1
SETD.0 SbfsFileTail
LDA.0
SETD.1 DirSize
INCD.1
STA.1
SETD.0 DirSize
CALL printWordDecimal
CALL newLine
BRI dirStep
dirDone:
SETD.0 DirSeen
LDA.0
CALL printByteDecimal
SETD.0 FilesText
CALL printString
CALL newLine
BRI prompt
dirNoDisk:
SETD.0 NoDisk
CALL printString
CALL newLine
BRI prompt
; DP0 names a string. Q is how many spaces pad it out to twenty four columns. A name
; already that long gets one space, so that it cannot run into the number after it.
nameWidth:
INIA 0d24
SETD.1 WidthLeft
STA.1
widthLoop:
LDA.0
BRA widthDone
SETD.1 WidthLeft
LDA.1
DECA
STA.1
BRA widthFloor
INCD.0
BRI widthLoop
widthFloor:
INIA 0d1
SETD.1 WidthLeft
STA.1
widthDone:
SETD.1 WidthLeft
LDA.1
RSTB
CCF
ADD
RET
; ---- load ----
;
; Reads a program off the disk and puts it where its header asks to go. Nothing relocates
; anything: the addresses in the header are the ones the program was built for, and it
; would not work anywhere else.
;
; The whole file is staged at 0x8000 first and then blitted into place, because where the
; pieces belong is not known until the header has been read, and the header is in the file.
doLoad:
SETD.0 DiskReady
LDA.0
BRA loadNoDisk
SETD.1 TextRest
LDD.0.1
LDA.0
BRA loadNothingNamed
CALL sbfsFind
BNQ loadMissing
SETD.1 0x80 0x00
CALL sbfsRead
BNQ loadUnreadable
; "SBEX", or this is not a program. Without this, loading a text file would put nonsense
; into Program Memory and then jump into the middle of it.
SETD.0 0x80 0x00
SETD.2 ExecMagic
INIA 0d4
SETD.1 LoadCount
STA.1
loadMagicLoop:
LDA.0
LDB.2
XOR
BNQ loadNotProgram
INCD.0
INCD.2
LDA.1
DECA
STA.1
BNA loadMagicLoop
SETD.0 0x80 0x00
DPUP.0 0d04
LDA.0
INIB 0d1
XOR
BNQ loadWrongVersion
; The code. It comes from the staging area just past the sixteen byte header, and goes
; wherever the header says, in Program Memory, which the instruction set cannot write
; and the controller can.
INIA 0d1
OUTA 0xE0 ; SourceBank: Data Memory, where the file was staged.
INIA 0x80
OUTA 0xE1
INIA 0d16
OUTA 0xE2 ; 0x8010, the first byte after the header.
RSTA
OUTA 0xE3 ; DestBank: Program Memory.
SETD.0 0x80 0x00
DPUP.0 0d06
LDA.0
OUTA 0xE4
INCD.0
LDA.0
OUTA 0xE5
SETD.0 0x80 0x00
DPUP.0 0d10
LDA.0
OUTA 0xE6
INCD.0
LDA.0
OUTA 0xE7
INIA 0x01
OUTA 0xE8 ; Blit.
; Then the data. A blit leaves its addresses past whatever it touched, so the source is
; already sitting on the first byte of the data and only the destination changes.
INIA 0d1
OUTA 0xE3 ; DestBank: Data Memory.
SETD.0 0x80 0x00
DPUP.0 0d12
LDA.0
OUTA 0xE4
INCD.0
LDA.0
OUTA 0xE5
SETD.0 0x80 0x00
DPUP.0 0d14
LDA.0
OUTA 0xE6
INCD.0
LDA.0
OUTA 0xE7
INIA 0x01
OUTA 0xE8 ; Blit.
; Where it starts. Written out by hand rather than through a routine, because a routine
; could not hand two bytes back: CALL puts A, B and the first three pointers back the
; way it found them.
SETD.0 0x80 0x00
DPUP.0 0d08
LDA.0
SETD.1 LoadedEntry
STA.1
INCD.0
INCD.1
LDA.0
STA.1
INIA 0x01
SETD.0 LoadedOk
STA.0
SETD.0 LoadedText
CALL printString
SETD.0 LoadedEntry
CALL printWordHex
CALL newLine
BRI prompt
loadNoDisk:
SETD.0 NoDisk
BRI loadComplain
loadNothingNamed:
SETD.0 LoadWhat
BRI loadComplain
loadMissing:
SETD.0 NoSuchFile
BRI loadComplain
loadUnreadable:
SETD.0 Unreadable
BRI loadComplain
loadNotProgram:
SETD.0 NotProgram
BRI loadComplain
loadWrongVersion:
SETD.0 WrongVersion
loadComplain:
CALL printString
CALL newLine
BRI prompt
; ---- run ----
;
; Hands the machine to whatever was loaded. Where the Stack is now is written down first,
; because the program is not going to unwind anything it pushes and the exit handler has
; to be able to put the Stack back.
doRun:
SETD.0 LoadedOk
LDA.0
BRA runNothing
MVSD.0
SETD.1 SystemStack
STD.0.1
; The entry address is a number until BRD makes it a place. DP3 is the one to build it
; in, because it is the pointer nothing puts back.
SETD.1 LoadedEntry
LDD.3.1
BRD.3
runNothing:
SETD.0 NothingLoaded
CALL printString
CALL newLine
BRI prompt
; ---- The services ----
;
; These are what a loaded program is allowed to ask for. The names and their numbers come
; from services.asm, which the programs include as well, so neither side writes a number
; down and the two cannot disagree about them.
;
; A handler arrives with the caller's registers exactly as they were: an interrupt frame
; is pushed, not cleared. So the pointer a program put in DP0 is still there to be used.
handlePrintString:
CALL printString
RETI
handleReadLine:
CALL readLine
RETI
; Giving the machine back. This is the one place MVDS earns its keep. The program's Stack,
; and the frame this very interrupt arrived on, are both abandoned where they lie, because
; nothing is going to return through either of them.
;
; Which is exactly why this cannot RETI. Its return address is on the Stack it just walked
; away from, so it branches to the prompt instead.
handleExit:
SETD.1 SystemStack
LDD.0.1
MVDS.0
; The console goes back to line mode whatever the program left it in. A program that
; wanted keys is expected to put it back itself, but one that stopped early, or forgot,
; would otherwise hand back a shell with no echo and no backspace, and the shell has no
; way to find out that happened. Writing line mode when it is already in line mode costs
; a byte out of a port and does nothing, which is the right price for not having to know.
RSTA
OUTA 0x02
SETD.0 Finished
CALL printString
CALL newLine
BRI prompt
; ---- dump ----
;
; dump Sixty four more bytes, carrying on from the last one.
; dump <where> From the start of that bank.
; dump <where> <addr> From there.
;
; <where> is program, data, or a bank number in hexadecimal. That the CPU cannot read
; Program Memory and this can is the whole point: the instruction set has no way to look
; at itself, and the controller does, so a monitor is possible at all only through it.
doDump:
SETD.1 TextRest
LDD.0.1
LDA.0
BRA dumpGo ; Nothing said, so carry on from where the last one stopped.
; Which bank. The two that always exist have names, because typing "program" is what
; somebody means and 0 is what the machine calls it.
CALL textSplit
SETD.1 ProgramWord
CALL textSame
BRQ dumpBankProgram
SETD.1 DataWord
CALL textSame
BRQ dumpBankData
CALL textHexWord
BNQ dumpBadWhere
SETD.0 TextValue
INCD.0
LDA.0
BRI dumpSetBank
dumpBankProgram:
RSTA
BRI dumpSetBank
dumpBankData:
INIA 0d1
dumpSetBank:
SETD.0 DumpBank
STA.0
; And where in it. Naming a bank without an address means the start of it, which is the
; only answer that does not depend on what was asked for last time.
RSTA
SETD.0 DumpAt
STA.0
INCD.0
STA.0
SETD.1 TextRest
LDD.0.1
LDA.0
BRA dumpCheckBank
CALL textHexWord
BNQ dumpBadWhere
SETD.0 TextValue
LDA.0
SETD.1 DumpAt
STA.1
SETD.0 TextValue
INCD.0
LDA.0
SETD.1 DumpAt
INCD.1
STA.1
dumpCheckBank:
; Is there such a bank? Asking the controller for a bank that is not there is refused,
; and a refusal nobody catches stops the machine, which is a poor answer to a typing
; mistake. The bank table says what exists, and it lives in bank 2.
;
; Bank n's record starts at n times eight. A and B are a shift register sixteen bits
; wide, so putting the number in the low half and rotating left three times multiplies
; it by eight without anything falling off the top: the most it can reach is 2040.
RSTA
SETD.0 DumpBank
LDB.0
SHL SHL SHL
SETD.0 DumpRecord
STA.0
INCD.0
STB.0
INIA 0d2
OUTA 0xE0 ; SourceBank: the controller's own memory.
SETD.0 DumpRecord
LDA.0
OUTA 0xE1
INCD.0
LDA.0
OUTA 0xE2
INA 0xE9 ; The flags byte of that bank's record.
INIB 0x01
AND
BRQ dumpNoBank ; The present bit is down, so nothing is there.
dumpGo:
INIA 0d4
SETD.0 DumpRows
STA.0
dumpRow:
SETD.0 DumpAt
CALL printWordHex
INIA 0d2
CALL printSpaces
; Point the controller at the row. Reading the Data port takes a byte and steps the
; source on, so the whole row is one instruction repeated.
SETD.0 DumpBank
LDA.0
OUTA 0xE0
SETD.0 DumpAt
LDA.0
OUTA 0xE1
INCD.0
LDA.0
OUTA 0xE2
; Sixteen bytes, kept as they go past so that they can be shown twice.
INIA 0d16
SETD.0 DumpCount
STA.0
SETD.1 DumpBytes
dumpByte:
INA 0xE9
STA.1
CALL printByteHex
INIA 0x20
OUTA 0x00
INCD.1
SETD.0 DumpCount
LDA.0
DECA
STA.0
BNA dumpByte
; The same sixteen again, as characters. Anything that is not printable shows as a dot,
; because a control character sent to the console would move the cursor and ruin the
; shape of the dump.
INIA 0x20
OUTA 0x00
INIA 0d16
SETD.0 DumpCount
STA.0
SETD.1 DumpBytes
dumpChar:
LDA.1
INIB 0x20
CCF
SUB
BRC dumpDot ; Below a space.
INIB 0x7F
CCF
SUB
BNC dumpDot ; Delete, or above it.
OUTA 0x00
BRI dumpCharNext
dumpDot:
INIA 0x2E
OUTA 0x00
dumpCharNext:
INCD.1
SETD.0 DumpCount
LDA.0
DECA
STA.0
BNA dumpChar
CALL newLine
; Sixteen further along, carrying into the high byte if the low one wrapped.
SETD.0 DumpAt
INCD.0
LDA.0
INIB 0d16
CCF
ADD
STQ.0
BNC dumpRowNext
SETD.0 DumpAt
LDA.0
INCA
STA.0
dumpRowNext:
SETD.0 DumpRows
LDA.0
DECA
STA.0
BNA dumpRow
BRI prompt
dumpBadWhere:
SETD.0 DumpUsage
CALL printString
CALL newLine
BRI prompt
dumpNoBank:
SETD.0 NoSuchBank
CALL printString
CALL newLine
BRI prompt
; ---- help ----
doHelp:
SETD.0 HelpText
CALL printString
CALL newLine
SETD.0 HelpMoreText
CALL printString
CALL newLine
BRI prompt
#Data
Banner:
"CosmOS"
PromptText:
"> "
NoDisk:
"no filesystem on the disk"
Unknown:
"I do not know: "
Farewell:
"halted"
FilesText:
" files"
; Two strings rather than one, because a string literal stops at 255 characters and each
; one carries its own zero byte, so they are printed in turn rather than joined.
HelpText:
"dir list what is on the disk
load <file> read a program off the disk
run start what was loaded"
HelpMoreText:
"dump sixty four bytes of memory, and again for more
dump <program|data|bank> <address>
help this
exit stop"
DumpUsage:
"dump <program|data|bank> <address>"
NoSuchBank:
"there is no such bank"
ProgramWord:
"program"
DataWord:
"data"
DumpName:
"dump"
ExecMagic:
"SBEX"
LoadWhat:
"load what?"
NoSuchFile:
"no such file"
Unreadable:
"could not read it"
NotProgram:
"not a program"
WrongVersion:
"a version I do not know"
LoadedText:
"loaded, starting at "
NothingLoaded:
"nothing is loaded"
Finished:
"finished"
DirName:
"dir"
LoadName:
"load"
RunName:
"run"
HelpName:
"help"
ExitName:
"exit"
DiskReady:
0x00
LoadedOk:
0x00
LoadedEntry:
0x00 0x00
LoadCount:
0x00
; Where the monitor is looking, so that a bare 'dump' can carry on from it.
DumpBank:
0x00
DumpAt:
0x00 0x00
DumpRows:
0x00
DumpCount:
0x00
DumpRecord:
0x00 0x00
DumpBytes:
#Reserve 0d16
; Where the system's Stack was when it handed the machine to a program. Kept below the
; region a program owns, so that a program has to go looking to break it.
SystemStack:
0x00 0x00
DirSeen:
0x00
DirSize:
0x00 0x00
WidthLeft:
0x00
; Sixty three characters and the zero byte that ends them.
CommandLine:
#Reserve 0d64
#Vectors
Boot boot
osPrintString handlePrintString
osReadLine handleReadLine
osExit handleExit
@@ -1,6 +1,10 @@
; sbfs.asm
; Reading the SplitBit Filesystem.
;
; This belongs to CosmOS, which is the thing that needs it. Programs outside CosmOS may
; include it, and the test programs do, but when CosmOS becomes a repository of its own
; this file goes with it and anything left behind takes a copy.
;
; The other implementation of this format is SplitDisk on the host. Nothing is shared
; between them but the written specification, so anything that changes here has to change
; there in the same breath.
@@ -182,6 +186,167 @@ sbfsFindFound:
ADD ; Q is zero: found.
RET
; ---- Walking the directory ----
;
; sbfsFind answers a question about one name. Listing what is on a disk is a different
; job: it needs the directory walked rather than searched. sbfsFirst starts a walk and
; sbfsNext steps it, and each of them stops on an entry that is in use, skipping the free
; ones on the way past.
;
; Q is zero while there is an entry to look at. When it is, SbfsName holds the name with
; a zero byte after it, and SbfsFileStart, SbfsFileBlocks and SbfsFileTail describe the
; file exactly the way sbfsFind leaves them. Q is something else when the directory is
; finished, and also when a block could not be read, which are the same answer to the
; question "is there another one" and different answers to "why not".
;
; A walk keeps a directory block in SbfsBuffer between calls, so anything else that goes
; to the disk in the middle of one ends it. Take what is wanted out of an entry before
; asking the disk for anything else.
sbfsFirst:
SETD.0 SbfsDirStart
SETD.1 SbfsWalkBlock
CALL sbfsCopyWord
SETD.0 SbfsDirBlocks
DPUP.0 0d01
LDA.0
SETD.1 SbfsWalkLeft
STA.1 ; Only the low byte, the same as sbfsFind.
; Nothing is loaded yet. Saying the block in hand is empty makes the scan fetch the
; first one, so there is one way into a block rather than two.
RSTA
SETD.0 SbfsWalkCount
STA.0
BRI sbfsWalkScan
sbfsNext:
; The pointer and the count were stepped when the last entry was handed out, so there
; is nothing to do here but carry on looking.
BRI sbfsWalkScan
sbfsWalkScan:
; Anything left in the block in hand?
SETD.0 SbfsWalkCount
LDA.0
BNA sbfsWalkEntry
; No. Take the next directory block, if the directory has one.
SETD.0 SbfsWalkLeft
LDA.0
BRA sbfsWalkEnd
DECA
STA.0
SETD.0 SbfsWalkBlock
SETD.1 SbfsBlock
CALL sbfsCopyWord
CALL sbfsReadBlock
BRQ sbfsWalkLoaded
RET ; The read failed, and Q says so.
sbfsWalkLoaded:
SETD.1 SbfsBuffer
CALL sbfsBufferOut
; That one is in hand now, so the walk's block number moves on to the following one.
SETD.0 SbfsWalkBlock
CALL sbfsStepWord
; Eight entries to a block, starting at the top of the buffer.
SETD.0 SbfsBuffer
SETD.1 SbfsWalkPointer
STD.0.1
INIA 0d8
SETD.0 SbfsWalkCount
STA.0
BRI sbfsWalkScan
sbfsWalkEntry:
; Where this entry is. Kept in memory rather than in DP3, so that a walk does not
; quietly take the one pointer a caller can carry things across a call in.
SETD.1 SbfsWalkPointer
LDD.2.1
SETD.1 SbfsWalkAt
STD.2.1
; Step past it now, so that the walk has moved on whether this one is taken or skipped.
; Getting that wrong in only one of the two paths is how a walk repeats an entry
; forever, and it repeats the interesting ones rather than the boring ones.
SETD.0 SbfsWalkCount
LDA.0
DECA
STA.0
PSHD.2
POPD.0
DPUP.0 0d32
SETD.1 SbfsWalkPointer
STD.0.1
; Is it a file? A free entry is not one, and there can be free entries in the middle of
; a directory, so this is a skip rather than a stop.
LDA.2
INIB 0x01
AND
BRQ sbfsWalkScan
CALL sbfsWalkTake
RSTA
RSTB
CCF
ADD ; Q is zero: here is an entry.
RET
sbfsWalkEnd:
RSTA
INIB 0d1
CCF
ADD ; Q is one: the directory is finished.
RET
; Takes the pieces out of the entry the walk stopped on. Everything lands in memory,
; because that is the only place a subroutine can leave anything.
sbfsWalkTake:
SETD.1 SbfsWalkAt
LDD.0.1
DPUP.0 0d01
SETD.1 SbfsFileStart
CALL sbfsCopyWord
SETD.1 SbfsWalkAt
LDD.0.1
DPUP.0 0d03
SETD.1 SbfsFileBlocks
CALL sbfsCopyWord
SETD.1 SbfsWalkAt
LDD.0.1
DPUP.0 0d05
LDA.0
SETD.1 SbfsFileTail
STA.1
; The name. Twenty two bytes of it, padded with zeroes rather than terminated, so it is
; copied into a buffer with a twenty third byte that nothing ever writes. That byte is
; what makes a name that fills the field into a string that can be printed.
SETD.1 SbfsWalkAt
LDD.0.1
DPUP.0 0d06
SETD.1 SbfsName
INIA 0d22
SETD.2 SbfsCount
STA.2
sbfsWalkName:
LDA.0
STA.1
INCD.0
INCD.1
SETD.2 SbfsCount
LDA.2
DECA
STA.2
BNA sbfsWalkName
RET
; Compares the name in the entry at DP2 with the one kept in SbfsWanted. Q is zero if
; they are the same. Names are padded with zeroes rather than terminated, so a name that
; fills the field has no terminator to look for, which is why the count is what stops it.
@@ -864,6 +1029,24 @@ SbfsCount:
SbfsLeft:
0x00
; ---- What a walk through the directory keeps between calls ----
SbfsWalkBlock:
0x00 0x00
SbfsWalkLeft:
0x00
SbfsWalkCount:
0x00
SbfsWalkPointer:
0x00 0x00
SbfsWalkAt:
0x00 0x00
; Twenty three bytes for a name of twenty two, so that the last one is a zero nothing
; ever writes over and the name is always a string.
SbfsName:
#Reserve 0d23
SbfsWanted:
#Reserve 0d22
+19
View File
@@ -0,0 +1,19 @@
; The services the system offers, named and numbered.
;
; Both sides include this. The system follows it with handlers for the ones it implements.
; A program that only calls them includes this and nothing else, and can then say them by
; name, because a line with a name and nothing after it declares what a vector is called
; and what number it has without claiming to implement it.
;
; The order here is what fixes the numbers, and it is fixed in one file, so the two sides
; cannot disagree about them and nobody has to write a number down. Adding a service goes
; at the end: putting one in the middle would renumber everything after it, and any
; program already assembled against the old numbers would call the wrong thing.
;
; Written by Anachronaut
#Vectors
osPrintString ; DP0 names a string. Prints it.
osReadLine ; DP0 names somewhere to put a line read from the console.
osExit ; Give the machine back to the system.
+230
View File
@@ -0,0 +1,230 @@
; text.asm
; Picking a line of typing apart.
;
; A shell reads a line and has to decide what was asked for. That is two jobs: cutting the
; first word off the line, and telling whether a word is the one being looked for. There
; is nothing else here, because there is nothing else a command line needs yet.
;
; Written by Anachronaut
#Program
; DP0 names a line ending in a zero byte. Cuts the first word off it, in place, by writing
; a zero byte over the space that ends the word. DP0 is unchanged, because a RET puts it
; back, so afterwards DP0 names just the first word.
;
; Where the rest of the line begins goes in TextRest, with any spaces between skipped. A
; line with only one word on it leaves TextRest naming that line's zero byte, which reads
; as an empty argument rather than as a missing one, and is the same thing here.
textSplit:
LDA.0
BRA textSplitHere ; The line ended, so the whole of it was one word.
INIB 0x20
CCF
SUB
BRQ textSplitCut
INCD.0
BRI textSplit
textSplitCut:
RSTA
STA.0 ; The space becomes the end of the word.
INCD.0
textSplitSkip:
LDA.0
BRA textSplitHere
INIB 0x20
CCF
SUB
BNQ textSplitHere ; Something that is not a space: the rest starts here.
INCD.0
BRI textSplitSkip
textSplitHere:
SETD.1 TextRest
STD.0.1
RET
; DP0 and DP1 name strings ending in zero bytes. Q is zero if they are the same.
;
; The two ending together is what makes them the same. Comparing until one of them ends
; would call "dir" and "dirty" the same word, which is the kind of thing a shell gets
; wrong once and confusingly.
textSame:
LDA.0
LDB.1
CCF
SUB
BNQ textDiffer
LDA.0
BRA textAlike ; Equal, and both of them zero: they ended together.
INCD.0
INCD.1
BRI textSame
textAlike:
RSTA
RSTB
CCF
ADD ; Q is zero: the same.
RET
textDiffer:
RSTA
INIB 0d1
CCF
ADD ; Q is one: not the same.
RET
; DP0 names text. Reads hexadecimal digits off the front of it into TextValue, most
; significant byte first. Q is zero if there was at least one digit to read.
;
; Digits past the fourth push the earlier ones off the top rather than being refused,
; which is what typing over an address does on every monitor there has ever been.
textHexWord:
RSTA
SETD.1 TextValue
STA.1
INCD.1
STA.1
SETD.1 TextDigits
STA.1
textHexLoop:
LDA.0
CALL textHexDigit
PSHQ
POPA
INIB 0xFF
CCF
SUB
BRQ textHexEnd ; Not a digit, so the number stopped before it.
CALL textHexShift
SETD.1 TextDigits
LDA.1
INCA
STA.1
INCD.0
BRI textHexLoop
textHexEnd:
SETD.1 TextDigits
LDA.1
BRA textHexNothing
RSTA
RSTB
CCF
ADD ; Q is zero: there was a number.
RET
textHexNothing:
RSTA
INIB 0d1
CCF
ADD ; Q is one: there was not.
RET
; A holds the digit just read. Moves TextValue up by one place and puts the digit in the
; hole that leaves.
;
; A and B are a circular shift register sixteen bits long, so rotating them left four
; times multiplies the pair by sixteen. What fell off the top of the high byte comes round
; into the bottom of the low one, which is exactly the nybble the new digit wants, so it
; is masked away first.
textHexShift:
SETD.1 TextDigit
STA.1
SETD.0 TextValue
LDA.0
INCD.0
LDB.0
SHL SHL SHL SHL
SETD.0 TextValue
STA.0 ; The high byte is finished.
PSHB
POPA
INIB 0xF0
AND
MVQA
SETD.1 TextDigit
LDB.1
OR
SETD.0 TextValue
INCD.0
STQ.0
RET
; A holds a character. Q is what it is worth as a hexadecimal digit, or 0xFF if it is not
; one. Upper and lower case both count, because nobody wants to be told which they meant.
;
; Everything below works from the distance above '0', which is why the letters are tested
; at seventeen and thirty two rather than at anything recognisable.
textHexDigit:
INIB 0x30
CCF
SUB
BRC textHexNo ; Below '0'.
MVQA
INIB 0d10
CCF
SUB
BNC textHexUpper ; Ten or more above '0', so not 0 to 9.
RSTB
CCF
ADD ; Q is the digit itself.
RET
textHexUpper:
INIB 0d17
CCF
SUB
BRC textHexNo ; Between '9' and 'A'.
MVQA
INIB 0d6
CCF
SUB
BNC textHexLower ; Past 'F'.
INIB 0d10
CCF
ADD
RET
textHexLower:
INIB 0d32
CCF
SUB
BRC textHexNo ; Between 'F' and 'a'.
MVQA
INIB 0d6
CCF
SUB
BNC textHexNo ; Past 'f'.
INIB 0d10
CCF
ADD
RET
textHexNo:
RSTA
INIB 0xFF
CCF
ADD
RET
#Data
; Where the rest of the line begins, after textSplit has taken a word off the front.
TextRest:
0x00 0x00
; What textHexWord read, and what it needs while reading it.
TextValue:
0x00 0x00
TextDigits:
0x00
TextDigit:
0x00
Binary file not shown.
+7 -7
View File
@@ -1,16 +1,16 @@
; A program meant to be loaded off a disk rather than booted from.
;
; It is assembled for where it will live. Reserving the front of each segment puts the
; first thing in it at a known address, and everything after that follows, so the labels
; inside are already right for the place the loader will put it. Nothing relocates
; anything: this works because the addresses here and the addresses in its header say the
; same thing.
; It is assembled for where it will live. Giving each segment a base says where it goes,
; so the labels inside are already right for the place the loader will put it, and the
; assembler writes it out as a loadable program rather than as a boot image. Nothing
; relocates anything: this works because the addresses here and the addresses in its
; header say the same thing.
;
; The loader keeps below both of these, which is the whole of the arrangement.
#Program
#Reserve 0x2000 ; This program's code lives from 0x2000.
#Base 0x2000 ; This program's code lives from 0x2000.
hello:
SETD.0 HelloText
@@ -27,7 +27,7 @@ helloDone:
#Data
#Reserve 0x1000 ; And its data from 0x1000.
#Base 0x1000 ; And its data from 0x1000.
HelloText:
"loaded off a disk, with a string and a loop of its own"
+40 -2
View File
@@ -13,12 +13,14 @@ EMU ?= ../SplitBit
BUILD ?= build
# Libraries are included by bare name, so the assembler is told where to find them.
INCLUDES = -I Libraries
# CosmOS owns the filesystem library and the service names, so it is a place to look too.
INCLUDES = -I Libraries -I CosmOS/Source
# The programs worth building. Files in Libraries/ are left out because they have no
# entry point of their own, and the ones in testPrograms/ are covered by 'make test'
# in the parent directory.
PROGRAMS = \
CosmOS/Source/cosmos.asm \
hello.asm \
printHello.asm \
inputTest.asm \
@@ -46,6 +48,42 @@ $(BUILD)/%.bin: %.asm
run-%: $(BUILD)/%.bin
$(EMU) $<
# ---- CosmOS ----
#
# make cosmos Assemble the system and everything it can load.
# make cosmos-disk ... and put the loadable programs on a disk image.
# make run-cosmos ... and boot the machine with that disk in the drive.
#
# Programs in Apps/ say where they live with #Base, so the assembler writes them out as
# loadable programs rather than as boot images. They are named .sbx to keep that
# difference visible: a .bin is something the machine boots, a .sbx is something a
# running system loads.
DISKTOOL ?= ../SplitDisk
COSMOS = $(BUILD)/CosmOS/Source/cosmos.bin
APPS = $(patsubst CosmOS/Apps/%.asm,$(BUILD)/CosmOS/Apps/%.sbx,$(wildcard CosmOS/Apps/*.asm))
COSMOS_DISK = $(BUILD)/cosmos.img
DEPENDENCIES += $(APPS:.sbx=.d)
$(BUILD)/CosmOS/Apps/%.sbx: CosmOS/Apps/%.asm
@mkdir -p $(@D)
$(ASM) $(INCLUDES) -M $(@:.sbx=.d) -o $@ $<
cosmos: $(COSMOS) $(APPS)
# Made from scratch every time, so that what is on it is what is in Apps/ now and not
# also whatever used to be.
$(COSMOS_DISK): $(APPS)
@mkdir -p $(@D)
rm -f $@
$(DISKTOOL) format $@ 64 2
@for app in $(APPS); do $(DISKTOOL) put $@ $$app; done
cosmos-disk: $(COSMOS_DISK)
run-cosmos: $(COSMOS) $(COSMOS_DISK)
$(EMU) --disk $(COSMOS_DISK) $(COSMOS)
clean:
rm -rf $(BUILD)
@@ -53,4 +91,4 @@ clean:
# reassembles every program that includes it.
-include $(DEPENDENCIES)
.PHONY: all clean
.PHONY: all clean cosmos cosmos-disk run-cosmos
+140
View File
@@ -0,0 +1,140 @@
; Exercises the console's status and control ports.
;
; The console answers on three ports now: 0x00 for bytes as it always did, 0x01 for what
; it is doing, and 0x02 to say which mode it should be in. The point of the mode is that
; the data port never changed, so this checks the old behaviour is still there as much as
; it checks the new behaviour arrived.
;
; What the status bits mean:
; bit 0 READY reading the data port will not have to wait
; bit 1 ENDED input has run out for good
; bit 2 KEYMODE the console is in key mode
;
; This runs with input from a file rather than a terminal, so key mode has no terminal to
; put into another state and the mode bit is the only thing that changes. That is on
; purpose: the same program has to work either way, and a test that needed a terminal
; could not run here at all.
#Include console.asm
#Program
start:
SETD.0 Banner
CALL printString
CALL newLine
; ---- What mode does it start in ----
;
; Nothing has been written to the control port, so this is how the machine comes up, and
; every program written before these ports existed runs in exactly this.
SETD.0 AtStartLabel
CALL printString
CALL showStatus
; ---- Into key mode ----
INIA 0x01
OUTA 0x02
SETD.0 KeyModeLabel
CALL printString
CALL showStatus
; ---- Read what is waiting ----
;
; Reading until ENDED rather than until a count, which is the thing that could not be
; done before: 0xFF from the data port used to mean both "the byte 0xFF" and "there is
; no more", and nothing could tell those apart.
SETD.0 ReadLabel
CALL printString
CALL newLine
readLoop:
INA 0x01
INIB 0x02 ; ENDED
AND
BNQ readDone
INA 0x00
OUTA 0x00 ; Nothing echoes in key mode, so the program does it.
BRI readLoop
readDone:
CALL newLine
; ---- The status at the end of input ----
;
; READY is set as well as ENDED, because a read does answer at once. It just answers
; 0xFF forever. ENDED is what says so.
SETD.0 AtEndLabel
CALL printString
CALL showStatus
; ---- Back to line mode ----
RSTA
OUTA 0x02
SETD.0 LineModeLabel
CALL printString
CALL showStatus
HALT
; Prints the status byte as hex and then names the bits that are up, so that a change in
; the output says which bit moved rather than only that the number is different.
showStatus:
INA 0x01
PSHA
CALL printByteHex
INIA 0x20
OUTA 0x00
POPA
PSHA
INIB 0x01
AND
BRQ showNotReady
SETD.0 ReadyWord
CALL printString
showNotReady:
POPA
PSHA
INIB 0x02
AND
BRQ showNotEnded
SETD.0 EndedWord
CALL printString
showNotEnded:
POPA
INIB 0x04
AND
BRQ showNotKeys
SETD.0 KeysWord
CALL printString
showNotKeys:
CALL newLine
RET
#Data
Banner:
"console mode ports"
AtStartLabel:
"at start: "
KeyModeLabel:
"key mode: "
AtEndLabel:
"at the end: "
LineModeLabel:
"line mode: "
ReadLabel:
"what came in:"
ReadyWord:
"ready "
EndedWord:
"ended "
KeysWord:
"keys "
#Vectors
Boot start
+201
View File
@@ -0,0 +1,201 @@
; Exercises console.asm, the modern console library.
;
; Every routine in it is called here with the cases that are easy to get wrong: a zero,
; the largest thing that fits, the boundary either side of a carry, and a leading zero
; that should not print. The reading half is driven by consoleTest.in, which ends with a
; line longer than the buffer so that the truncation and the swallowing of the rest are
; both shown rather than assumed.
;
; There is no BRI at the top of console.asm, so this program says where it begins in its
; Vector Segment. That is the whole of the arrangement the old library needed a branch for.
#Include console.asm
#Program
start:
SETD.0 Banner
CALL printString
CALL newLine
; ---- Hexadecimal ----
SETD.0 HexLabel
CALL printString
RSTA
CALL printByteHex
INIA 0x20
OUTA 0x00
INIA 0x0F
CALL printByteHex
INIA 0x20
OUTA 0x00
INIA 0xA5
CALL printByteHex
INIA 0x20
OUTA 0x00
INIA 0xFF
CALL printByteHex
CALL newLine
SETD.0 WordHexLabel
CALL printString
SETD.0 Zero
CALL printWordHex
INIA 0x20
OUTA 0x00
SETD.0 Thousand
CALL printWordHex
INIA 0x20
OUTA 0x00
SETD.0 Largest
CALL printWordHex
CALL newLine
; ---- Decimal, one byte wide ----
; Zero has to print a digit, and a hundred has to not print its tens as a blank.
SETD.0 ByteLabel
CALL printString
RSTA
CALL printByteDecimal
CALL blank
INIA 0d7
CALL printByteDecimal
CALL blank
INIA 0d42
CALL printByteDecimal
CALL blank
INIA 0d100
CALL printByteDecimal
CALL blank
INIA 0d255
CALL printByteDecimal
CALL newLine
; ---- Decimal, two bytes wide ----
; Ten thousand and one is the case that catches a place that stops counting too soon,
; and the three zeroes in the middle of it are the case that catches a leading zero
; test applied after the first digit.
SETD.0 WordLabel
CALL printString
SETD.0 Zero
CALL printWordDecimal
CALL blank
SETD.0 Nine
CALL printWordDecimal
CALL blank
SETD.0 Ten
CALL printWordDecimal
CALL blank
SETD.0 TwoFiveFive
CALL printWordDecimal
CALL blank
SETD.0 Thousand
CALL printWordDecimal
CALL blank
SETD.0 TenThousandOne
CALL printWordDecimal
CALL blank
SETD.0 Largest
CALL printWordDecimal
CALL newLine
; ---- Spaces ----
; None of them is a fair number to ask for, and has to print nothing at all.
SETD.0 SpaceLabel
CALL printString
INIA 0x7C
OUTA 0x00
RSTA
CALL printSpaces
INIA 0x7C
OUTA 0x00
INIA 0d4
CALL printSpaces
INIA 0x7C
OUTA 0x00
CALL newLine
; ---- Reading ----
SETD.0 ReadLabel
CALL printString
CALL newLine
readLoop:
SETD.0 LineBuffer
INIB 0d16
CALL readLine
SETD.0 LineLength
STQ.0
; Running out is not an empty line, and this is where the difference shows.
SETD.0 ConsoleEndOfInput
LDA.0
BNA readDone
INIA 0x5B
OUTA 0x00
SETD.0 LineBuffer
CALL printString
INIA 0x5D
OUTA 0x00
CALL blank
SETD.0 LineLength
LDA.0
CALL printByteDecimal
CALL newLine
BRI readLoop
readDone:
SETD.0 Ended
CALL printString
CALL newLine
HALT
; One space. Used between the numbers so that each line reads as a list.
blank:
INIA 0x20
OUTA 0x00
RET
#Data
Banner:
"console test"
HexLabel:
"byte hex: "
WordHexLabel:
"word hex: "
ByteLabel:
"byte dec: "
WordLabel:
"word dec: "
SpaceLabel:
"spaces: "
ReadLabel:
"lines read:"
Ended:
"end of input"
Zero:
0x00 0x00
Nine:
0x00 0x09
Ten:
0x00 0x0A
TwoFiveFive:
0x00 0xFF
Thousand:
0x03 0xE8
TenThousandOne:
0x27 0x11
Largest:
0xFF 0xFF
LineLength:
0x00
LineBuffer:
#Reserve 0d17
#Vectors
Boot start
@@ -0,0 +1,22 @@
; A program that bases one segment and forgets the other.
;
; This is the shape of a real bug: the original Fib-8, brought over to CosmOS as an
; application, based its code at 0x2000 but got its Data Segment from an included library
; and never based it. The three bytes went to 0x0000, on top of the console's own
; variables, and it appeared to work because those three bytes are scratch that readLine
; rewrites before every use. A larger data segment would have reached the decimal table
; and the mounted filesystem behind it, and failed somewhere else entirely.
;
; Nothing relocates, so the assembler is the last place to catch it.
#Program
#Base 0x2000
start:
HALT
#Data
value:
0x00
+132
View File
@@ -0,0 +1,132 @@
; Walking the directory of a SplitBit filesystem.
;
; sbfsFind answers a question about one name. This is the other thing a directory is for:
; being listed. The disk was made by SplitDisk, so the order and contents here are what
; the other implementation of the format wrote, not what this one assumed.
;
; The cases that matter are all on this disk already. There are twelve files, which is
; more than the eight an entry block holds, so the walk has to cross from the first
; directory block into the second. aName22CharactersLong! fills the name field exactly and
; so has no zero byte to end it, which is what the twenty third byte of SbfsName is for.
; empty.txt has no blocks at all.
;
; The count at the end is the proof that the walk stopped where the directory did. A walk
; that stepped its pointer only when it took an entry would loop forever on a free one,
; and one that stepped only when it skipped would hand out every eighth file.
;
; Correct output is the twelve files with their sizes, then how many there were.
#Include console.asm
#Include sbfs.asm
#Program
start:
CALL sbfsMount
BRQ mounted
SETD.0 NoMount
CALL printString
CALL newLine
HALT
mounted:
; Nothing found yet.
RSTA
SETD.0 Seen
STA.0
CALL sbfsFirst
BRI walkCheck
walkStep:
CALL sbfsNext
walkCheck:
BNQ walkDone
SETD.0 Seen
LDA.0
INCA
STA.0
SETD.0 SbfsName
CALL printString
; Line the sizes up, so a name that fills the field and a short one both read cleanly.
SETD.0 SbfsName
CALL nameWidth
MVQA
CALL printSpaces
; A file is its blocks times 256 plus its tail, which is the block count in the high
; byte and the tail in the low one. Nothing has to multiply anything.
SETD.0 SbfsFileBlocks
DPUP.0 0d01
LDA.0
SETD.1 Size
STA.1
SETD.0 SbfsFileTail
LDA.0
SETD.1 Size
INCD.1
STA.1
SETD.0 Size
CALL printWordDecimal
CALL newLine
BRI walkStep
walkDone:
SETD.0 Total
CALL printString
SETD.0 Seen
LDA.0
CALL printByteDecimal
CALL newLine
HALT
; DP0 names a string. Q is how many spaces would pad it out to twenty four characters.
; A name that is already that long gets one space, because none at all would run the
; name into the number.
nameWidth:
INIA 0d24
SETD.1 Padding
STA.1
widthLoop:
LDA.0
BRA widthDone
SETD.1 Padding
LDA.1
DECA
STA.1
BRA widthFloor ; It is already too long to pad.
INCD.0
BRI widthLoop
widthFloor:
INIA 0d1
SETD.1 Padding
STA.1
widthDone:
SETD.1 Padding
LDA.1
RSTB
CCF
ADD
RET
#Data
NoMount:
"no filesystem on that disk"
Total:
"files: "
Seen:
0x00
Padding:
0x00
Size:
0x00 0x00
#Vectors
Boot start
+210
View File
@@ -0,0 +1,210 @@
; Tests MVDS, which copies a Data Pointer into the Stack Pointer.
;
; This is the dangerous one. Moving the Stack Pointer abandons everything below the new
; position: return addresses, saved registers, interrupt frames, all of it. Nothing is
; unwound, because moving the Stack does not move what is on it.
;
; It is here for one job, and that job is what this program acts out. A system that runs
; other programs and takes the machine back afterwards cannot do so without it. A program
; that gives up part way through leaves whatever it pushed behind, and nothing is ever
; going to return and tidy it away. Without MVDS the Stack only ever moves downward, a
; little further with every program run, and a shell cannot outlive many of them.
;
; The other half of the story is that moving the Stack does not destroy what was on it.
; A routine that puts the Stack Pointer back where it found it returns perfectly normally,
; because the frame was only stepped away from. Both halves are acted out here, and the
; nesting case with them: two routines that each borrow a Stack, one inside the other,
; each keeping its own saved Stack Pointer on the Stack it borrowed. A fixed location in
; Data Memory would fail exactly there, and would fail silently.
;
; Both sequences are the ones the Programming Manual prints under "The Stack Pointer,
; Set By Hand", so if either example ever stops working this test says so.
;
; Correct output is:
; stack reclaim test
; at start: FFFF
; spent: FFF7
; reclaimed: FFFF
; still good: 33
; borrowed: 7 and 9
; back home: FFFF
#Include console.asm
#Program
start:
SETD.0 Banner
CALL printString
CALL newLine
; Where the Stack is before anything has touched it. Read first, so that no call has
; had a chance to move it.
MVSD.0
SETD.1 SavedStack
STD.0.1
SETD.0 StartLabel
CALL printString
SETD.0 SavedStack
CALL printWordHex
CALL newLine
; Spend some Stack and never give it back. This is not a subroutine, because a
; subroutine that pushed without popping would take its own return address with it.
; It stands in for a program that stops half way through.
INIA 0x11
PSHA PSHA PSHA PSHA
PSHA PSHA PSHA PSHA
MVSD.0
SETD.1 Spent
STD.0.1
SETD.0 SpentLabel
CALL printString
SETD.0 Spent
CALL printWordHex
CALL newLine
; Take it back. Nothing pops those eight bytes: they are simply no longer on the Stack,
; which is the whole of what reclaiming means.
SETD.1 SavedStack
LDD.0.1
MVDS.0
MVSD.0
SETD.1 Reclaimed
STD.0.1
SETD.0 ReclaimedLabel
CALL printString
SETD.0 Reclaimed
CALL printWordHex
CALL newLine
; A Stack Pointer in the right place is not proof that the Stack works. Push something
; through it and get it back, so that the reclaimed Stack is shown to be usable and not
; merely well positioned.
SETD.0 GoodLabel
CALL printString
INIA 0d33
PSHA
RSTA
POPA
CALL printByteDecimal
CALL newLine
; ---- Moving the Stack and coming back from it ----
; borrower runs on a Stack of its own and returns with a plain RET, which only works
; because putting the Stack Pointer back leaves the frame exactly as CALL left it.
CALL borrower
SETD.0 BorrowedLabel
CALL printString
SETD.0 OuterResult
LDA.0
CALL printByteDecimal
SETD.0 AndText
CALL printString
SETD.0 InnerResult
LDA.0
CALL printByteDecimal
CALL newLine
; The Stack has to be back where it started, or the nesting quietly lost track of it.
MVSD.0
SETD.1 Reclaimed
STD.0.1
SETD.0 HomeLabel
CALL printString
SETD.0 Reclaimed
CALL printWordHex
CALL newLine
HALT
; Borrows a Stack, works on it, calls something that does the same thing again, then puts
; the Stack back and returns. The old Stack Pointer goes onto the borrowed Stack rather
; than into a fixed place in memory, which is what makes the nesting safe: the inner
; routine keeps its own copy on its own Stack and cannot tread on this one.
borrower:
MVSD.3
SETD.0 OuterTop
MVDS.0
PSHD.3
INIA 0d7
PSHA
RSTA
POPA
SETD.1 OuterResult
STA.1
CALL innerBorrower
POPD.3
MVDS.3
RET
; The same again, one level down and on a Stack of its own.
innerBorrower:
MVSD.3
SETD.0 InnerTop
MVDS.0
PSHD.3
INIA 0d9
PSHA
RSTA
POPA
SETD.1 InnerResult
STA.1
POPD.3
MVDS.3
RET
#Data
Banner:
"stack reclaim test"
StartLabel:
"at start: "
SpentLabel:
"spent: "
ReclaimedLabel:
"reclaimed: "
GoodLabel:
"still good: "
BorrowedLabel:
"borrowed: "
AndText:
" and "
HomeLabel:
"back home: "
SavedStack:
0x00 0x00
Spent:
0x00 0x00
Reclaimed:
0x00 0x00
OuterResult:
0x00
InnerResult:
0x00
; Two places a Stack can live, sixty four bytes each. The label is on the last byte of
; each, because a Stack grows downward from wherever it is set.
OuterStack:
#Reserve 0d63
OuterTop:
0x00
InnerStack:
#Reserve 0d63
InnerTop:
0x00
#Vectors
Boot start
+204
View File
@@ -0,0 +1,204 @@
; Exercises text.asm, which is how CosmOS understands what was typed at it.
;
; The cases here are the ones that decide whether a shell feels right or feels broken.
; textSame has to say that "dir" and "dirty" are different words, or every command would
; match its own prefix. textSplit has to give an empty rest rather than a missing one when
; there is no argument, so that "load" with nothing after it is a question the shell can
; answer. And textHexWord has to take upper and lower case alike, refuse things that only
; look like numbers, and push digits off the top rather than refusing a fifth one.
;
; Correct output is:
; split: [dump] [program 2000]
; split: [dir] []
; same: yes no no no
; hex: 2000 00FF 00FF BEEF FFFF 0000
; 0 is a fine way to begin a string
; no number here
#Include console.asm
#Include text.asm
#Program
start:
; ---- Splitting ----
SETD.0 LineOne
CALL textSplit
SETD.0 SplitLabel
CALL printString
INIA 0x5B
OUTA 0x00
SETD.0 LineOne
CALL printString
INIA 0x5D
OUTA 0x00
CALL blank
INIA 0x5B
OUTA 0x00
SETD.1 TextRest
LDD.0.1
CALL printString
INIA 0x5D
OUTA 0x00
CALL newLine
; One word on its own. The rest has to be empty rather than absent.
SETD.0 LineTwo
CALL textSplit
SETD.0 SplitLabel
CALL printString
INIA 0x5B
OUTA 0x00
SETD.0 LineTwo
CALL printString
INIA 0x5D
OUTA 0x00
CALL blank
INIA 0x5B
OUTA 0x00
SETD.1 TextRest
LDD.0.1
CALL printString
INIA 0x5D
OUTA 0x00
CALL newLine
; ---- Comparing ----
; The same, then longer, then shorter, then different. The middle two are what a
; comparison that stopped at the first ending would get wrong.
SETD.0 SameLabel
CALL printString
SETD.0 WordDir
SETD.1 WordDir2
CALL textSame
CALL sayQ
SETD.0 WordDir
SETD.1 WordDirty
CALL textSame
CALL sayQ
SETD.0 WordDirty
SETD.1 WordDir2
CALL textSame
CALL sayQ
SETD.0 WordDir
SETD.1 WordRun
CALL textSame
CALL sayQ
CALL newLine
; ---- Reading numbers ----
SETD.0 HexLabel
CALL printString
SETD.0 HexPlain
CALL showHex
SETD.0 HexUpper
CALL showHex
SETD.0 HexLower
CALL showHex
SETD.0 HexMixed
CALL showHex
SETD.0 HexTooMany
CALL showHex
SETD.0 HexStops
CALL showHex
CALL newLine
; A string may begin with anything, including a zero. The assembler strips the quotes
; before it decides what a token is, so a string starting with a zero looked exactly
; like a malformed literal and was rejected as one. This line is here so that it cannot
; go back to being rejected quietly.
SETD.0 ZeroString
CALL printString
CALL newLine
; Something that is not a number at all has to be refused rather than read as zero.
SETD.0 HexNone
CALL textHexWord
BRQ hexUnexpected
SETD.0 NoNumber
CALL printString
CALL newLine
HALT
hexUnexpected:
SETD.0 Unexpected
CALL printString
CALL newLine
HALT
; DP0 names text. Reads a number from it and prints what came out.
showHex:
CALL textHexWord
SETD.0 TextValue
CALL printWordHex
CALL blank
RET
; Prints yes if Q is zero and no if it is not. Q survives a RET, which is what lets the
; answer get this far.
sayQ:
BNQ sayNo
SETD.0 YesText
CALL printString
RET
sayNo:
SETD.0 NoText
CALL printString
RET
blank:
INIA 0x20
OUTA 0x00
RET
#Data
SplitLabel:
"split: "
SameLabel:
"same: "
HexLabel:
"hex: "
YesText:
"yes "
NoText:
"no "
NoNumber:
"no number here"
ZeroString:
"0 is a fine way to begin a string"
Unexpected:
"a number was found where there is none"
; textSplit writes over the space it cuts at, so these are written out rather than shared.
LineOne:
"dump program 2000"
LineTwo:
"dir"
WordDir:
"dir"
WordDir2:
"dir"
WordDirty:
"dirty"
WordRun:
"run"
HexPlain:
"2000"
HexUpper:
"FF"
HexLower:
"ff"
HexMixed:
"bEeF"
HexTooMany:
"12FFFF"
HexStops:
"0 and more"
HexNone:
"zebra"
#Vectors
Boot start