CosmOS pre-alpha and launchable application versions of old programs.
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
@@ -0,0 +1 @@
|
||||
Hello, SplitBit!
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user