New SplitBit programs.

This commit is contained in:
Anachronaut
2026-08-13 23:42:55 -04:00
parent c2440ae5fa
commit 94b3a7af28
15 changed files with 1155 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+79
View File
@@ -0,0 +1,79 @@
#Program
; These routines untested and written entirely by Claude Code!
; int32 subroutines.
; An int32 value is a 32-bit unsigned integer, represented by four bytes,
; in the order most-significant to least-significant.
; The basic operations take two sequential int32 values as operands from Data Memory.
; They assume that the Data Pointer is set to the high byte of Operand A.
; Operands are stored in an eight-byte array in the following order:
; Operand A, byte 3 (most significant)
; Operand A, byte 2
; Operand A, byte 1
; Operand A, byte 0 (least significant)
; Operand B, byte 3 (most significant)
; Operand B, byte 2
; Operand B, byte 1
; Operand B, byte 0 (least significant)
; All operations overwrite Operand A. This causes the Data Pointer to point to the result after returning.
int32add:
; Add two int32 values.
; If the addition results in a carry, the carry flag will be set.
CCF ; Clear the Carry Flag.
DPUP 0d07 ; Move to Operand B's byte 0 (least significant).
LDB ; Load it to B.
DPDN 0d04 ; Move to Operand A's byte 0.
LDA ; Load it to A.
ADD ; Add them together.
STQ ; Store the result.
DPUP 0d03 ; Move to Operand B's byte 1.
LDB ; Load it to B.
DPDN 0d04 ; Move to Operand A's byte 1.
LDA ; Load it to A.
ADD ; Add with carry from byte 0.
STQ ; Store the result.
DPUP 0d03 ; Move to Operand B's byte 2.
LDB ; Load it to B.
DPDN 0d04 ; Move to Operand A's byte 2.
LDA ; Load it to A.
ADD ; Add with carry from byte 1.
STQ ; Store the result.
DPUP 0d03 ; Move to Operand B's byte 3 (most significant).
LDB ; Load it to B.
DPDN 0d04 ; Move to Operand A's byte 3.
LDA ; Load it to A.
ADD ; Add with carry from byte 2.
STQ ; Store the result.
RET ; Return to the caller.
int32sub:
; Subtract Operand B from Operand A.
; If the subtraction results in a borrow, the carry flag will be set.
CCF ; Clear the Carry Flag.
DPUP 0d07 ; Move to Operand B's byte 0 (least significant).
LDB ; Load it to B.
DPDN 0d04 ; Move to Operand A's byte 0.
LDA ; Load it to A.
SUB ; Subtract B from A.
STQ ; Store the result.
DPUP 0d03 ; Move to Operand B's byte 1.
LDB ; Load it to B.
DPDN 0d04 ; Move to Operand A's byte 1.
LDA ; Load it to A.
SUB ; Subtract with borrow from byte 0.
STQ ; Store the result.
DPUP 0d03 ; Move to Operand B's byte 2.
LDB ; Load it to B.
DPDN 0d04 ; Move to Operand A's byte 2.
LDA ; Load it to A.
SUB ; Subtract with borrow from byte 1.
STQ ; Store the result.
DPUP 0d03 ; Move to Operand B's byte 3 (most significant).
LDB ; Load it to B.
DPDN 0d04 ; Move to Operand A's byte 3.
LDA ; Load it to A.
SUB ; Subtract with borrow from byte 2.
STQ ; Store the result.
RET ; Return to the caller.
+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 Libraries/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
+236
View File
@@ -0,0 +1,236 @@
; 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 Libraries/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
; print.asm deliberately pads its data to one page, so this begins at 0x0100.
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
Binary file not shown.
Binary file not shown.
+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 Libraries/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
Binary file not shown.
+54
View File
@@ -0,0 +1,54 @@
; Tests the Data Pointer calling convention.
;
; Every Data Pointer is aimed at a different marker byte, then a subroutine
; repoints all four of them at Z and returns.
;
; DP0 through DP2 are preserved across a CALL, so they should come back still
; aimed at their own markers. DP3 is volatile, which is what lets a subroutine
; hand an address back to its caller, so it should come back aimed at Z.
;
; Correct output is ABCZ.
#Program
start:
SETD.0 MarkA
SETD.1 MarkB
SETD.2 MarkC
SETD.3 MarkD
CALL clobber
; Read a byte through each pointer in turn and print it.
LDA.0
OUTA 0x00
LDA.1
OUTA 0x00
LDA.2
OUTA 0x00
LDA.3
OUTA 0x00
INIA 0x0A
OUTA 0x00
HALT
clobber:
; Aim every pointer somewhere else. Only the change to DP3 should outlive the RET.
SETD.0 MarkZ
SETD.1 MarkZ
SETD.2 MarkZ
SETD.3 MarkZ
RET
#Data
MarkA:
"A"
MarkB:
"B"
MarkC:
"C"
MarkD:
"D"
MarkZ:
"Z"
+46
View File
@@ -0,0 +1,46 @@
; Copies a string from one place in Data Memory to another, reading through DP0
; and writing through DP1 at the same time.
;
; On a machine with one Data Pointer this loop needs a PSHD and a POPD on every
; pass to swap the pointer between the two regions. With two pointers it is just
; a load and a store.
;
; It also checks that a bare mnemonic still means DP0.
#Program
start:
SETD.0 Source ; DP0 walks the source.
SETD.1 Dest ; DP1 walks the destination.
copy:
LDA.0 ; Read a byte through DP0.
BRA copyDone ; A zero byte is the end of the string.
STA.1 ; Write it through DP1.
INCD.0
INCD.1
BRI copy
copyDone:
SETD Dest ; No selector, so this is DP0.
print:
LDA
BRA end
OUTA 0x00
INCD
BRI print
end:
INIA 0x0A
OUTA 0x00
HALT
#Data
Source:
"Two pointers, no stack shenanigans."
Dest:
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00