Interrupt system implemented, some new programs.
This commit is contained in:
@@ -132,21 +132,7 @@ DecimalValue:
|
||||
0x00 ; The tens place.
|
||||
0x00 ; The hundreds place.
|
||||
|
||||
; For convenience, I'll pad this out so programs using this library store their data in a fresh page of memory.
|
||||
|
||||
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
; This library used to pad its data out to a whole page, so that anything including it
|
||||
; started on a page boundary. That cost every program 253 bytes of zeroes to give one
|
||||
; program a guarantee it could not ask for out loud. A program that needs a boundary now
|
||||
; writes #Align in front of the thing that needs it.
|
||||
|
||||
@@ -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
|
||||
+3
-1
@@ -28,7 +28,9 @@ PROGRAMS = \
|
||||
Fibonacci/32bitFibonacci.asm \
|
||||
primeSieve/8bitSieve.asm \
|
||||
primeSieve/16bitSegmentedSieve.asm \
|
||||
gameOfLife/16x16Life.asm
|
||||
primeSieve/16bitSegmentedSieveModern.asm \
|
||||
gameOfLife/16x16Life.asm \
|
||||
gameOfLife/16x16LifeModern.asm
|
||||
|
||||
BINARIES = $(PROGRAMS:%.asm=$(BUILD)/%.bin)
|
||||
DEPENDENCIES = $(BINARIES:.bin=.d)
|
||||
|
||||
@@ -157,7 +157,12 @@ printCandidateHex:
|
||||
|
||||
#Data
|
||||
|
||||
; print.asm deliberately pads its data to one page, so this begins at 0x0100.
|
||||
; 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
|
||||
|
||||
@@ -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
|
||||
@@ -43,7 +43,11 @@ start:
|
||||
|
||||
#Data
|
||||
|
||||
; The table of our prime candidates.
|
||||
; 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
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
; Tests a hardware interrupt delivered to a handler named in the Vector Segment.
|
||||
;
|
||||
; A device is named by the port it is plugged into, because that is what decides which
|
||||
; vector it arrives through. The test device on port 0x10 puts its line up when anything
|
||||
; is written to it.
|
||||
;
|
||||
; The device is asked for attention while the Interrupt Flag is down, so the line waits.
|
||||
; SIF lets it through, and the handler runs before the next instruction does.
|
||||
;
|
||||
; Correct output is:
|
||||
; O printed with the line up and the flag down
|
||||
; K printed by the handler
|
||||
; ! printed after RETI came back
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
CIF ; Hold devices off.
|
||||
INIA 0d1
|
||||
OUTA 0x10 ; The device asks. Its line goes up and stays up.
|
||||
|
||||
INIA 0d79 ; 'O'
|
||||
OUTA 0x00
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
|
||||
SIF ; Let it through. It is answered on the very next step.
|
||||
|
||||
INIA 0d33 ; '!'. Reached only because RETI came back here.
|
||||
OUTA 0x00
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
HALT
|
||||
|
||||
deviceHandler:
|
||||
INIA 0d75 ; 'K'
|
||||
OUTA 0x00
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
RETI
|
||||
|
||||
#Vectors
|
||||
|
||||
Device 0x10 deviceHandler
|
||||
@@ -0,0 +1,11 @@
|
||||
; #Align before either segment has been opened.
|
||||
;
|
||||
; The directive moves a cursor along, and outside a segment there is no cursor for
|
||||
; it to move, so this has to be an error rather than quietly doing nothing.
|
||||
|
||||
#Align 0x100
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
HALT
|
||||
@@ -0,0 +1,10 @@
|
||||
; #Align with no number after it.
|
||||
;
|
||||
; Without this check the next token is taken as the alignment, which would silently
|
||||
; align to whatever the following instruction happened to be worth.
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
HALT
|
||||
#Align
|
||||
@@ -0,0 +1,10 @@
|
||||
; Deliberately broken, to check that the assembler still catches it.
|
||||
; The file ends straight after the keyword, so there is no name to read. This
|
||||
; used to walk into whatever the array happened to hold and crash.
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
HALT
|
||||
|
||||
#Include
|
||||
@@ -0,0 +1,10 @@
|
||||
; SWI with nothing after it.
|
||||
;
|
||||
; Without the operand check this assembles, and SWI quietly takes the next
|
||||
; instruction as its vector number. Everything after it then shifts by a byte.
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
SWI
|
||||
HALT
|
||||
@@ -0,0 +1,14 @@
|
||||
; Deliberately broken, to check that the assembler still catches it.
|
||||
; The same name is defined twice, which used to be accepted silently, with every
|
||||
; reference quietly resolving to whichever definition came first.
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
BRI twice
|
||||
|
||||
twice:
|
||||
HALT
|
||||
|
||||
twice:
|
||||
HALT
|
||||
@@ -0,0 +1,20 @@
|
||||
; Two handlers claiming the same vector.
|
||||
;
|
||||
; Device lines name a port directly, so two of them can collide even though the
|
||||
; assembler numbers the named vectors itself.
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
HALT
|
||||
|
||||
firstHandler:
|
||||
RETI
|
||||
|
||||
secondHandler:
|
||||
RETI
|
||||
|
||||
#Vectors
|
||||
|
||||
Device 0x10 firstHandler
|
||||
Device 0x10 secondHandler
|
||||
@@ -0,0 +1,10 @@
|
||||
; SWI names something that was never given a handler.
|
||||
;
|
||||
; A vector name is not a label, so the usual "undefined label" error would be
|
||||
; misleading. It has to say that the name needs a #Vectors entry.
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
SWI neverDeclared
|
||||
HALT
|
||||
@@ -0,0 +1,77 @@
|
||||
; Tests BRD, the only branch whose destination is not written into the program.
|
||||
;
|
||||
; A table of addresses in the Data Segment is walked with one Data Pointer, each
|
||||
; entry is pulled out with LDD, and BRD jumps to it. That is dispatch: choosing
|
||||
; where to go from data rather than from a branch the assembler laid down.
|
||||
;
|
||||
; Correct output is:
|
||||
; one
|
||||
; two
|
||||
; three
|
||||
; done
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
SETD.0 Handlers ; DP0 walks the table of handler addresses.
|
||||
INIB 0d3 ; Three of them.
|
||||
|
||||
dispatchLoop:
|
||||
LDD.1.0 ; DP1 becomes the address of the next handler.
|
||||
BRD.1 ; Go there. The handler branches back to itself.
|
||||
|
||||
; Each handler prints its name and returns to the loop by hand. There is no CALL
|
||||
; here on purpose, so that what BRD does is the only thing under test.
|
||||
handlerOne:
|
||||
SETD.2 One
|
||||
CALL printDP2
|
||||
BRI nextHandler
|
||||
|
||||
handlerTwo:
|
||||
SETD.2 Two
|
||||
CALL printDP2
|
||||
BRI nextHandler
|
||||
|
||||
handlerThree:
|
||||
SETD.2 Three
|
||||
CALL printDP2
|
||||
BRI nextHandler
|
||||
|
||||
nextHandler:
|
||||
DPUP.0 0d02 ; Step over the two byte table entry.
|
||||
DECB
|
||||
BRB finished
|
||||
BRI dispatchLoop
|
||||
|
||||
finished:
|
||||
SETD.2 Done
|
||||
CALL printDP2
|
||||
HALT
|
||||
|
||||
printDP2:
|
||||
LDA.2
|
||||
BRA printDone
|
||||
OUTA 0x00
|
||||
INCD.2
|
||||
BRI printDP2
|
||||
printDone:
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
RET
|
||||
|
||||
#Data
|
||||
|
||||
One:
|
||||
"one"
|
||||
Two:
|
||||
"two"
|
||||
Three:
|
||||
"three"
|
||||
Done:
|
||||
"done"
|
||||
|
||||
; The dispatch table. Each name becomes the address of that handler.
|
||||
Handlers:
|
||||
handlerOne
|
||||
handlerTwo
|
||||
handlerThree
|
||||
@@ -0,0 +1,52 @@
|
||||
; Tests a fault handler that steps over the byte it could not decode and carries on.
|
||||
;
|
||||
; The frame holds the address of the offending byte rather than the one after it, so a
|
||||
; handler can see exactly what failed. The cost is that returning with a bare RETI meets
|
||||
; the same byte again, which is why this handler moves the saved address on by one
|
||||
; first. MVSD is what lets it reach the frame at all.
|
||||
;
|
||||
; 0xFE is not an instruction. Writing it as a literal is the only way past the
|
||||
; assembler, which is what makes a program containing one buildable.
|
||||
;
|
||||
; Correct output is:
|
||||
; O printed before the byte that fails
|
||||
; K printed after the handler stepped over it
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
INIA 0d79 ; 'O'
|
||||
OUTA 0x00
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
|
||||
0xFE ; Not an instruction. The handler steps over this.
|
||||
|
||||
INIA 0d75 ; 'K'. Reached only because the handler moved the address on.
|
||||
OUTA 0x00
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
HALT
|
||||
|
||||
faultHandler:
|
||||
; The frame sits above the Stack Pointer, which points at the next free slot. The
|
||||
; address to resume at went on first, so it is furthest up: high byte 13 above the
|
||||
; Stack Pointer, low byte 14 above.
|
||||
MVSD.0
|
||||
DPUP.0 0d14
|
||||
LDA.0
|
||||
INCA ; Step past the one byte that failed.
|
||||
STA.0
|
||||
BRC carried ; The low byte wrapped, so the high byte needs the carry.
|
||||
RETI
|
||||
|
||||
carried:
|
||||
DPDN.0 0d01
|
||||
LDA.0
|
||||
INCA
|
||||
STA.0
|
||||
RETI
|
||||
|
||||
#Vectors
|
||||
|
||||
BadOpcode faultHandler
|
||||
@@ -0,0 +1,15 @@
|
||||
; Tests that the CPU stops when it meets a byte it cannot decode.
|
||||
;
|
||||
; 0xFE is not an instruction. Placing it in the Program Segment as a literal gets
|
||||
; it past the assembler, which is the only way to build a program containing one.
|
||||
;
|
||||
; The CPU should raise the Fault Flag, halt, and leave the Program Counter pointing
|
||||
; at the offending byte rather than stepping over it and carrying on. The emulator
|
||||
; then reports what it was and where, and exits non zero.
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
INIA 0d65 ; Something harmless first, so the fault is not at address zero.
|
||||
0xFE ; Not an instruction.
|
||||
HALT ; Never reached.
|
||||
@@ -0,0 +1,72 @@
|
||||
; The worked example from the SplitBit Assembler Manual, kept here so that the manual
|
||||
; cannot quietly stop being true. If this test changes, the manual changes with it.
|
||||
;
|
||||
; Correct output is:
|
||||
; ready
|
||||
; trap
|
||||
; device
|
||||
|
||||
; Interrupt handling from all three directions.
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
CIF ; Hold devices off while we set up.
|
||||
SETD.0 Greeting
|
||||
CALL printString
|
||||
|
||||
SWI announce ; A trap of our own, reached by name.
|
||||
|
||||
INIA 0d1
|
||||
OUTA 0x10 ; Ask the test device for attention. Its line goes up.
|
||||
SIF ; Let it through. It is answered before the next instruction.
|
||||
|
||||
HALT
|
||||
|
||||
; A trap. It is entered with a full frame, so it may use any register it likes
|
||||
; without agreeing anything with the code it interrupted.
|
||||
announce:
|
||||
SETD.0 Trapped
|
||||
CALL printString
|
||||
RETI
|
||||
|
||||
; The device handler. Reached because the device sits on port 0x10.
|
||||
deviceReady:
|
||||
SETD.0 Device
|
||||
CALL printString
|
||||
RETI
|
||||
|
||||
; The fault handler. It reports and stops, rather than trying to carry on.
|
||||
reportFault:
|
||||
SETD.0 Broken
|
||||
CALL printString
|
||||
HALT
|
||||
|
||||
printString: ; Expects DP0 to be set to the beginning of the string.
|
||||
LDA.0
|
||||
BRA printDone
|
||||
OUTA 0x00
|
||||
INCD.0
|
||||
BRI printString
|
||||
printDone:
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
RET
|
||||
|
||||
#Data
|
||||
|
||||
Greeting:
|
||||
"ready"
|
||||
Trapped:
|
||||
"trap"
|
||||
Device:
|
||||
"device"
|
||||
Broken:
|
||||
"bad opcode"
|
||||
|
||||
#Vectors
|
||||
|
||||
Boot start ; Begin here rather than at the first byte.
|
||||
BadOpcode reportFault
|
||||
announce announce ; A name of our own. The assembler numbers it.
|
||||
Device 0x10 deviceReady ; Named by the port, because that is what decides it.
|
||||
@@ -0,0 +1,45 @@
|
||||
; Tests SIF and CIF, the Interrupt Flag.
|
||||
;
|
||||
; Nothing reads the Interrupt Flag yet, so what this proves is that the two
|
||||
; instructions decode, execute, and leave everything else exactly as they found
|
||||
; it. The Carry Flag is the part worth checking, because it lives in the same
|
||||
; Status register and a careless mask would take it out.
|
||||
;
|
||||
; Correct output is:
|
||||
; OKC
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
; Load two registers, run the new instructions across them, and prove that
|
||||
; nothing moved.
|
||||
INIA 0d79 ; 'O'
|
||||
INIB 0d75 ; 'K'
|
||||
SIF
|
||||
CIF
|
||||
SIF
|
||||
OUTA 0x00 ; Still 'O'.
|
||||
OUTB 0x00 ; Still 'K'.
|
||||
|
||||
; Now set the Carry Flag, and work the Interrupt Flag around it.
|
||||
CCF
|
||||
INIA 0xFF
|
||||
INIB 0x01
|
||||
ADD ; Q = 0, and the Carry Flag is set.
|
||||
SIF
|
||||
CIF
|
||||
BRC carryHeld
|
||||
|
||||
; Falling through here means one flag trampled the other.
|
||||
INIA 0d88 ; 'X'
|
||||
OUTA 0x00
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
HALT
|
||||
|
||||
carryHeld:
|
||||
INIA 0d67 ; 'C'
|
||||
OUTA 0x00
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
HALT
|
||||
@@ -0,0 +1,44 @@
|
||||
; Tests that the Interrupt Flag holds a device off, and that the device is still
|
||||
; waiting once the flag goes back up.
|
||||
;
|
||||
; The test device on port 0x10 puts its own line up when anything is written to it. A
|
||||
; device on port N interrupts on N, so this one arrives on hardware vector 16.
|
||||
;
|
||||
; Nothing is installed at that vector, so answering it is a fault. That is the point:
|
||||
; the fault is proof the line was answered, and where it appears in the output is proof
|
||||
; of when. The letters are printed to mark how far the program got.
|
||||
;
|
||||
; Correct output is:
|
||||
; M the device has asked, and the flag is down, so nothing has happened
|
||||
; S still nothing, several instructions later
|
||||
; then a fault naming hardware vector 16, raised after SIF and not before.
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
CIF ; Hold devices off.
|
||||
INIA 0d1
|
||||
OUTA 0x10 ; The device asks for attention. Its line goes up and stays up.
|
||||
|
||||
INIA 0d77 ; 'M', printed with the line still up and the flag still down.
|
||||
OUTA 0x00
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
|
||||
NOP ; Several instructions pass and the line is still not answered.
|
||||
NOP
|
||||
NOP
|
||||
|
||||
INIA 0d83 ; 'S'
|
||||
OUTA 0x00
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
|
||||
SIF ; Now let it through. The line is answered on the very next step.
|
||||
|
||||
; Never reached.
|
||||
INIA 0d88 ; 'X'
|
||||
OUTA 0x00
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
HALT
|
||||
@@ -0,0 +1,41 @@
|
||||
; Tests MVQA and MVQB.
|
||||
;
|
||||
; Every ALU result lands in Q, and Q is not an ALU operand. Without these two
|
||||
; instructions the only way to use a result in the next sum is to store it into
|
||||
; Data Memory and load it back, which costs two instructions and a Data Pointer
|
||||
; that has to be pointing somewhere sensible.
|
||||
;
|
||||
; Correct output is:
|
||||
; AAA
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
CCF
|
||||
INIA 0d60
|
||||
INIB 0d5
|
||||
ADD ; Q = 65, which is 'A'.
|
||||
MVQA ; A = 65
|
||||
OUTA 0x00
|
||||
MVQB ; And B, from the same result.
|
||||
OUTB 0x00
|
||||
|
||||
; A running total kept entirely in registers, which is the thing that was not
|
||||
; possible before. Nothing here touches Data Memory at all.
|
||||
CCF
|
||||
RSTA
|
||||
INIB 0d1
|
||||
ADD ; Q = 1
|
||||
MVQA
|
||||
ADD ; Q = 2
|
||||
MVQA
|
||||
ADD ; Q = 3
|
||||
MVQA
|
||||
INIB 0d62
|
||||
ADD ; Q = 65 again
|
||||
MVQA
|
||||
OUTA 0x00
|
||||
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
HALT
|
||||
@@ -0,0 +1,43 @@
|
||||
; Tests #Align and #Reserve by printing the addresses they produce.
|
||||
;
|
||||
; Both directives only move the cursor along, so what they do is entirely visible in
|
||||
; where the labels after them land. The program pushes each pointer and prints the two
|
||||
; bytes of its address, which is the only way a SplitBit program can look at one.
|
||||
;
|
||||
; Aligned is asked for on a page boundary, so its low byte has to be 0x00. Reserved
|
||||
; follows one byte of data and a reservation of 0x30, so it lands 0x31 further on.
|
||||
;
|
||||
; Correct output is:
|
||||
; 00 31
|
||||
|
||||
#Include print.asm
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
; The aligned label. Only the low byte is interesting: a page boundary means zero.
|
||||
SETD.0 Aligned
|
||||
PSHD.0 ; High byte, then low, so the low byte comes off first.
|
||||
POPA
|
||||
POPB
|
||||
CALL printByteHex
|
||||
CALL blankSpace
|
||||
|
||||
; The reserved region. Reserved sits one byte of data plus 0x30 reserved bytes past
|
||||
; Aligned, so its low byte says how far the reservation moved the cursor.
|
||||
SETD.0 Reserved
|
||||
PSHD.0
|
||||
POPA
|
||||
POPB
|
||||
CALL printByteHex
|
||||
CALL lineFeed
|
||||
HALT
|
||||
|
||||
#Data
|
||||
|
||||
#Align 0x100
|
||||
Aligned:
|
||||
0x41 ; One byte of real data, so the cursor is one past the boundary.
|
||||
#Reserve 0x30
|
||||
Reserved:
|
||||
0x42
|
||||
@@ -0,0 +1,36 @@
|
||||
; Tests MVSD, which copies the Stack Pointer into a Data Pointer.
|
||||
;
|
||||
; The Stack Pointer still cannot be written, so this does not let a program move the
|
||||
; Stack. It lets a program find it, which is what reading anything already on the Stack
|
||||
; requires. Without it, the manual's claim that a Data Pointer can be aimed at the Stack
|
||||
; is not something a program can actually act on: there is no way to learn where the
|
||||
; Stack is without already knowing.
|
||||
;
|
||||
; An interrupt handler needs this to reach its own frame, which is how a fault handler
|
||||
; steps over the byte that failed and carries on.
|
||||
;
|
||||
; Correct output is:
|
||||
; OK
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
; Push two bytes, then go looking for them.
|
||||
INIA 0d79 ; 'O'
|
||||
PSHA
|
||||
INIA 0d75 ; 'K'
|
||||
PSHA
|
||||
|
||||
; The Stack Pointer points at the next free slot, so the byte pushed last sits one
|
||||
; above it, and the one before that sits two above.
|
||||
MVSD.0
|
||||
DPUP.0 0d01
|
||||
LDA.0 ; 'K', the last one pushed.
|
||||
INCD.0
|
||||
LDB.0 ; 'O', the one before it.
|
||||
|
||||
OUTB 0x00
|
||||
OUTA 0x00
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
HALT
|
||||
@@ -0,0 +1,30 @@
|
||||
; Tests a software interrupt that names a vector with nothing installed in it.
|
||||
;
|
||||
; Until the assembler can lay down a vector table, every entry reads as zero, and zero
|
||||
; means no handler. Dispatching through one has to stop the machine and say which
|
||||
; vector was empty, rather than jumping to the bottom of Program Memory and running
|
||||
; whatever happens to be sitting there.
|
||||
;
|
||||
; The letter is printed first so that it is obvious the program ran at all, and that it
|
||||
; stopped exactly where it should have.
|
||||
;
|
||||
; Correct output is:
|
||||
; O
|
||||
; a fault naming software vector 20, and a non zero exit.
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
INIA 0d79 ; 'O'
|
||||
OUTA 0x00
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
|
||||
SWI 0d20 ; Nothing is installed here.
|
||||
|
||||
; Never reached. If the machine ever prints this, the empty vector was taken.
|
||||
INIA 0d88 ; 'X'
|
||||
OUTA 0x00
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
HALT
|
||||
@@ -0,0 +1,94 @@
|
||||
; Tests the Vector Segment: a Boot Vector pointing somewhere other than the start of the
|
||||
; program, and a software interrupt the program names for itself.
|
||||
;
|
||||
; The decoy sits at address 0x0000, where execution would begin if the Boot Vector were
|
||||
; not obeyed. It prints an X, so if an X ever appears the vector was ignored.
|
||||
;
|
||||
; The handler tramples every register it can reach. Everything printed afterwards comes
|
||||
; out of the frame, which is the point: an interrupt gives back what it borrowed, and
|
||||
; unlike a subroutine that includes Q and Data Pointer 3.
|
||||
;
|
||||
; Correct output is:
|
||||
; OK!
|
||||
; good
|
||||
; AFTER
|
||||
|
||||
#Program
|
||||
|
||||
decoy:
|
||||
INIA 0d88 ; 'X'. Never reached.
|
||||
OUTA 0x00
|
||||
HALT
|
||||
|
||||
realStart:
|
||||
CCF
|
||||
INIA 0d79 ; 'O'
|
||||
INIB 0d75 ; 'K'
|
||||
SETD.0 Good ; DP0 is preserved across a CALL as well.
|
||||
SETD.3 After ; DP3 is not, but an interrupt has to give it back anyway.
|
||||
|
||||
SWI stampTrap
|
||||
|
||||
OUTA 0x00 ; 'O'
|
||||
OUTB 0x00 ; 'K'
|
||||
BRC carryLost
|
||||
INIA 0d33 ; '!', so the Status register came back too.
|
||||
OUTA 0x00
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
|
||||
CALL printDP0
|
||||
CALL printDP3
|
||||
HALT
|
||||
|
||||
carryLost:
|
||||
INIA 0d63 ; '?'
|
||||
OUTA 0x00
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
HALT
|
||||
|
||||
; The handler. Nothing it does to a register should survive.
|
||||
stampRegisters:
|
||||
INIA 0xFF
|
||||
INIB 0x01
|
||||
ADD ; Q is stamped, and the Carry Flag is set.
|
||||
SETD.0 Bad
|
||||
SETD.3 Bad
|
||||
RETI
|
||||
|
||||
printDP0:
|
||||
LDA.0
|
||||
BRA done0
|
||||
OUTA 0x00
|
||||
INCD.0
|
||||
BRI printDP0
|
||||
done0:
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
RET
|
||||
|
||||
printDP3:
|
||||
LDA.3
|
||||
BRA done3
|
||||
OUTA 0x00
|
||||
INCD.3
|
||||
BRI printDP3
|
||||
done3:
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
RET
|
||||
|
||||
#Data
|
||||
|
||||
Good:
|
||||
"good"
|
||||
Bad:
|
||||
"bad"
|
||||
After:
|
||||
"AFTER"
|
||||
|
||||
#Vectors
|
||||
|
||||
Boot realStart
|
||||
stampTrap stampRegisters
|
||||
Reference in New Issue
Block a user