Memory controller implemented.
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
; Running off the end of a bank.
|
||||
;
|
||||
; Bank 2 is the bank table: 256 banks at eight bytes each, so 2048 bytes exactly. The
|
||||
; last byte is at 0x07FF and there is nothing at 0x0800. A bank knowing how big it is
|
||||
; means the difference shows up as a fault at the instruction that overran, instead of
|
||||
; quietly reading whatever happened to be next in the emulator's memory.
|
||||
;
|
||||
; Nothing is installed for BankFault here, so the machine stops.
|
||||
;
|
||||
; Correct output is:
|
||||
; O the last byte of the bank was read without complaint
|
||||
; a fault naming port 233, which is the controller's Data port, and a non zero exit.
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
INIA 0d2
|
||||
OUTA 0xE0 ; SourceBank = 2
|
||||
INIA 0x07
|
||||
OUTA 0xE1
|
||||
INIA 0xFF
|
||||
OUTA 0xE2 ; 0x07FF, the last byte there is
|
||||
|
||||
INA 0xE9 ; Fine.
|
||||
INIA 0d79 ; 'O'
|
||||
OUTA 0x00
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
|
||||
INIA 0x08
|
||||
OUTA 0xE1
|
||||
RSTA
|
||||
OUTA 0xE2 ; 0x0800, one past the end
|
||||
|
||||
INA 0xE9 ; Refused: BankFault.
|
||||
|
||||
; Never reached.
|
||||
INIA 0d88 ; 'X'
|
||||
OUTA 0x00
|
||||
HALT
|
||||
@@ -0,0 +1,210 @@
|
||||
; Blits and fills.
|
||||
;
|
||||
; A blit moves a run of bytes between any two banks, including from one place in a bank
|
||||
; to another. A fill writes one byte across a run. Both are instantaneous: waiting
|
||||
; belongs to a peripheral that has something to wait for, not to the moving of bytes.
|
||||
;
|
||||
; Everything a transfer will touch is checked before any of it moves, so a transfer that
|
||||
; would run out of bank refuses outright rather than stopping halfway and leaving memory
|
||||
; in a state nobody asked for. The last case here proves that: the blit is refused, and
|
||||
; the bytes it would have written are still what they were.
|
||||
;
|
||||
; Correct output is:
|
||||
; blitted copied from one place in Data Memory to another
|
||||
; .... filled with a byte of our choosing
|
||||
; DE AD blitted from Data Memory into Program Memory
|
||||
; untouched a refused blit moved nothing at all
|
||||
; ABABCDEFGH an overlapping blit slid the bytes rather than repeating one
|
||||
|
||||
#Include print.asm
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
; ---- A blit inside Data Memory, which is what a memcpy is here. ----
|
||||
SETD.0 Message
|
||||
CALL aimSource
|
||||
SETD.0 Landing
|
||||
CALL aimDest
|
||||
INIA 0d8
|
||||
CALL setLength ; "blitted" and its terminator.
|
||||
INIA 0x01
|
||||
OUTA 0xE8 ; Blit.
|
||||
|
||||
SETD.0 Landing
|
||||
CALL printString
|
||||
CALL lineFeed
|
||||
|
||||
; ---- A fill, with a byte of our choosing rather than only zero. ----
|
||||
SETD.0 Landing
|
||||
CALL aimDest
|
||||
INIA 0x2E ; '.', into SourceLow, which is where a fill takes its byte.
|
||||
OUTA 0xE2
|
||||
INIA 0d4
|
||||
CALL setLength
|
||||
INIA 0x02
|
||||
OUTA 0xE8 ; Fill.
|
||||
|
||||
; Terminate it so printString knows where to stop.
|
||||
SETD.0 Landing
|
||||
DPUP.0 0d04
|
||||
RSTA
|
||||
STA.0
|
||||
SETD.0 Landing
|
||||
CALL printString
|
||||
CALL lineFeed
|
||||
|
||||
; ---- Data Memory into Program Memory. This is what a loader does. ----
|
||||
SETD.0 Code
|
||||
CALL aimSource
|
||||
SETD.0 Target
|
||||
CALL aimDestProgram
|
||||
INIA 0d2
|
||||
CALL setLength
|
||||
INIA 0x01
|
||||
OUTA 0xE8 ; Blit.
|
||||
|
||||
; Read it back out of Program Memory to prove it landed.
|
||||
SETD.0 Target
|
||||
CALL aimSourceProgram
|
||||
CALL readAndPrint
|
||||
CALL readAndPrint
|
||||
CALL lineFeed
|
||||
|
||||
; ---- A blit that would run past the end of bank 2. ----
|
||||
; Bank 2 is 2048 bytes. Starting at 2040 and asking for 16 does not fit, so the whole
|
||||
; thing is refused and Landing keeps what it already had.
|
||||
SETD.0 Untouched
|
||||
CALL aimSource
|
||||
SETD.0 Landing
|
||||
CALL aimDest
|
||||
INIA 0d10
|
||||
CALL setLength
|
||||
INIA 0x01
|
||||
OUTA 0xE8 ; This one works, so Landing says "untouched".
|
||||
|
||||
INIA 0d2
|
||||
OUTA 0xE0 ; SourceBank = 2, the bank table.
|
||||
INIA 0x07
|
||||
OUTA 0xE1
|
||||
INIA 0xF8
|
||||
OUTA 0xE2 ; 0x07F8, eight bytes from the end.
|
||||
SETD.0 Landing
|
||||
CALL aimDest
|
||||
INIA 0d16
|
||||
CALL setLength
|
||||
INIA 0x01
|
||||
OUTA 0xE8 ; Refused: it would read past the end of bank 2.
|
||||
|
||||
SETD.0 Landing
|
||||
CALL printString
|
||||
CALL lineFeed
|
||||
|
||||
; ---- A blit whose source and destination overlap. ----
|
||||
; Sliding a run of bytes along inside its own bank is an ordinary thing to want, so it
|
||||
; works rather than quietly repeating the first byte the way a naive forward copy does.
|
||||
SETD.0 Slide
|
||||
CALL aimSource
|
||||
SETD.0 Slide
|
||||
DPUP.0 0d02
|
||||
CALL aimDest
|
||||
INIA 0d8
|
||||
CALL setLength
|
||||
INIA 0x01
|
||||
OUTA 0xE8
|
||||
|
||||
SETD.0 Slide
|
||||
CALL printString
|
||||
CALL lineFeed
|
||||
HALT
|
||||
|
||||
; ---- Helpers. DP0 holds the address to aim at. ----
|
||||
|
||||
aimSource:
|
||||
INIA 0d1 ; Bank 1, Data Memory.
|
||||
OUTA 0xE0
|
||||
BRI aimSourceAddress
|
||||
aimSourceProgram:
|
||||
RSTA ; Bank 0, Program Memory.
|
||||
OUTA 0xE0
|
||||
aimSourceAddress:
|
||||
PSHD.0
|
||||
POPA
|
||||
POPB
|
||||
OUTB 0xE1
|
||||
OUTA 0xE2
|
||||
RET
|
||||
|
||||
aimDest:
|
||||
INIA 0d1 ; Bank 1, Data Memory.
|
||||
OUTA 0xE3
|
||||
BRI aimDestAddress
|
||||
aimDestProgram:
|
||||
RSTA ; Bank 0, Program Memory.
|
||||
OUTA 0xE3
|
||||
aimDestAddress:
|
||||
PSHD.0
|
||||
POPA
|
||||
POPB
|
||||
OUTB 0xE4
|
||||
OUTA 0xE5
|
||||
RET
|
||||
|
||||
; A is the length, which is never more than a byte in this program.
|
||||
setLength:
|
||||
PSHA
|
||||
RSTA
|
||||
OUTA 0xE6
|
||||
POPA
|
||||
OUTA 0xE7
|
||||
RET
|
||||
|
||||
readAndPrint:
|
||||
INA 0xE9
|
||||
CALL printByteHex
|
||||
CALL blankSpace
|
||||
RET
|
||||
|
||||
; The refused blit is caught so the program can carry on and show that nothing moved.
|
||||
; The command write is two bytes, an opcode and a port, so stepping by two gets past it.
|
||||
refusedHandler:
|
||||
MVSD.0
|
||||
DPUP.0 0d14
|
||||
LDA.0
|
||||
CCF
|
||||
INIB 0d2
|
||||
ADD
|
||||
MVQA
|
||||
STA.0
|
||||
BRC refusedCarried
|
||||
RETI
|
||||
refusedCarried:
|
||||
DPDN.0 0d01
|
||||
LDA.0
|
||||
INCA
|
||||
STA.0
|
||||
RETI
|
||||
|
||||
; Never executed, only written over.
|
||||
Target:
|
||||
0x00 0x00
|
||||
|
||||
#Data
|
||||
|
||||
Message:
|
||||
"blitted"
|
||||
Untouched:
|
||||
"untouched"
|
||||
Code:
|
||||
0xDE 0xAD
|
||||
|
||||
Landing:
|
||||
#Reserve 0d16
|
||||
|
||||
Slide:
|
||||
"ABCDEFGH"
|
||||
#Reserve 0d4
|
||||
|
||||
#Vectors
|
||||
|
||||
BankFault refusedHandler
|
||||
@@ -0,0 +1,66 @@
|
||||
; Reads Program Memory through the memory controller.
|
||||
;
|
||||
; Nothing on SplitBit could do this before. The CPU cannot reach its own code, which is
|
||||
; what a Harvard machine is, and this is the device that can.
|
||||
;
|
||||
; The bytes read back are a marker written into the Program Segment after the HALT, where
|
||||
; nothing executes them. The controller is aimed at it using the label's own address, so
|
||||
; this checks the controller against what the source says rather than against a guess
|
||||
; about where the assembler put things.
|
||||
;
|
||||
; The address steps on by itself, so reading a run of bytes is a loop over one
|
||||
; instruction rather than four.
|
||||
;
|
||||
; Correct output is:
|
||||
; DE AD BE EF the marker, read out of Program Memory
|
||||
; 01 FF bank 0's record in bank 2: present, owned by the machine
|
||||
; 00 00 and its capacity, where zero means the whole 64K
|
||||
|
||||
#Include print.asm
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
; Aim the controller at the marker, using the marker's own address.
|
||||
SETD.0 Marker
|
||||
PSHD.0 ; High byte, then low, so the low byte comes off first.
|
||||
POPA
|
||||
POPB
|
||||
PSHA ; Keep the low byte while A is needed for something else.
|
||||
|
||||
RSTA
|
||||
OUTA 0xE0 ; SourceBank = 0, which is Program Memory.
|
||||
OUTB 0xE1 ; SourceHigh
|
||||
POPA
|
||||
OUTA 0xE2 ; SourceLow
|
||||
|
||||
CALL readAndPrint
|
||||
CALL readAndPrint
|
||||
CALL readAndPrint
|
||||
CALL readAndPrint
|
||||
CALL lineFeed
|
||||
|
||||
; Now the bank table, which is bank 2. Bank 0's record comes first.
|
||||
INIA 0d2
|
||||
OUTA 0xE0
|
||||
RSTA
|
||||
OUTA 0xE1
|
||||
OUTA 0xE2
|
||||
|
||||
CALL readAndPrint ; Flags: present.
|
||||
CALL readAndPrint ; Owner: the machine itself.
|
||||
CALL lineFeed
|
||||
CALL readAndPrint ; Capacity, high byte.
|
||||
CALL readAndPrint ; Capacity, low byte.
|
||||
CALL lineFeed
|
||||
HALT
|
||||
|
||||
readAndPrint:
|
||||
INA 0xE9
|
||||
CALL printByteHex
|
||||
CALL blankSpace
|
||||
RET
|
||||
|
||||
; Never executed. It is here to be read rather than run.
|
||||
Marker:
|
||||
0xDE 0xAD 0xBE 0xEF
|
||||
@@ -0,0 +1,127 @@
|
||||
; Writes Program Memory through the controller, and gets turned away from what it may
|
||||
; not touch.
|
||||
;
|
||||
; Writing code is the thing SplitBit's instruction set deliberately cannot do. The
|
||||
; controller can, which is what makes a loader possible. The marker below is written and
|
||||
; then read back through the controller, so the change is observed rather than assumed.
|
||||
;
|
||||
; The two refusals are the other half. Bank 2 is the bank table and is read only, so
|
||||
; writing to it is a GuardViolation. Bank 200 has nothing registered in it, so naming it
|
||||
; is a BankFault. Both handlers step the saved address past the instruction that was
|
||||
; refused, the way any handler carrying on past a fault has to.
|
||||
;
|
||||
; Correct output is:
|
||||
; AA BB the marker after being written through the controller
|
||||
; readonly
|
||||
; nobank
|
||||
; done
|
||||
|
||||
#Include print.asm
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
; Aim both ends of the controller at the marker.
|
||||
SETD.0 Marker
|
||||
PSHD.0
|
||||
POPA
|
||||
POPB
|
||||
PSHA
|
||||
PSHB
|
||||
|
||||
RSTA
|
||||
OUTA 0xE0 ; SourceBank = 0
|
||||
OUTA 0xE3 ; DestBank = 0
|
||||
POPB
|
||||
OUTB 0xE1 ; SourceHigh
|
||||
OUTB 0xE4 ; DestHigh
|
||||
POPA
|
||||
OUTA 0xE2 ; SourceLow
|
||||
OUTA 0xE5 ; DestLow
|
||||
|
||||
; Write two bytes over the marker. This is Program Memory being changed at run time.
|
||||
INIA 0xAA
|
||||
OUTA 0xE9
|
||||
INIA 0xBB
|
||||
OUTA 0xE9
|
||||
|
||||
; Read them back the same way, to prove they landed.
|
||||
CALL readAndPrint
|
||||
CALL readAndPrint
|
||||
CALL lineFeed
|
||||
|
||||
; Bank 2 is the bank table, and it is read only.
|
||||
INIA 0d2
|
||||
OUTA 0xE3
|
||||
RSTA
|
||||
OUTA 0xE4
|
||||
OUTA 0xE5
|
||||
INIA 0d1
|
||||
OUTA 0xE9 ; Refused: GuardViolation.
|
||||
|
||||
; Bank 200 has nothing in it.
|
||||
INIA 0d200
|
||||
OUTA 0xE3
|
||||
INIA 0d1
|
||||
OUTA 0xE9 ; Refused: BankFault.
|
||||
|
||||
SETD.0 Done
|
||||
CALL printString
|
||||
CALL lineFeed
|
||||
HALT
|
||||
|
||||
readAndPrint:
|
||||
INA 0xE9
|
||||
CALL printByteHex
|
||||
CALL blankSpace
|
||||
RET
|
||||
|
||||
readOnlyHandler:
|
||||
SETD.0 ReadOnly
|
||||
CALL printString
|
||||
CALL lineFeed
|
||||
BRI stepPastRefusal
|
||||
|
||||
noBankHandler:
|
||||
SETD.0 NoBank
|
||||
CALL printString
|
||||
CALL lineFeed
|
||||
BRI stepPastRefusal
|
||||
|
||||
; Steps the saved address past the refused instruction. OUT is two bytes, an opcode and
|
||||
; a port, so two is what it takes.
|
||||
stepPastRefusal:
|
||||
MVSD.0
|
||||
DPUP.0 0d14
|
||||
LDA.0
|
||||
CCF
|
||||
INIB 0d2
|
||||
ADD
|
||||
MVQA
|
||||
STA.0
|
||||
BRC carried
|
||||
RETI
|
||||
carried:
|
||||
DPDN.0 0d01
|
||||
LDA.0
|
||||
INCA
|
||||
STA.0
|
||||
RETI
|
||||
|
||||
; Never executed.
|
||||
Marker:
|
||||
0x00 0x00
|
||||
|
||||
#Data
|
||||
|
||||
ReadOnly:
|
||||
"readonly"
|
||||
NoBank:
|
||||
"nobank"
|
||||
Done:
|
||||
"done"
|
||||
|
||||
#Vectors
|
||||
|
||||
GuardViolation readOnlyHandler
|
||||
BankFault noBankHandler
|
||||
@@ -0,0 +1,172 @@
|
||||
; The fence.
|
||||
;
|
||||
; A guarded range is a fence rather than a wall. Any program may raise one and any
|
||||
; program may lower it, so nobody is ever told no. What it stops is walking into
|
||||
; something by accident, which is the failure that costs an afternoon: without it, code
|
||||
; that gets walked over is found much later, when the wreckage is finally executed and
|
||||
; the evidence is long gone.
|
||||
;
|
||||
; The handler reads the controller's destination registers to say where the refused write
|
||||
; was aimed. That is the whole point of catching it at the instruction that did it: the
|
||||
; address is still sitting there to be read.
|
||||
;
|
||||
; The addresses here are absolute rather than labels, so that this says the same thing no
|
||||
; matter what else ends up in the Data Segment. Data Memory is 64K and this program uses
|
||||
; a hundred bytes of it, so 0x0200 is empty ground.
|
||||
;
|
||||
; Correct output is:
|
||||
; outside ok a write just below the fence went through
|
||||
; caught 0200 a write inside it was refused, and the handler says where
|
||||
; caught 01FC a blit that merely clipped it was refused whole
|
||||
; read ok reading inside the fence is allowed: it guards writing
|
||||
; lowered ok after GuardOff the same write goes through
|
||||
; fenced 05 the bank table shows bank 1 present and fenced
|
||||
|
||||
#Include print.asm
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
; Fence off 0x0200 to 0x020F in Data Memory.
|
||||
INIA 0d1
|
||||
OUTA 0xEB ; GuardBank = 1
|
||||
INIA 0x02
|
||||
OUTA 0xEC
|
||||
RSTA
|
||||
OUTA 0xED ; GuardStart = 0x0200
|
||||
INIA 0x02
|
||||
OUTA 0xEE
|
||||
INIA 0x0F
|
||||
OUTA 0xEF ; GuardEnd = 0x020F
|
||||
INIA 0x10
|
||||
OUTA 0xE8 ; GuardOn
|
||||
|
||||
; Just below the fence is ordinary ground.
|
||||
INIA 0x01
|
||||
OUTA 0xE4
|
||||
INIA 0xFF
|
||||
OUTA 0xE5 ; Dest = 0x01FF
|
||||
INIA 0d1
|
||||
OUTA 0xE3 ; DestBank = 1
|
||||
INIA 0x41
|
||||
OUTA 0xE9
|
||||
SETD.0 OutsideOk
|
||||
CALL say
|
||||
|
||||
; Inside it is not.
|
||||
INIA 0x02
|
||||
OUTA 0xE4
|
||||
RSTA
|
||||
OUTA 0xE5 ; Dest = 0x0200
|
||||
INIA 0x42
|
||||
OUTA 0xE9 ; Refused.
|
||||
|
||||
; A blit from 0x01FC for eight bytes runs to 0x0203, so it clips the fence. It is
|
||||
; refused whole rather than doing the four bytes that would have fitted.
|
||||
INIA 0x01
|
||||
OUTA 0xE4
|
||||
INIA 0xFC
|
||||
OUTA 0xE5 ; Dest = 0x01FC
|
||||
INIA 0d1
|
||||
OUTA 0xE0
|
||||
RSTA
|
||||
OUTA 0xE1
|
||||
OUTA 0xE2 ; Source = bank 1, 0x0000
|
||||
OUTA 0xE6
|
||||
INIA 0d8
|
||||
OUTA 0xE7 ; Length = 8
|
||||
INIA 0x01
|
||||
OUTA 0xE8 ; Refused.
|
||||
|
||||
; Reading inside a fence is allowed. A debugger has to be able to see what is being
|
||||
; protected.
|
||||
INIA 0d1
|
||||
OUTA 0xE0
|
||||
INIA 0x02
|
||||
OUTA 0xE1
|
||||
RSTA
|
||||
OUTA 0xE2 ; Source = 0x0200
|
||||
INA 0xE9
|
||||
SETD.0 ReadOk
|
||||
CALL say
|
||||
|
||||
; Lower it, and the write that was refused goes through.
|
||||
INIA 0x11
|
||||
OUTA 0xE8 ; GuardOff
|
||||
INIA 0x02
|
||||
OUTA 0xE4
|
||||
RSTA
|
||||
OUTA 0xE5
|
||||
INIA 0x43
|
||||
OUTA 0xE9
|
||||
SETD.0 LoweredOk
|
||||
CALL say
|
||||
|
||||
; Raise it again, and read the flags back out of the bank table, where a fence is
|
||||
; published along with everything else about a bank.
|
||||
INIA 0x10
|
||||
OUTA 0xE8
|
||||
INIA 0d2
|
||||
OUTA 0xE0
|
||||
RSTA
|
||||
OUTA 0xE1
|
||||
INIA 0d8
|
||||
OUTA 0xE2 ; Bank 1's record begins at 8.
|
||||
SETD.0 Fenced
|
||||
CALL printString
|
||||
CALL blankSpace
|
||||
INA 0xE9
|
||||
CALL printByteHex
|
||||
CALL lineFeed
|
||||
HALT
|
||||
|
||||
; Says where the refused write was aimed, by reading the controller's own registers.
|
||||
guardHandler:
|
||||
SETD.0 Caught
|
||||
CALL printString
|
||||
CALL blankSpace
|
||||
INA 0xE4 ; DestHigh
|
||||
CALL printByteHex
|
||||
INA 0xE5 ; DestLow
|
||||
CALL printByteHex
|
||||
CALL lineFeed
|
||||
|
||||
; Step past the refused instruction. Every write here is an opcode and a port.
|
||||
MVSD.0
|
||||
DPUP.0 0d14
|
||||
LDA.0
|
||||
CCF
|
||||
INIB 0d2
|
||||
ADD
|
||||
MVQA
|
||||
STA.0
|
||||
BRC stepCarried
|
||||
RETI
|
||||
stepCarried:
|
||||
DPDN.0 0d01
|
||||
LDA.0
|
||||
INCA
|
||||
STA.0
|
||||
RETI
|
||||
|
||||
say:
|
||||
CALL printString
|
||||
CALL lineFeed
|
||||
RET
|
||||
|
||||
#Data
|
||||
|
||||
OutsideOk:
|
||||
"outside ok"
|
||||
Caught:
|
||||
"caught"
|
||||
ReadOk:
|
||||
"read ok"
|
||||
LoweredOk:
|
||||
"lowered ok"
|
||||
Fenced:
|
||||
"fenced"
|
||||
|
||||
#Vectors
|
||||
|
||||
GuardViolation guardHandler
|
||||
@@ -0,0 +1,125 @@
|
||||
; A program that loads a program.
|
||||
;
|
||||
; This is what the memory controller was built for. The bytes of a small routine sit in
|
||||
; the Data Segment the way a program read off a disk would. The loader blits them into
|
||||
; Program Memory, writes their address into a software vector, and calls it. Nothing
|
||||
; about that routine was known to the assembler as code: to the loader it is data, and it
|
||||
; becomes code only because it was put somewhere the CPU fetches from.
|
||||
;
|
||||
; Installing the vector at run time is the part worth watching. The argument that started
|
||||
; the whole controller design was that a vector table nothing can write is not really a
|
||||
; vector table. Vector 20 is empty when this program starts and holds a real handler by
|
||||
; the time it is called, and nothing but the controller could have put it there.
|
||||
;
|
||||
; WHAT THIS DOES NOT SOLVE:
|
||||
;
|
||||
; The payload works at whatever address it lands at only because it contains no
|
||||
; addresses. It is INIA and OUTA and a RETI, and none of those name a place. The moment a
|
||||
; loaded routine contains a branch, a CALL, or a SETD, it holds an address that was fixed
|
||||
; when it was assembled, and it will be wrong everywhere except where it was assembled
|
||||
; for. SplitBit has no answer to that yet. Loading works; relocating does not exist.
|
||||
;
|
||||
; Correct output is:
|
||||
; before the vector is still empty here
|
||||
; loaded printed by code that was data a moment ago
|
||||
; back and RETI came home
|
||||
|
||||
#Include print.asm
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
SETD.0 Before
|
||||
CALL say
|
||||
|
||||
; ---- Put the payload into Program Memory. ----
|
||||
SETD.0 Payload
|
||||
CALL aimSourceData
|
||||
SETD.0 LandingZone
|
||||
CALL aimDestProgram
|
||||
INIA 0d29
|
||||
CALL setLength
|
||||
INIA 0x01
|
||||
OUTA 0xE8 ; Blit: Data Memory into Program Memory.
|
||||
|
||||
; ---- Write its address into software vector 20. ----
|
||||
; The table is at 0xFC00 and entries are two bytes, so vector 20 is at 0xFC28.
|
||||
RSTA
|
||||
OUTA 0xE3 ; DestBank = 0, Program Memory.
|
||||
INIA 0xFC
|
||||
OUTA 0xE4
|
||||
INIA 0x28
|
||||
OUTA 0xE5
|
||||
SETD.0 LandingZone
|
||||
PSHD.0
|
||||
POPA
|
||||
POPB
|
||||
OUTB 0xE9 ; High byte of the handler's address.
|
||||
OUTA 0xE9 ; Low byte. The Data port steps on by itself.
|
||||
|
||||
; ---- Call it. ----
|
||||
SWI 0d20
|
||||
|
||||
SETD.0 Back
|
||||
CALL say
|
||||
HALT
|
||||
|
||||
; DP0 holds the address to aim at.
|
||||
aimSourceData:
|
||||
INIA 0d1
|
||||
OUTA 0xE0
|
||||
PSHD.0
|
||||
POPA
|
||||
POPB
|
||||
OUTB 0xE1
|
||||
OUTA 0xE2
|
||||
RET
|
||||
|
||||
aimDestProgram:
|
||||
RSTA
|
||||
OUTA 0xE3
|
||||
PSHD.0
|
||||
POPA
|
||||
POPB
|
||||
OUTB 0xE4
|
||||
OUTA 0xE5
|
||||
RET
|
||||
|
||||
setLength:
|
||||
PSHA
|
||||
RSTA
|
||||
OUTA 0xE6
|
||||
POPA
|
||||
OUTA 0xE7
|
||||
RET
|
||||
|
||||
say:
|
||||
CALL printString
|
||||
CALL lineFeed
|
||||
RET
|
||||
|
||||
; Where the payload lands. Reserving it means the assembler knows the room is spoken for,
|
||||
; so nothing else is ever placed here.
|
||||
LandingZone:
|
||||
#Reserve 0d48
|
||||
|
||||
#Data
|
||||
|
||||
Before:
|
||||
"before"
|
||||
Back:
|
||||
"back"
|
||||
|
||||
; The payload, as bytes rather than as code. It prints "loaded" and returns. Written out
|
||||
; by hand because SplitBit has no linker: to this program it is just so much data.
|
||||
;
|
||||
; INIA 'l' OUTA 0 INIA 'o' OUTA 0 ... and so on, then RETI
|
||||
Payload:
|
||||
0x26 0x6C 0xD1 0x00
|
||||
0x26 0x6F 0xD1 0x00
|
||||
0x26 0x61 0xD1 0x00
|
||||
0x26 0x64 0xD1 0x00
|
||||
0x26 0x65 0xD1 0x00
|
||||
0x26 0x64 0xD1 0x00
|
||||
0x26 0x0A 0xD1 0x00
|
||||
0x19
|
||||
@@ -0,0 +1,25 @@
|
||||
; A device refuses, and nothing is installed to deal with it.
|
||||
;
|
||||
; The machine has to stop where it stands and say which port refused and where. Letting
|
||||
; execution carry on would mean the program continues as though the access had worked,
|
||||
; which is the failure the fault trap exists to prevent.
|
||||
;
|
||||
; Correct output is:
|
||||
; O
|
||||
; a fault naming port 17, and a non zero exit.
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
INIA 0d79 ; 'O', so it is clear how far it got.
|
||||
OUTA 0x00
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
|
||||
INIA 0d1
|
||||
OUTA 0x11 ; The device refuses, and no handler is installed.
|
||||
|
||||
; Never reached.
|
||||
INIA 0d88 ; 'X'
|
||||
OUTA 0x00
|
||||
HALT
|
||||
@@ -0,0 +1,74 @@
|
||||
; Tests a device refusing what it was asked to do.
|
||||
;
|
||||
; Interrupting is a device asking for attention later. Refusing is a device saying no to
|
||||
; the instruction happening now, so it has to stop the machine where it stands instead of
|
||||
; letting execution carry on past the mistake.
|
||||
;
|
||||
; The device on port 0x11 refuses everything, in both directions, so that this path can
|
||||
; be exercised without the memory controller. A refusal names a software vector, so the
|
||||
; handler knows what happened from the entry it arrived through.
|
||||
;
|
||||
; The handler steps the saved address past the instruction that was refused, the same way
|
||||
; the fault handler steps past a byte that would not decode. Without that a bare RETI
|
||||
; would meet the same refused instruction again forever.
|
||||
;
|
||||
; Correct output is:
|
||||
; caught the handler caught a refused write
|
||||
; caught and a refused read
|
||||
; done
|
||||
|
||||
#Include print.asm
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
INIA 0d1
|
||||
OUTA 0x11 ; Refused. The handler runs and steps us past this.
|
||||
|
||||
INA 0x11 ; Refused as well, and reads are caught the same way.
|
||||
|
||||
SETD.0 Done
|
||||
CALL printString
|
||||
CALL lineFeed
|
||||
HALT
|
||||
|
||||
; A refusal arrives with a full frame, so the handler may use whatever it likes.
|
||||
refusalHandler:
|
||||
CALL reportRefusal
|
||||
|
||||
; Step the saved address past the instruction that was refused. Both OUT and IN are
|
||||
; two bytes, an opcode and a port, so two is what it takes.
|
||||
MVSD.0
|
||||
DPUP.0 0d14
|
||||
LDA.0
|
||||
CCF
|
||||
INIB 0d2
|
||||
ADD
|
||||
MVQA
|
||||
STA.0
|
||||
BRC carried
|
||||
RETI
|
||||
|
||||
carried:
|
||||
DPDN.0 0d01
|
||||
LDA.0
|
||||
INCA
|
||||
STA.0
|
||||
RETI
|
||||
|
||||
reportRefusal:
|
||||
SETD.0 Caught
|
||||
CALL printString
|
||||
CALL lineFeed
|
||||
RET
|
||||
|
||||
#Data
|
||||
|
||||
Caught:
|
||||
"caught"
|
||||
Done:
|
||||
"done"
|
||||
|
||||
#Vectors
|
||||
|
||||
GuardViolation refusalHandler
|
||||
@@ -0,0 +1,132 @@
|
||||
; Giving a bank number to a device's memory.
|
||||
;
|
||||
; This is the whole sequence an OS goes through: ask the bus registry which ports bring
|
||||
; memory, give one of them a bank number, and then reach it with the controller like any
|
||||
; other bank. A device's memory is unreachable until it has a number, and the controller
|
||||
; is the only thing that can reach it even then.
|
||||
;
|
||||
; How big the bank is comes from the device rather than from this program. Capacity was
|
||||
; settled when the machine was built, so software asserting it could only ever be wrong.
|
||||
;
|
||||
; The device on port 0x12 owns 256 bytes and fills them with whatever byte is written to
|
||||
; its port, which stands in for a disk controller reading a sector.
|
||||
;
|
||||
; Correct output is:
|
||||
; flags 01 the registry says port 0x12 brings memory
|
||||
; 01 12 01 00 bank 5's record: present, owned by port 0x12, 256 bytes
|
||||
; 5A 5A the device's memory, reached through the controller
|
||||
; refused registering over one of the machine's own banks
|
||||
; refused and registering memory from a port that brings none
|
||||
|
||||
#Include print.asm
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
; Ask the registry whether port 0x12 brings memory.
|
||||
INIA 0x12
|
||||
OUTA 0xFF
|
||||
INA 0xFF ; Class, which we do not need here.
|
||||
INA 0xFF ; Flags: bit 0 says it brings memory.
|
||||
PSHA
|
||||
SETD.0 Flags
|
||||
CALL printString
|
||||
CALL blankSpace
|
||||
POPA
|
||||
CALL printByteHex
|
||||
CALL lineFeed
|
||||
|
||||
; Tell the device to fill its memory, the way a disk controller would be told to read.
|
||||
INIA 0x5A
|
||||
OUTA 0x12
|
||||
|
||||
; Give that memory bank number 5.
|
||||
INIA 0d5
|
||||
OUTA 0xE3 ; DestBank = 5, the number being handed out.
|
||||
INIA 0x12
|
||||
OUTA 0xE2 ; SourceLow = the port that owns it.
|
||||
INIA 0x03
|
||||
OUTA 0xE8 ; RegisterBank.
|
||||
|
||||
; Read bank 5's record out of the bank table. Eight bytes a bank, so bank 5 is at 40.
|
||||
INIA 0d2
|
||||
OUTA 0xE0
|
||||
RSTA
|
||||
OUTA 0xE1
|
||||
INIA 0d40
|
||||
OUTA 0xE2
|
||||
CALL readAndPrint ; Flags: present.
|
||||
CALL readAndPrint ; Owner: port 0x12.
|
||||
CALL readAndPrint ; Capacity, high byte.
|
||||
CALL readAndPrint ; Capacity, low byte.
|
||||
CALL lineFeed
|
||||
|
||||
; Now reach the device's memory the way any other bank is reached.
|
||||
INIA 0d5
|
||||
OUTA 0xE0
|
||||
RSTA
|
||||
OUTA 0xE1
|
||||
OUTA 0xE2
|
||||
CALL readAndPrint
|
||||
CALL readAndPrint
|
||||
CALL lineFeed
|
||||
|
||||
; Banks 0 to 2 are the machine's own and cannot be handed out.
|
||||
INIA 0d1
|
||||
OUTA 0xE3
|
||||
INIA 0x12
|
||||
OUTA 0xE2
|
||||
INIA 0x03
|
||||
OUTA 0xE8 ; Refused.
|
||||
|
||||
; Neither can memory from a port that brings none. Port 0x10 is the test device.
|
||||
INIA 0d6
|
||||
OUTA 0xE3
|
||||
INIA 0x10
|
||||
OUTA 0xE2
|
||||
INIA 0x03
|
||||
OUTA 0xE8 ; Refused.
|
||||
HALT
|
||||
|
||||
readAndPrint:
|
||||
INA 0xE9
|
||||
CALL printByteHex
|
||||
CALL blankSpace
|
||||
RET
|
||||
|
||||
; Both refusals arrive here, and they really are the same fault: a bank that cannot be
|
||||
; registered, for two different reasons. Saying so twice is more honest than inventing a
|
||||
; distinction the machine does not draw.
|
||||
bankHandler:
|
||||
SETD.0 Refused
|
||||
CALL printString
|
||||
CALL lineFeed
|
||||
|
||||
; Step past the refused command write, which is an opcode and a port.
|
||||
MVSD.0
|
||||
DPUP.0 0d14
|
||||
LDA.0
|
||||
CCF
|
||||
INIB 0d2
|
||||
ADD
|
||||
MVQA
|
||||
STA.0
|
||||
BRC bankCarried
|
||||
RETI
|
||||
bankCarried:
|
||||
DPDN.0 0d01
|
||||
LDA.0
|
||||
INCA
|
||||
STA.0
|
||||
RETI
|
||||
|
||||
#Data
|
||||
|
||||
Flags:
|
||||
"flags"
|
||||
Refused:
|
||||
"refused"
|
||||
|
||||
#Vectors
|
||||
|
||||
BankFault bankHandler
|
||||
Reference in New Issue
Block a user