Interrupt system implemented, some new programs.

This commit is contained in:
Anachronaut
2026-08-15 00:44:13 -04:00
parent 638b68b25c
commit 6d1966d500
79 changed files with 2778 additions and 88 deletions
+4 -18
View File
@@ -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.
+266
View File
@@ -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
View File
@@ -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)
+6 -1
View File
@@ -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
+5 -1
View File
@@ -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
+44
View File
@@ -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
+77
View File
@@ -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
+52
View File
@@ -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
+15
View File
@@ -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
+44
View File
@@ -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
+41
View File
@@ -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
+43
View File
@@ -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
+30
View File
@@ -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
+94
View File
@@ -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