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
+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