Give Programs/ one rule: a directory per kind, nothing loose

Five .asm files sat at the top of Programs/ beside six directories, with
nothing to say which a new file should join - and hello.asm, which is the
native assembler's first target and named in sixteen places, looked like a
stray.

    Programs/
      Examples/     what you read to learn: hello, printHello, inputTest,
                    replCalculator, and Fibonacci, primeSieve and gameOfLife
                    as sets of their own
      Libraries/    included by name, no entry point of their own
      Loader/       loader.asm, and the loadable program it reads
      CosmOS/       the system, its applications and its assembler
      testPrograms/ what 'make test' drives

Loader/ is the one worth explaining. loader.asm is not a demonstration: it
reads a program off a disk, puts the two pieces where the header asks, and
jumps to the entry. CosmOS grew out of it and does the same thing as one of
its commands. It is kept because backward compatibility with the simplest
version of the system is a standing goal, and it was sitting loose next to
the demos as though it were one.

Programs/loadable/ was a directory holding one file called hello.asm - a
third thing of that name, and the name said nothing about why it was there.
It is Loader/loadable.asm now, beside the loader that reads it.

Every reference moved with them: the makefile's program list, twelve
manifest lines, makedisks.sh, native.sh, and four paths across the README
and both manuals. Verified by deleting both build directories and running
the whole suite from nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-08-21 13:38:47 -04:00
co-authored by Claude Opus 5
parent 6dbb38b209
commit ccf4b384e1
21 changed files with 41 additions and 37 deletions
@@ -0,0 +1,77 @@
; A Fibonacci number generating program that uses two bytes to store the value.
#Include print.asm
#Program
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
HALT
#Data
ValueA:
; Low byte, high byte.
0x00 0x01
ValueB:
; Low byte, high byte.
0x00 0x00
@@ -0,0 +1,132 @@
; A Fibonacci number generating program that uses four bytes to store the value.
#Include print.asm
#Program
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
#Data
ValueA:
; Lowest byte ... Highest byte.
0x01 0x00 0x00 0x00
ValueB:
; Lowest byte ... Highest byte.
0x00 0x00 0x00 0x00
@@ -0,0 +1,33 @@
; A Fibonacci number generating program that uses only one byte to store the value.
#Include print.asm
#Program
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
+326
View File
@@ -0,0 +1,326 @@
; Conway's Game of Life for SplitBit.
;
; The visible field is 16x16, surrounded by a permanently dead one-cell border.
; Each of the 18x18 allocated cells is stored as two adjacent bytes:
; current state, next state
; This makes double buffering possible with SplitBit's single Data Pointer.
;
; The initial pattern is a glider. ANSI terminal control codes redraw the field
; in place. Press Ctrl-C to stop the emulator.
#Include print.asm
#Program
start:
CALL seedGlider
SETD ClearScreen
CALL printString
generationLoop:
CALL renderBoard
CALL evolveBoard
CALL commitBoard
CALL frameDelay
BRI generationLoop
seedGlider:
; Coordinates in the padded field:
; .#.
; ..#
; ###
SETD Board
DPUP 0d42
INIA 0x01
STA
SETD Board
DPUP 0d80
STA
SETD Board
DPUP 0d112
STA
SETD Board
DPUP 0d114
STA
SETD Board
DPUP 0d116
STA
RET
renderBoard:
SETD CursorHome
CALL printString
SETD Board
DPUP 0d38
INIA 0d16
SETD RowCount
STA
SETD Board
DPUP 0d38
renderRow:
PSHD
INIA 0d16
SETD ColCount
STA
POPD
renderCell:
LDA
BRA renderDead
INIB 0x23
OUTB 0x00
BRI renderCellDone
renderDead:
INIB 0x20
OUTB 0x00
renderCellDone:
DPUP 0d02
PSHD
SETD ColCount
LDA
DECA
STA
BRA renderRowDone
POPD
BRI renderCell
renderRowDone:
POPD
CALL lineFeed
DPUP 0d04
PSHD
SETD RowCount
LDA
DECA
STA
BRA renderDone
POPD
BRI renderRow
renderDone:
POPD
RET
evolveBoard:
SETD Board
DPUP 0d38
INIA 0d16
SETD RowCount
STA
SETD Board
DPUP 0d38
evolveRow:
PSHD
INIA 0d16
SETD ColCount
STA
POPD
evolveCellLoop:
CALL evolveCell
DPUP 0d02
PSHD
SETD ColCount
LDA
DECA
STA
BRA evolveRowDone
POPD
BRI evolveCellLoop
evolveRowDone:
POPD
DPUP 0d04
PSHD
SETD RowCount
LDA
DECA
STA
BRA evolveDone
POPD
BRI evolveRow
evolveDone:
POPD
RET
; DP points to the current-state byte of a visible cell.
evolveCell:
CALL countNeighbors
PSHQ
LDA
POPB
; 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
STA
DECD
RET
preserveCell:
LDA
BRA makeDead
makeAlive:
INIA 0x01
INCD
STA
DECD
RET
; Return the sum of the eight neighboring current-state bytes in Q.
; With interleaved cells and an 18-cell row, the relative offsets are:
; -38, -36, -34, -2, +2, +34, +36, +38.
countNeighbors:
RSTA
DPDN 0d38
LDB
CCF
ADD
PSHQ
POPA
DPUP 0d02
LDB
CCF
ADD
PSHQ
POPA
DPUP 0d02
LDB
CCF
ADD
PSHQ
POPA
DPUP 0d32
LDB
CCF
ADD
PSHQ
POPA
DPUP 0d04
LDB
CCF
ADD
PSHQ
POPA
DPUP 0d32
LDB
CCF
ADD
PSHQ
POPA
DPUP 0d02
LDB
CCF
ADD
PSHQ
POPA
DPUP 0d02
LDB
CCF
ADD
RET
; Copy every next-state byte to its adjacent current-state byte.
; The border's next bytes remain zero, so it stays permanently dead.
commitBoard:
SETD Board
INIA 0d18
SETD RowCount
STA
SETD Board
commitRow:
PSHD
INIA 0d18
SETD ColCount
STA
POPD
commitCell:
INCD
LDA
DECD
STA
DPUP 0d02
PSHD
SETD ColCount
LDA
DECA
STA
BRA commitRowDone
POPD
BRI commitCell
commitRowDone:
POPD
PSHD
SETD RowCount
LDA
DECA
STA
BRA commitDone
POPD
BRI commitRow
commitDone:
POPD
RET
; Approximately 0.2 seconds at the emulator's nominal 1 MHz rate.
frameDelay:
INIA 0xFF
delayOuter:
INIB 0xFF
delayInner:
DECB
BRB delayInnerDone
BRI delayInner
delayInnerDone:
DECA
BRA delayDone
BRI delayOuter
delayDone:
RET
#Data
RowCount:
0x00
ColCount:
0x00
ClearScreen:
0x1B
"[2J"
CursorHome:
0x1B
"[H"
; The emulator zero-fills the remainder of Data Memory. Board names the first
; byte of a 648-byte logical allocation (18 * 18 * 2).
Board:
0x00
@@ -0,0 +1,266 @@
; Conway's Game of Life rewritten for SplitBit's four-Data-Pointer ISA.
;
; The representation and display match 16x16Life.asm: a visible 16x16 field,
; a dead border, and interleaved current/next bytes. DP0 walks the board, DP1
; and DP2 address the loop counters, and volatile DP3 walks the neighborhood.
#Include print.asm
#Program
start:
CALL seedGlider
SETD.0 ClearScreen
CALL printString
generationLoop:
CALL renderBoard
CALL evolveBoard
CALL commitBoard
CALL frameDelay
BRI generationLoop
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
commitBoard:
SETD.1 RowCount
SETD.2 ColCount
INIA 0d18
STA.1
SETD.0 Board
commitRow:
INIA 0d18
STA.2
commitCell:
INCD.0
LDA.0
DECD.0
STA.0
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
RowCount:
0x00
ColCount:
0x00
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
+20
View File
@@ -0,0 +1,20 @@
; 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.
#Program
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.
#Data
"Hello, World!"
+55
View File
@@ -0,0 +1,55 @@
; This program asks the user for input, then prints whatever they input back to the console again.
; It stores the input string in a buffer in the Data Memory.
#Include print.asm
#Program
start:
SETD Message0 ; Set the Data Pointer to the explanation message.
CALL printString ; Print it out.
CALL lineFeed
SETD Buffer ; Set the Data Pointer to the start of the buffer.
INIB 0x0A ; Load the value of a linefeed into B.
inputLoop:
INA 0x00 ; Poll the command line.
CCF
SUB
BRQ inputEnd ; If we encountered a linefeed, the string is over, proceed to processing.
STA ; Store A into the buffer.
INCD ; Increment to the next spot in the buffer.
BRI inputLoop ; Loop again to grab more string.
inputEnd:
INIA 0x00 ; Set A to 0.
STA ; Store it in the buffer.
SETD Buffer ; Set the Data Pointer to the start of the buffer again.
CALL printString ; Print it out.
CALL lineFeed
HALT ; End the program.
#Data
; A 256 character buffer for the input string.
; There will need to be some kind of way to define large arrays better than this...
Buffer:
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
Message0:
"Input Test: Will echo anything you put in."
@@ -0,0 +1,241 @@
; A segmented Sieve of Eratosthenes for the complete 16-bit address range.
;
; The sieve uses a 256-byte sliding window. Each entry in PrimeStates contains:
; prime, high byte of next multiple, low byte of next multiple
;
; Once a prime becomes active at p*p, adding an 8-bit prime to offsets in a
; 256-byte window must wrap exactly once before the next window. The wrapped
; low byte becomes that prime's starting offset in the following window.
;
; Output is hexadecimal (0002 through FFFD), separated by spaces.
#Include print.asm
#Program
start:
RSTA
SETD Page
STA
nextPage:
; Clear all 256 flags. A wrapping to zero terminates the loop.
SETD Segment
RSTA
RSTB
clearSegment:
STB
INCD
INCA
BRA segmentCleared
BRI clearSegment
segmentCleared:
; Zero and one are not prime.
SETD Page
LDA
BRA excludeZeroAndOne
BRI markSegment
excludeZeroAndOne:
SETD Segment
INIA 0x01
STA
INCD
STA
markSegment:
; Process the 54 primes not greater than sqrt(0xFFFF).
SETD PrimeStates
INIA 0d54
primeLoop:
CALL processPrime
DPUP 0d03
DECA
BRA scanSegment
BRI primeLoop
scanSegment:
; A is the low byte of the candidate and wraps after 0xFF.
SETD Segment
RSTA
scanLoop:
LDB
BRB emitPrime
scanNext:
INCD
INCA
BRA advancePage
BRI scanLoop
emitPrime:
CALL printCandidateHex
BRI scanNext
advancePage:
SETD Page
LDA
INCA
STA
BRA finished
BRI nextPage
finished:
CALL lineFeed
HALT
; DP points at a PrimeStates entry on entry and is preserved by CALL.
processPrime:
PSHD
INCD
LDA
SETD Page
LDB
XOR
BRQ primeIsActive
POPD
RET
primeIsActive:
; Recover and retain the state-entry pointer for the final update.
POPD
PSHD
; Load the prime into B and its current offset into A.
LDA
PSHA
DPUP 0d02
LDA
POPB
; Form Segment + offset. Segment is page-aligned in Data Memory.
SETD Segment
PSHB
PSHD
POPB
CCF
ADD
PSHQ
POPD
POPB
markPrimeLoop:
INIA 0x01
STA
; Add the prime to the low byte of DP using the stack as a 16-bit
; address adapter. Carry means that the next multiple is in the next page.
PSHD
POPA
CCF
ADD
PSHQ
POPD
BRC primeFinished
BRI markPrimeLoop
primeFinished:
; Q is the wrapped offset for the next page.
POPD
INCD
LDA
INCA
STA
INCD
STQ
RET
; A contains the candidate's low byte. Page contains its high byte.
printCandidateHex:
PSHA
SETD Page
LDA
CALL printByteHex
POPA
CALL printByteHex
CALL blankSpace
RET
#Data
; This 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 treats a carry out
; of the low byte as the end of the page, which only finds the right boundary if the
; window starts on one.
#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
@@ -0,0 +1,224 @@
; 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 print.asm
#Program
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
HALT
; 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
; 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
@@ -0,0 +1,67 @@
; This is an implementation of The Sieve of Eratosthenes that finds all the primes between 2 and 255.
#Include print.asm
#Program
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.
HALT ; The program is done, we found all the primes!
#Data
; 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
+32
View File
@@ -0,0 +1,32 @@
; printHello.asm
; As a demonstration of the new print routines, here's a new implementation for hello.asm
; Anachronaut
; 10/27/2024
; Include the subroutine file.
#Include print.asm
#Program
start:
SETD MyString ; Set the Data Pointer to the start of MyString.
CALL printString ; Call the string printing subroutine.
CALL lineFeed ; Call the line feed subroutine to end the line.
SETD MyDecimal ; Set the Data Pointer to the value to print as a decimal.
LDA ; Load it into A.
CALL printByteDecimal ; Call the decimal printing subroutine.
SETD MyMessage ; Set the Data Pointer to another string.
CALL printString ; Call the string printing subroutine again.
CALL lineFeed ; Call the line feed subroutine to end the line.
HALT ; Stop the program.
#Data
MyString:
"Hello, World! "
MyDecimal:
0d42
MyMessage:
" is the great answer."
+410
View File
@@ -0,0 +1,410 @@
; Interactive hexadecimal calculator for SplitBit.
;
; Enter expressions as:
; HH operator HH
; Whitespace is optional. Supported operators are + - * & | and ^.
; Arithmetic wraps to eight bits. Enter Q to quit.
#Include print.asm
#Program
start:
SETD Welcome
CALL printString
repl:
SETD Prompt
CALL printString
CALL readNonSpace
PSHQ
POPA
; Q, q, or end-of-file exits.
INIB 0xFF
XOR
BRQ quit
INIB 0x51
XOR
BRQ quit
INIB 0x71
XOR
BRQ quit
CALL readHexByteFirst
SETD LeftOperand
STQ
CALL parseIsInvalid
BRQ inputError
CALL readNonSpace
PSHQ
POPA
SETD Operator
STA
CALL readNonSpace
PSHQ
POPA
CALL readHexByteFirst
SETD RightOperand
STQ
CALL parseIsInvalid
BRQ inputError
CALL discardLine
CALL evaluate
SETD Result
STQ
CALL operatorIsInvalid
BRQ operatorError
SETD Result
LDA
CALL printByteHex
CALL lineFeed
BRI repl
inputError:
CALL discardLine
SETD BadInput
CALL printString
BRI repl
operatorError:
SETD BadOperator
CALL printString
BRI repl
quit:
SETD Goodbye
CALL printString
HALT
; Read a character other than space, tab, CR, or LF. Return it in Q.
readNonSpace:
readNonSpaceLoop:
INA 0x00
INIB 0x20
XOR
BRQ readNonSpaceLoop
INIB 0x09
XOR
BRQ readNonSpaceLoop
INIB 0x0A
XOR
BRQ readNonSpaceLoop
INIB 0x0D
XOR
BRQ readNonSpaceLoop
INIB 0x00
CCF
ADD
RET
; A contains the first hexadecimal digit. Read the second and return the byte
; in Q. Q is 0xFF on malformed input.
readHexByteFirst:
CALL clearParseStatus
CALL hexNibble
PSHQ
POPA
INIB 0xFF
XOR
BRQ invalidByte
INIB 0x00
SHL
SHL
SHL
SHL
PSHA
CALL readNonSpace
PSHQ
POPA
CALL hexNibble
PSHQ
POPA
INIB 0xFF
XOR
BRQ invalidLowNibble
POPB
OR
RET
invalidLowNibble:
POPB
invalidByte:
PSHD
SETD ParseStatus
INIA 0x01
STA
POPD
INIA 0xFF
INIB 0x00
CCF
ADD
RET
; Convert the ASCII hexadecimal digit in A to a value in Q.
; Uppercase and lowercase letters are accepted. Q=0xFF means invalid.
hexNibble:
PSHA
; Try 0 through 9.
INIB 0x30
CCF
SUB
BRC tryUpperHex
PSHQ
POPA
INIB 0d10
CCF
SUB
BRC decimalNibble
tryUpperHex:
POPA
PSHA
INIB 0x41
CCF
SUB
BRC tryLowerHex
PSHQ
POPA
INIB 0d06
CCF
SUB
BRC upperNibble
tryLowerHex:
POPA
PSHA
INIB 0x61
CCF
SUB
BRC badNibble
PSHQ
POPA
INIB 0d06
CCF
SUB
BRC lowerNibble
badNibble:
POPA
INIA 0xFF
INIB 0x00
CCF
ADD
RET
decimalNibble:
POPB
INIB 0x00
CCF
ADD
RET
upperNibble:
POPB
INIB 0d10
CCF
ADD
RET
lowerNibble:
POPB
INIB 0d10
CCF
ADD
RET
; Return Q=0 if Q was 0xFF, otherwise return a nonzero value.
resultIsInvalid:
PSHQ
POPA
INIB 0xFF
XOR
RET
clearParseStatus:
PSHD
SETD ParseStatus
RSTA
STA
POPD
RET
parseIsInvalid:
PSHD
SETD ParseStatus
LDA
INIB 0x01
XOR
POPD
RET
operatorIsInvalid:
PSHD
SETD OperatorStatus
LDA
INIB 0x01
XOR
POPD
RET
; Evaluate the stored expression and return its result in Q.
evaluate:
PSHD
SETD OperatorStatus
RSTA
STA
POPD
SETD Operator
LDA
INIB 0x2B
XOR
BRQ evaluateAdd
INIB 0x2D
XOR
BRQ evaluateSubtract
INIB 0x2A
XOR
BRQ evaluateMultiply
INIB 0x26
XOR
BRQ evaluateAnd
INIB 0x7C
XOR
BRQ evaluateOr
INIB 0x5E
XOR
BRQ evaluateXor
SETD OperatorStatus
INIA 0x01
STA
RSTA
INIB 0x00
CCF
ADD
RET
evaluateAdd:
SETD LeftOperand
LDA
INCD
LDB
CCF
ADD
RET
evaluateSubtract:
SETD LeftOperand
LDA
INCD
LDB
CCF
SUB
RET
evaluateAnd:
SETD LeftOperand
LDA
INCD
LDB
AND
RET
evaluateOr:
SETD LeftOperand
LDA
INCD
LDB
OR
RET
evaluateXor:
SETD LeftOperand
LDA
INCD
LDB
XOR
RET
evaluateMultiply:
RSTA
SETD Product
STA
multiplyLoop:
SETD RightOperand
LDA
BRA multiplyDone
DECA
STA
SETD Product
LDA
SETD LeftOperand
LDB
CCF
ADD
SETD Product
STQ
BRI multiplyLoop
multiplyDone:
SETD Product
LDA
INIB 0x00
CCF
ADD
RET
; Consume the rest of the current input line.
discardLine:
discardLoop:
INA 0x00
INIB 0x0A
XOR
BRQ discardDone
INIB 0xFF
XOR
BRQ discardDone
BRI discardLoop
discardDone:
RET
#Data
LeftOperand:
0x00
RightOperand:
0x00
Operator:
0x00
Product:
0x00
Result:
0x00
ParseStatus:
0x00
OperatorStatus:
0x00
Welcome:
"SplitBit calculator (+ - * & | ^), Q quits."
Prompt:
0x0A
"> "
BadInput:
"Invalid hexadecimal input."
BadOperator:
"Unknown operator."
Goodbye:
"Goodbye!"
0x0A