diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index 3982ffaf..c579cccc 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -46,6 +46,7 @@ The full set of tests is only run for tagged releases. - pdp11.vhd: rename, eg srv->ser; cpustat_type: drop trap_done, add in_vecysv, treq_tbit,resetcnt; decode_stat_type: op_rti instead of op_rtt - pdp11_decode.vhd: use op_rti instead of op_rtt + - pdp11_mmu.vhd: logic cleanup - pdp11_sequencer.vhd: - tbit logic overhaul; use treq_tbit; cleanups; use resetcnt for 8 cycle RESET wait, see [ECO-035](ECO-035-stklim-tbit-fixes.md) @@ -70,8 +71,11 @@ The full set of tests is only run for tagged releases. [ECO-035](ECO-035-stklim-tbit-fixes.md) - BUGFIX: correct mmu trap vs interrupt priority, see [ECO-035](ECO-035-stklim-tbit-fixes.md) - - pdp11_vmbox: BUGFIX: correct red/yellow zone boundary, see - [ECO-035](ECO-035-stklim-tbit-fixes.md) + - pdp11_vmbox: + - BUGFIX: correct red/yellow zone boundary, see + [ECO-035](ECO-035-stklim-tbit-fixes.md) + - BUGFIX: MMU trap after IB access, see + [ECO-038](ECO-038-MMU_trap_on_IB_access.md) --- diff --git a/doc/ECO-038-MMU_trap_on_IB_access.md b/doc/ECO-038-MMU_trap_on_IB_access.md new file mode 100644 index 00000000..97288785 --- /dev/null +++ b/doc/ECO-038-MMU_trap_on_IB_access.md @@ -0,0 +1,29 @@ +# ECO-038: fix MMU trap after IB access; MMU logic cleanup (2022-12-18) + +### Scope +- was in w11a since 2008 +- affects: all w11a systems + +### Symptom summary +When the IO page, in kernel mode by convention page 7, was mapped with MMU traps +enabled in `PDR` and `MMR0`, the AI bits on the `PDR` and the `trap_mmu` bit +in `MMR0` were set, but no MMU trap was taken. + +### Analysis +The error was in `pdp11_vmbox`, the `VM_STAT` flag `trap_mmu` was only set in +the state that handles memory access, but not in the states that handle ibus +access. +Further code inspection revealed that the `pdp11_mmu` code handling traps was +functionally correct but not well structured. + +### Changes +The `trap_mmu` flag is only inspected in `pdp11_sequencer` when `ack` is set. +It is therefore safe to set `trap_mmu` in all states of the `pdp11_vmbox` FSM. +Some logic in `pdp11_mmu` was restructured and is now more compact, but +stayed functionally equivalent. + +### Hindsight +The MMU traps are not used by any OS, and therefore were not tested very +thoroughly. +This bug was only discovered a few months ago and resurfaced when the 'vector +push abort recovery' demonstrator was written. diff --git a/rtl/w11a/pdp11_mmu.vhd b/rtl/w11a/pdp11_mmu.vhd index 8d59272d..f3f2d9fb 100644 --- a/rtl/w11a/pdp11_mmu.vhd +++ b/rtl/w11a/pdp11_mmu.vhd @@ -17,6 +17,7 @@ -- -- Revision History: -- Date Rev Version Comment +-- 2022-12-17 1331 1.4.7 some logic cleanup -- 2022-12-12 1330 1.4.6 implement MMR0 instruction complete -- 2022-11-29 1323 1.4.5 rename mmu_mmr0_type dspace->page_dspace -- 2022-09-05 1294 1.4.4 BUGFIX: correct trap and PDR A logic @@ -351,6 +352,8 @@ begin abo_nonres := '1'; end case; + STAT <= mmu_stat_init; + if IBSEL_MMR0='1' and IB_MREQ.we='1' then if IB_MREQ.be1 = '1' then @@ -364,38 +367,37 @@ begin nmmr0.ena_mmu := IB_MREQ.din(mmr0_ibf_ena_mmu); end if; - elsif nmmr0.ena_mmu='1' and CNTL.cacc='0' then + elsif R_MMR0.ena_mmu='1' and CNTL.cacc='0' then - if mmr_freeze = '0' then - nmmr0.inst_compl := MONI.vflow; + if mmr_freeze = '0' then -- independent of an active request will + nmmr0.inst_compl := MONI.vflow; -- the inst_compl flag follow vflow end if; if CNTL.req = '1' then AIB_WE <= '1'; if mmr_freeze = '0' then - nmmr0.abo_nonres := abo_nonres; - nmmr0.abo_length := abo_length; - nmmr0.abo_rdonly := abo_rdonly; + nmmr0.abo_nonres := abo_nonres; + nmmr0.abo_length := abo_length; + nmmr0.abo_rdonly := abo_rdonly; + nmmr0.page_dspace := DSPACE; + nmmr0.page_num := apf; + nmmr0.page_mode := CNTL.mode; end if; doabort := abo_nonres or abo_length or abo_rdonly; if doabort = '0' then AIB_SETA <= dotrap; AIB_SETW <= iswrite; + if dotrap='1' then -- if trap condition fulfilled + nmmr0.trap_mmu := '1'; -- set trap_mmu flag + if R_MMR0.ena_trap='1' and R_MMR0.trap_mmu='0' then + STAT.trap <= '1'; -- trap if enabled and not blocked + end if; + end if; end if; - if mmr_freeze = '0' then - nmmr0.page_dspace := DSPACE; - nmmr0.page_num := apf; - nmmr0.page_mode := CNTL.mode; - end if; end if; -- CNTL.req = '1' - end if; -- nmmr0.ena_mmu='1' and CNTL.cacc='0' - - if CNTL.req='1' and R_MMR0.ena_mmu='1' and CNTL.cacc='0' and - dotrap='1' then - nmmr0.trap_mmu := '1'; - end if; + end if; -- R_MMR0.ena_mmu='1' and CNTL.cacc='0' nmmr0.trace_prev := dotrace; @@ -413,13 +415,6 @@ begin STAT.vaok <= '1'; end if; - if R_MMR0.ena_mmu='1' and CNTL.cacc='0' and doabort='0' and - R_MMR0.ena_trap='1' and R_MMR0.trap_mmu='0' and dotrap='1' then - STAT.trap <= '1'; - else - STAT.trap <= '0'; - end if; - STAT.ena_mmu <= R_MMR0.ena_mmu; STAT.ena_22bit <= R_MMR3.ena_22bit; STAT.ena_ubmap <= R_MMR3.ena_ubmap; diff --git a/rtl/w11a/pdp11_vmbox.vhd b/rtl/w11a/pdp11_vmbox.vhd index 249c9ff1..dd17537c 100644 --- a/rtl/w11a/pdp11_vmbox.vhd +++ b/rtl/w11a/pdp11_vmbox.vhd @@ -18,6 +18,7 @@ -- -- Revision History: -- Date Rev Version Comment +-- 2022-12-17 1331 1.6.10 BUGFIX: request mmu trap also on ib accesses -- 2022-11-21 1320 1.6.9 rename some rsv->ser; remove obsolete trap_done; -- 2022-11-18 1317 1.6.8 BUGFIX: correct red/yellow zone boundary -- 2019-06-22 1170 1.6.7 support membe for em cacc access @@ -475,8 +476,6 @@ begin if EM_SRES.ack_r='1' or EM_SRES.ack_w='1' then ivm_stat.ack := '1'; - ivm_stat.trap_ysv := r.ysv; - ivm_stat.trap_mmu := r.trap_mmu; if r.macc='1' and r.wacc='0' then n.state := s_idle_mw_mem; else @@ -657,6 +656,9 @@ begin n.paddr_iopage := ipaddr_iopage; end if; + ivm_stat.trap_ysv := r.ysv; -- request ysv trap if condition seen + ivm_stat.trap_mmu := r.trap_mmu; -- forward mmu trap request + iem_mreq.addr := ipaddr(21 downto 1); N_REGS <= n; diff --git a/tools/tcode/cpu_mmu.mac b/tools/tcode/cpu_mmu.mac index 8b6d57df..99333a9a 100644 --- a/tools/tcode/cpu_mmu.mac +++ b/tools/tcode/cpu_mmu.mac @@ -4,7 +4,7 @@ ; ; Revision History: ; Date Rev Version Comment -; 2022-12-16 1330 1.0 Initial version +; 2022-12-17 1331 1.0 Initial version ; 2022-07-24 1262 0.1 First draft ; ; Test CPU MMU: all aspects of the MMU @@ -39,6 +39,12 @@ sipdr0 = sipdr+ 0 sipar0 = sipar+ 0 + sipdr1 = sipdr+ 2 + sipar1 = sipar+ 2 + sipdr2 = sipdr+ 4 + sipar2 = sipar+ 4 + sipdr3 = sipdr+ 6 + sipar3 = sipar+ 6 sipdr6 = sipdr+14 sipar6 = sipar+14 sipdr7 = sipdr+16 @@ -47,6 +53,8 @@ kipdr0 = kipdr+ 0 kipar0 = kipar+ 0 kdpdr0 = kdpdr+ 0 + kipdr1 = kipdr+ 2 + kipar1 = kipar+ 2 kipdr5 = kipdr+12 kipdr6 = kipdr+14 kipar6 = kipar+14 @@ -70,6 +78,18 @@ p6p1p2 = p6base+<1*100>+2 ; page 6, +1 click, +2 p7base = <7*20000> ; page 7 ; +; helper macro for trace area check setup (from cpu_details A4) + .macro htinit,buf,nent + hcmpeq #buf+<4*nent>,r5 + mov #buf,r5 + .endm +; +; helper macro for trace area check entry (from cpu_details A4) + .macro htitem,tvec,tadr + hcmpeq tvec,(r5)+ + hcmpeq tadr,(r5)+ + .endm +; ; Section A: pdr,par registers =============================================== ; A1.1 test that pdr/par are 16 bit write/readable ; A1.2 set up MMU default configuration @@ -1626,6 +1646,7 @@ tc0210: tstb systyp ; skip if not on w11 ; ; Section D: mmr2+mmr1+mmr0 register, abort recovery ========================= ; D1 code in user mode with D space, simulated SP extend +; D2 vector push abort with simulated SP extend ; ; Test D1: ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ; @@ -1756,6 +1777,240 @@ td0101: ; 9999$: iot ; end of test D1.1 ; +; Test D2: ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; +; Test D2.1 -- vector push abort with simulated SP extend ++++++++++++ +; This test is a full mock-up of a vector push abort recovery using the +; MMR0,MMR2 instruction complete functionality. The test setup is +; - the PIRQ handler is delegated to supervisor mode +; - the supervisor stack is too short, the 2nd push of a vector flow +; causes an MMU length abort +; - the MMU handler sees MMR0(7) set to '1', extends the stack, builds +; the return frame on the supervisor stack, and starts the PIRQ handler +; - the PIRQ handler ends with a call to a simulated system service that +; handles the RTI in kernel mode. That is necessary because an RTI from +; supervisor mode can not return to kernel mode (escalation protection). +; To simulate a full function MMU handler the test setup has in addition +; - the PIRQ handler code is in an initially unmapped page. When the code +; is started after the stack recovery, the first instruction fetch will +; cause an MMU abort, now with MMR0(7) set to '0'. The handler will map +; the code page and re-run the instruction. +; - the IO page is mapped to supervisor mode with MMU traps enabled. +; The first IO page access of the PIRQ handler will therefore cause +; an MMU trap, the handler will just log it. +; +; The supervisor mapping is: +; page 0+1 1-to-1, this allows to write to trace area +; page 2 code area, initially non-resident +; page 3 stack area, initially too short +; page 7 IO page, with RW traps enabled +; +td0201: tstb systyp ; skip if not on w11 + bge 100$ + jmp 9999$ +; +; set up supervisor pdr/par + vc3sek = 157700 ; initial end of stack in kernel view + vc3ses = 077700 ; initial end of stack in supervisor view +; +100$: mov kipdr0,sipdr0 ; SM p0 1-to-1 + mov kipar0,sipar0 + mov kipdr1,sipdr1 ; SM p1 1-to-1 + mov kipar1,sipar1 + mov #<8.*md.plf>,sipdr2 ; SM p2 code, non-resident + mov #,sipar2 + mov #<127.*md.plf>!md.arw!md.dwn,sipdr3 ; SM p3 stack, short + mov #<140000/100>,sipar3 + mov #<127.*md.plf>!md.att,sipdr7 ; SM p7 IOpage + rw-trap + mov kipar7,sipar7 + mov #m0.ent!m0.ena,mmr0 ; enable mmu ;! MMU 18 +; +; set up handlers +; + mov #3000$,v..mmu ; MMU handler + mov #cp.pr7,v..mmu+2 ; lockout interrupts + mov #4000$,v..emt ; EMT handler + mov #cp.pr7,emt+2 ; lockout interrupts + mov #p2base,v..pir ; PIRQ handler (on page 2 in SM) + mov #cp.cms!cp.pr7,v..pir+2 ; in SM, lockout interrupts +; +; now start the game: set SM stack, set r5 and request a PIRQ 4 +; + mov #cp.pms,cp.psw ; SM now previous mode + push #vc3ses+2 ; SM stack 1 word above end + mtpi sp ; and set SM SP + mov #2000$,r5 ; ptr to trace area + mov #cp.pr1,cp.psw ; code signature PR1 + movb #bit04,cp.pir+1 ; request PIRQ 4 +200$: +; +; and check state and trace area +; kernel SP back to normal +; supervisor SP back to normal +; sipdr7 has AIA and AIW trace bits set +; + hcmpeq #cp.pr1,cp.psw ; check PS + hcmpeq #stack,sp ; check kernel SP + mov #cp.pms,cp.psw ; SM now previous mode + mfpi sp ; get SM SP + hcmpeq #vc3ses+2,(sp)+ ; check supervisor SP + clr cp.psw ; PSW to default + hcmpeq #<127.*md.plf>!md.aia!md.aiw!md.att,sipdr7 ; check sipdr7 +; + htinit 2000$,5. ; expect 5 items + htitem #250,#200$ ; mmu(ico=1) after movb to cp.pir+1 + htitem #250,#p2base+2 ; mmu(ico=1) after 1st instruction fetch + htitem #240,#200$ ; PIRQ, sees PC after movb to cp.pir+1 + htitem #250,#p2base+ ; mmu(trap) after clr of cp.pir + htitem #032,#p2base+ ; EMT after emt 100 +; + jmp 9000$ +; +2000$: .word 0,0 ; trace data area + .word 0,0 + .word 0,0 + .word 0,0 + .word 0,0 + .word -1,-1 +; +; MMU handler ---------------------------------------------- +; It expects an abort with ico=1, an abort with ico=0 and a trap. +; In these three cases, first the expected environment is checked and +; after that the corrective action is taken. +; +3000$: htstge (r5) ; r5 at fence ? + mov #250,(r5)+ ; trace + mov (sp),(r5)+ +; +; dispatch call cases based in mmr0 + mov mmr0,r0 + bit #m0.anr!m0.ale!m0.ard,r0 ; abort seen ? + beq 3300$ ; if not branch to trap handling + bit #m0.ico,r0 ; ico seen ? + beq 3200$ ; if not branch to ico=0 handling +; +; handle abort with ico=1 ------------------------ +; Expect length error for supervisor mode I space page 3. +; Actions: +; - roll-back previous mode SP. +; - extend previous mode stack segment. +; - move stack frame from from kernel to previous mode. +; Note: the push abort leaves on the kernel stack a stack frame identical +; to what would have been on supervisor mode stack without abort. +; - start handler for aborted vector flow. +; Note: MMR2 holds the vector address. So push PS and PC of that vector +; to kernel stack and start handler with an RTT. +; + hcmpeq #m0.ale!m0.ent!m0.ico!m0.pms!<3*m0.pno>!m0.ena,r0 ; check mmr0 + hcmpeq #240,mmr2 ; check mmr2: PIRQ vector +; +; use MMR1 to correct SM SP + mov mmr1,r1 ; get mmr1 + mfpi sp ; get SM sp + pop r2 + cmp #^b0000000011110110,r1 ; mmr1: sp -2 + bne 3100$ + add #2,r2 ; correct SP by 2 + br 3140$ +3100$: cmp #^b1111011011110110,r1 ; mmr1: sp -4 + bne 3110$ + add #4,r2 ; correct SP by 4 + br 3140$ +3110$: halt ; unexpected MMR1 +3140$: +; +; extend target stack frame --> decrease(!) plf +; + mov #<126.*md.plf>!md.arw!md.dwn,sipdr3 +; +; move stack frame from kernel to target mode (SM) +; + sub #4,r2 ; final SM SP + mtpi (r2) ; move SM frame PC + mtpi 2(r2) ; move SM frame PS + push r2 + mtpi sp ; update SM SP +; +; use MMR2 to start target handler (PIRQ) +; + mov mmr2,r2 ; get vector address + push 2(r2) ; handler PS + push (r2) ; handler PC + bic #m0.anr!m0.ale!m0.ard,mmr0 ; clear abort flags + rtt ; and start handler +; +; handle abort with ico=0 ------------------------ +; Expect non-resident error for supervisor mode I space page 2 +; Actions: +; - no register roll-back done, MMR1 expected (and checked) to be 0. +; - make page resident. +; - re-run instruction, MMR2 points to its address. +; +3200$: hcmpeq #m0.anr!m0.ent!m0.pms!<2*m0.pno>!m0.ena,r0 ; check mmr0 + htsteq mmr1 ; check mmr1: zero after ifetch + hcmpeq #p2base,mmr2 ; check mmr2: PIRQ handler start +; +; make code page resident and re-run instruction +; + bis #md.arw,sipdr2 ; code page read-writable + mov mmr2,(sp) ; point to failed instruction + bic #m0.anr!m0.ale!m0.ard,mmr0 ; clear abort flags + rtt ; re-run instruction +; +; handle MMU trap -------------------------------- +; Expect trap from cp.pir register access +; Actions: +; - just re-allow traps, even though none are expected +; +3300$: hbitne #m0.trp,r0 ; check mmr1 + bic #m0.trp,mmr0 ; re-allow traps + rti ; continue +; +; EMT handler ---------------------------------------------- +; The EMT handler simulates a system service that does a kernel mode RTI +; on behalf of the supervisor mode handler. This allows supervisor mode code +; to return to kernel mode code and bypasses the privileged escalation +; protection of RTI/RTT in a controlled manner. +; It simply moves the stack frame from previous mode to kernel mode and +; does an RTI. The EMT return frame on the kernel stack is discarded. +; + +4000$: htstge (r5) ; r5 at fence ? + mov #032,(r5)+ ; trace + mov (sp),(r5)+ +; +; move stack frame from hander to kernel stack +; + add #4,sp ; drop return frame + mfpi sp ; get SM SP + pop r2 + mfpi 2(r2) ; move frame PS + mfpi (r2) ; move frame PC + add #4,r2 ; correct SM SP + push r2 + mtpi sp ; update SM SP + rti ; finish rti from handler +; +; finally restore ------------------------------------------ +9000$: reset ;! MMU off + mov #sipdr0,r0 + mov #sipar0,r1 + mov #4,r2 +9010$: clr (r0)+ ; reset sipdr 0-3 + clr (r1)+ ; reset sipar 0-3 + sob r2,9010$ + clr sipdr7 + clr sipar7 +; + mov #v..mmu+2,v..mmu ; restore v..mmu to catcher + clr v..mmu+2 + mov #v..emt+2,v..emt ; restore v..emt to catcher + clr v..emt+2 + mov #v..pir+2,v..pir ; restore v..pir to catcher + clr v..pir+2 +; +9999$: iot ; end of test D2.1 +; ; Section E: traps and pdr aia and aiw bits ================================== ; E1 basic MMU trap and PDR aia/aiw logic ; E1.1 test m0.trp, pdr aia/aiw transitions @@ -2111,7 +2366,7 @@ te0103: mov #mmr0,r2 ; ptr to mmr0 ; should cause an MMU trap. ; te0104: mov #<127.*md.plf>!md.att,kipdr5 ; enable traps (afc=4) - mov #m0.ent!m0.ena,mmr0 ; enable mmu with traps ;! MMU 18 + mov #m0.ent!m0.ena,mmr0 ; enable mmu with traps ;! MMU 18 clr r2 ; clear counter mov #1000$,r3 ; ptr to failed landing mov #vhmmut,v..mmu ; setup MMU trap handler @@ -2128,6 +2383,25 @@ te0104: mov #<127.*md.plf>!md.att,kipdr5 ; enable traps (afc=4) ; 9999$: iot ; end of test E1.4 ; +; Test E1.5 -- test trap request after IO page access ++++++++++++++++ +; +te0105: mov #<127.*md.plf>!md.att,kipdr7 ; enable traps (afc=4) + mov #vhmmut,v..mmu ; setup MMU trap handler + mov #1000$,vhvmmu +; +; The write to mmr0 will not trigger a trap on w11 because the trap decision +; is taken during address translation and thus before MMR0 is changed. +; The read access to cp.psw will cause an MMU trap. + mov #m0.ent!m0.ena,mmr0 ; enable mmu with traps ;! MMU 18 + tst cp.psw + halt +; +1000$: reset ; mmu off ;! MMU off + mov #<127.*md.plf>!md.arw,kipdr7 ; reset kipdr7 + mov #v..mmu+2,v..mmu +; +9999$: iot ; end of test E1.5 +; ; Test E2: ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ; Check MMU trap priority behavior ; @@ -2153,14 +2427,10 @@ te0201: mov #m0.ent!m0.ena,mmr0 ; enable mmu with traps ;! MMU 18 .word 0,0 ; 3rd marker (MMU for rts) .word -1,-1 ; fence ; -2000$: hcmpeq #1500$+12.,r5 ; check 3 markers expected - mov #1500$,r5 - hcmpeq #250,(r5)+ ; 1st: MMU for movb - hcmpeq #p5ce21+6,(r5)+ ; PC after movb - hcmpeq #240,(r5)+ ; 2nd: PIRQ - hcmpeq #p5ce21+6,(r5)+ ; PC after movb - hcmpeq #250,(r5)+ ; 3rd: MMU for rts - hcmpeq #1000$,(r5)+ ; PC after rts (the return address) +2000$: htinit 1500$,3. ; expect 3 items + htitem #250,#p5ce21+6 ; mmu for movb + htitem #240,#p5ce21+6 ; PIRQ, sees PC after movb + htitem #250,#1000$ ; mmu for rts, PC is return address ; reset ; mmu off ;! MMU off ; @@ -2289,16 +2559,16 @@ tf0102: mov #154345,@#p6base ; inititialize target ; END OF ALL TESTS - loop closure ============================================ ; mov tstno,r0 ; hack, for easy monitoring ... - hcmpeq tstno,#29. ; all tests done ? + hcmpeq tstno,#31. ; all tests done ? call chkpdr ; kernel pdr/par OK ? ; jmp loop ; ; pdr/par consistency checker -; Verify that kernel pdr/par are in default configuration set up by A1.2. +; Verify that all I-space pdr/par are in default configuration set up by A1.2. ; Implentend as subroutine for debug purposes. Always called at end of tests. ; -chkpdr: mov #kipdr0,r0 +chkpdr: mov #kipdr0,r0 ; check kernel mov #kipar0,r1 mov #<127.*md.plf>!md.arw,r2 ; default pdr clr r3 ; current par @@ -2312,6 +2582,17 @@ chkpdr: mov #kipdr0,r0 mov (r0),(r0) ; clear AI bits with re-write hcmpeq r2,(r0)+ ; check pdr7 hcmpeq #177600,(r1)+ ; check par7 +; + mov #sipdr0,r0 ; check supervisor + user + mov #sipar0,r1 + mov #uipdr0,r2 + mov #uipar0,r3 + mov #8.,r4 +200$: htsteq (r0)+ ; check sipdr + htsteq (r1)+ ; check sipar + htsteq (r2)+ ; check uipdr + htsteq (r3)+ ; check uipar + sob r4,200$ return ; ; kernel handlers and helpers ================================================ @@ -2460,6 +2741,18 @@ vc2dat: .word 010111 .word 010333 .word 010444 ; +; vc3 - PIRQ handler code ++ used from D2.1 ++++++++++++++++++++++++++++++++++ +; Simply cancels all PIRQ interrupts. +; Will be mapped to page 2, the code must therefore be position-independent. +; + . = 105000 ; I space ------------------------------------ +vc3: htstge (r5) ; r5 at fence ? + mov #240,(r5)+ ; trace + mov (sp),(r5)+ + clr @#cp.pir ; cancel PIRQ (use absolute mode!) +vc3l1: emt 100 ; delegate RTI to system service +vc3l2: halt ; label after emt +; ; p5ce14 Test E1.4 test code +++++++++++++++++++++++++++++++++++++++++ ; located at border of page 4 and page 5 (touching both) ;