Put the frame clock on the 68000, and find that the 12 fps frame does not exist
ROADMAP P3 said "needs MFP timer or VBL" and neither can do it. The MFP's timer clock is 16 MHz/4, its prescalers stop at 200 and its data register is 8 bits, so the slowest tick any single timer can make is 78.125 Hz -- 6.5x faster than a frame -- and 4e6/12 is not an integer, so no setting reaches 12 Hz at all. The raster has no whole divide near 12 either: 4 refreshes is 13.86 fps and 5 is 11.09. tools/analysis/23_frame_clock.py walks all 7x256 timer settings rather than asserting it. src/player/clock.i takes the V-DISP falling edge on MFP GPIP4 -- the start of vertical blanking, which is when a player would present -- and adds fps*VTOTAL per edge to a 16-bit accumulator, emitting a tick at 31,500 and keeping the remainder. The long-run rate is fps*VTOTAL/VTOTAL = 12.000000 fps exactly, and both constants are read out of the CRTC at init, so the clock is derived from the registers that generate the raster it counts. Measured over 3,000 refreshes: 3,000 interrupts, 649 ticks where 649.1429 were due. It costs 181.35 clocks per V-DISP, 838 per frame, 0.1006% of the budget -- timed by the 68000 itself, because the host's granularity is 17.64 ms and the interrupt is microseconds. The loop's own cost was calibrated rather than looked up and landed on 38.000002 clocks, which both licenses the subtraction and confirms buscost.py's model; the 181.35 then decomposes exactly, leaving 43.99 clocks for the interrupt exception -- the textbook 44, measured. THE ONE THAT MOVES SOMETHING: 12 fps on a 55.4577 Hz raster is 4.6215 refreshes, so a frame is shown for 4 refreshes (72.13 ms) or 5 (90.16 ms), 37.9% of them short. The 833,333-clock budget every figure in this project is priced against is the MEAN slot, and the short one is 13.4% under it. The cadence was already in the tree unnamed: stream.lua's tick is sampled at frame boundaries, so its gaps were always 4 or 5, and every host-paced result in FINDINGS 49/51 carried it. P3 moved who produces it onto the machine and made it visible. It is not a dropped frame -- the pace gate lets an overrun eat the next frame's idle -- and on the gate container it costs 4 frames of 120 their idle against 1 for the nominal model, most of that the frame-0 transient at 111% of budget. stream.s counts it now, and the rig matches an offline model of the divider exactly. Also struck: MAME's raster runs 2.22% fast. refresh_mode() builds the frame period from scr.max_x*scr.max_y with scr.max_x = m_htotal - 8, one character cell short and an inclusive bound used as a count, so it runs at 56.6901 Hz where the registers say 55.4577 -- agreeing to six digits with the arithmetic. Every "1/55.46 s granularity" note in this tree was wrong and is 1/56.69 s, corrected in six files with the derivation put once in crtc_mode.lua. No conclusion changes and no 68000 cycle figure moves; the CPU clock is unrelated to the screen. But anything paced by the raster runs fast under MAME, so the rig reports both rates and prices the interrupt against the hardware's. decode.s and frame.i are unchanged; decode.bin is still 1,296 B at the same MD5. The pace gate's wait loop is byte-for-byte the one FINDINGS 51 measured and the free-running path executes none of the new code. check.sh gains two stages: the clock's own measurement, and 120 frames decoded pixel-exact with nothing outside the machine deciding when a frame may start. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
; ---------------------------------------------------------------------------
|
||||
; clock.i -- the FRAME CLOCK, on the 68000 itself. ROADMAP P3.
|
||||
;
|
||||
; src/player/stream.s has a pace gate: frame i may not START before tick i, and
|
||||
; PACE is the tick counter. Until now PACE was written by tools/bench/
|
||||
; stream.lua, i.e. by the host, off the host's idea of what 12 fps means. That
|
||||
; was honest for what FINDINGS 49/51 were measuring -- arrival times against a
|
||||
; deadline -- and it is not a player. A player has no host. These are the
|
||||
; bytes that replace it.
|
||||
;
|
||||
; WHAT THE MACHINE ACTUALLY OFFERS, because "use the MFP timer or vblank" hides
|
||||
; a real constraint. The MC68901's timer clock on this board is 16 MHz / 4 =
|
||||
; 4 MHz (MAME 0.277 src/mame/sharp/x68k.cpp:1027-1028), its prescaler ladder is
|
||||
; {4, 10, 16, 50, 64, 100, 200} (src/devices/machine/mc68901.cpp:173) and its
|
||||
; data register is 8 bits. The SLOWEST tick a single MFP timer can produce is
|
||||
; therefore 4e6 / (200*256) = 78.125 Hz, and 4e6/12 = 333,333.33 is not even an
|
||||
; integer -- so no prescaler/data pair ticks at 12 Hz, and no timer at any
|
||||
; setting ticks as slowly as a 12 fps frame. A frame clock needs a divider in
|
||||
; software whichever source it is built on. tools/analysis/23_frame_clock.py
|
||||
; enumerates the whole space rather than asserting this.
|
||||
;
|
||||
; So the source is the RASTER, and that is a better answer than a timer anyway.
|
||||
; GPIP4 on the MFP is V-DISP (x68k.cpp:1139, `m_crtc->vdisp_cb().set(i4_w)`),
|
||||
; high while the display is active; the same signal is the MFP's Timer A event
|
||||
; input (mc68901.cpp:167, GPIO_TIMER = {GPIP_4, GPIP_3}). Its interrupt is
|
||||
; channel 6, IR_GPIP_4 = $40 in IERB/IPRB/IMRB (mc68901.cpp:76). We take the
|
||||
; FALLING edge (AER bit 4 = 0), which is the start of vertical blanking -- the
|
||||
; instant a player would present a finished frame, so the clock and the flip
|
||||
; are the same event rather than two events with a phase between them.
|
||||
;
|
||||
; THE DIVIDER IS EXACT, AND IT IS EXACT BY CONSTRUCTION. The raster is
|
||||
;
|
||||
; 31,500 lines/s / (R04 + 1) lines/frame
|
||||
;
|
||||
; and 31,500 / 568 = 55.4577 Hz is not a multiple of 12, so a whole-number
|
||||
; divide cannot do it: 4 refreshes is 13.87 fps and 5 is 11.09 fps. Instead
|
||||
; each V-DISP adds `fps * (R04+1)` to an accumulator and a frame tick is emitted
|
||||
; whenever it reaches 31,500, keeping the remainder:
|
||||
;
|
||||
; acc += fps*VTOTAL ; if acc >= HFREQ: acc -= HFREQ ; PACE += 1
|
||||
;
|
||||
; Over VTOTAL/gcd raster frames that emits exactly fps*VTOTAL/gcd ticks, so the
|
||||
; long-run rate is fps*VTOTAL/VTOTAL = fps EXACTLY, with a bounded remainder and
|
||||
; ZERO accumulated drift -- not 12.0001, not 11.9998. It holds for any fps and
|
||||
; any vertical geometry, which is why the two constants are READ OUT OF THE
|
||||
; CRTC at init rather than assembled in: the clock is derived from the same
|
||||
; registers that generate the raster it is counting, so the two cannot disagree.
|
||||
;
|
||||
; WHAT IT COSTS IN CADENCE, WHICH IS THE PART THAT IS NOT FREE. 12 fps on a
|
||||
; 55.4577 Hz raster is 4.6215 refreshes per frame, so a frame is shown for
|
||||
; either 4 or 5 refreshes -- 72.13 ms or 90.16 ms. Nothing can change that;
|
||||
; it is the display's quantisation, not the clock's error, and a timer-derived
|
||||
; clock would have exactly the same cadence with an arbitrary phase against the
|
||||
; raster on top. It does mean the slot a frame gets is NOT always the 83.33 ms
|
||||
; every budget in this project is priced against, and the short slot is 13.4%
|
||||
; under it. tools/analysis/23_frame_clock.py prices that; do not read this file
|
||||
; as a claim that the clock made the budget bigger.
|
||||
;
|
||||
; INTERRUPTS, AND WHAT HAD TO BE TURNED OFF. The rigs launch the 68000 at
|
||||
; SR=$2700 with everything masked, into a machine the IPL ROM has already booted
|
||||
; -- so the MFP arrives with whatever IOCS enabled on it (keyboard receive,
|
||||
; Timer C, its own V-DISP handler) and vectors pointing into IOCS. Lowering the
|
||||
; mask without disarming the MFP would vector into code we did not put there.
|
||||
; clk_init therefore writes IERA = IERB = 0 first, which on the MC68901 also
|
||||
; clears the matching pending bits (mc68901.cpp REGISTER_IERA/B: `m_ipr &=
|
||||
; m_ier`), and only then arms GPIP4 alone. Levels 1-5 stay masked at SR=$2500,
|
||||
; so the DMAC (IRQ3) and the SCC (IRQ5) cannot get in either; level 7 is the
|
||||
; front-panel NMI and is not ours to mask.
|
||||
;
|
||||
; The vector is the MFP's own: VR is written with the S bit CLEAR, so the
|
||||
; in-service register is not used and an acknowledge clears the pending bit by
|
||||
; itself (mc68901.cpp get_vector). No end-of-interrupt write in the handler.
|
||||
; ---------------------------------------------------------------------------
|
||||
|
||||
; --- MFP registers. The device sits on D0-D7 of a 16-bit bus (x68k.cpp:793,
|
||||
; `.umask16(0x00ff)`), so register n is one BYTE at $E88001 + 2n.
|
||||
MFP = $E88001
|
||||
MFP_GPIP = MFP+0*2
|
||||
MFP_AER = MFP+1*2
|
||||
MFP_DDR = MFP+2*2
|
||||
MFP_IERA = MFP+3*2
|
||||
MFP_IERB = MFP+4*2
|
||||
MFP_IPRA = MFP+5*2
|
||||
MFP_IPRB = MFP+6*2
|
||||
MFP_ISRA = MFP+7*2
|
||||
MFP_ISRB = MFP+8*2
|
||||
MFP_IMRA = MFP+9*2
|
||||
MFP_IMRB = MFP+10*2
|
||||
MFP_VR = MFP+11*2
|
||||
|
||||
MFP_GPIP4 = 4 ; bit number of V-DISP in GPIP/AER/DDR
|
||||
MFP_IVDISP = $40 ; IR_GPIP_4, channel 6, in IERB/IPRB/IMRB
|
||||
MFP_VBASE = $40 ; vector base; S clear -> no in-service register
|
||||
CLK_VEC = (MFP_VBASE+6)*4 ; $118: MFP channel 6 vector, as an ADDRESS
|
||||
|
||||
CRTC = $E80000
|
||||
CRTC_R04 = CRTC+4*2 ; V total, in scanlines, minus one
|
||||
CRTC_R20 = CRTC+20*2 ; mode; bit 4 = 31.5 kHz
|
||||
HFREQ = 31500 ; lines/s in the 31.5 kHz modes. Exact: the
|
||||
; 768-wide IPL mode is 34.776 MHz / 1104 dots
|
||||
; and the 256-wide mode 11.592 MHz / 368, both
|
||||
; 31500.0 (tools/bench/crtc_mode.lua).
|
||||
|
||||
; --- state. PACE is stream.s's, deliberately: the whole point is that the
|
||||
; 68000 now writes the word the host used to write, and the pace gate that
|
||||
; reads it does not change by a single byte.
|
||||
CLK_PACE = $18034 ; == stream.s PACE
|
||||
CLK_ACC = $18060 ; word: Bresenham remainder, < HFREQ
|
||||
CLK_INCR = $18062 ; word: fps * (R04+1), computed by clk_init
|
||||
CLK_VDISP = $18064 ; long: V-DISP edges taken. An INSTRUMENT --
|
||||
; it is what lets a rig check that the tick
|
||||
; count and the raster count are the same clock.
|
||||
CLK_FPS = $18068 ; long: requested fps, an argument to clk_init
|
||||
CLK_ERR = $1806C ; long: 0 ok / 1 not a 31.5 kHz mode
|
||||
; / 2 fps*VTOTAL would overflow 16 bits
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; clk_init -- arm the frame clock. Reads CLK_FPS, leaves CLK_ERR.
|
||||
; Clobbers d0-d2. Leaves the CPU at SR=$2500 on success.
|
||||
; ---------------------------------------------------------------------------
|
||||
clk_init:
|
||||
clr.l CLK_ERR.l
|
||||
clr.l CLK_VDISP.l
|
||||
clr.w CLK_ACC.l
|
||||
clr.l CLK_PACE.l
|
||||
|
||||
; The mode has to be the one HFREQ describes. A 15 kHz mode would halve the
|
||||
; line rate and the divider would run at double speed while looking correct,
|
||||
; which is the failure this test exists to prevent.
|
||||
move.w CRTC_R20.l,d0
|
||||
btst #4,d0
|
||||
bne.s .modeok
|
||||
move.l #1,CLK_ERR.l
|
||||
rts
|
||||
.modeok:
|
||||
; VTOTAL and the increment. Both out of the CRTC, so a change of mode changes
|
||||
; the clock with it. acc is 16-bit and reaches at most HFREQ-1+incr, so incr
|
||||
; must leave room: 65536 - 31500 = 34036. At VTOTAL=568 that is fps < 59.9,
|
||||
; which is every rate this machine can display anyway -- but it is checked
|
||||
; rather than argued.
|
||||
move.w CRTC_R04.l,d0
|
||||
addq.w #1,d0 ; VTOTAL scanlines
|
||||
move.w d0,d1
|
||||
move.w CLK_FPS+2.l,d2 ; low word of the long
|
||||
mulu d2,d1 ; fps * VTOTAL (see FINDINGS 53.4 on
|
||||
; C68K's flat MULU charge; this is boot
|
||||
; code and is not cost-measured there)
|
||||
cmp.l #65536-HFREQ,d1
|
||||
bcs.s .fitok
|
||||
move.l #2,CLK_ERR.l
|
||||
rts
|
||||
.fitok:
|
||||
move.w d1,CLK_INCR.l
|
||||
|
||||
; The vector, before the source is armed.
|
||||
move.l #clk_isr,CLK_VEC.w
|
||||
|
||||
; Disarm everything the IPL left running, then arm GPIP4 alone. Order matters:
|
||||
; IER first (which clears IPR with it), then the edge, then the mask.
|
||||
move.b #0,MFP_IERA.l
|
||||
move.b #0,MFP_IERB.l
|
||||
move.b #0,MFP_IMRA.l
|
||||
move.b #MFP_VBASE,MFP_VR.l ; S clear: acknowledge clears pending
|
||||
bclr #MFP_GPIP4,MFP_DDR.l ; V-DISP is an input
|
||||
bclr #MFP_GPIP4,MFP_AER.l ; interrupt on the FALLING edge, i.e.
|
||||
; at the start of vertical blanking
|
||||
move.b #MFP_IVDISP,MFP_IERB.l
|
||||
move.b #MFP_IVDISP,MFP_IMRB.l
|
||||
move.w #$2500,sr ; let level 6 in; 1-5 stay masked
|
||||
rts
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; clk_stop -- disarm, and put the mask back where the rigs expect it.
|
||||
; ---------------------------------------------------------------------------
|
||||
clk_stop:
|
||||
move.w #$2700,sr
|
||||
move.b #0,MFP_IERB.l
|
||||
move.b #0,MFP_IMRB.l
|
||||
rts
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; clk_isr -- one V-DISP. Every instruction here is charged to every frame the
|
||||
; decoder draws, so it is deliberately the shortest thing that is still exact:
|
||||
; four word operations and one long increment.
|
||||
;
|
||||
; Only the LOW WORD of d0 is touched, so only the low word is saved. The
|
||||
; accumulator, the increment and the threshold all fit in 16 bits by the check
|
||||
; in clk_init, which is what makes that legal.
|
||||
; ---------------------------------------------------------------------------
|
||||
clk_isr:
|
||||
move.w d0,-(sp)
|
||||
addq.l #1,CLK_VDISP.l
|
||||
move.w CLK_ACC.l,d0
|
||||
add.w CLK_INCR.l,d0
|
||||
cmp.w #HFREQ,d0
|
||||
bcs.s .nf
|
||||
sub.w #HFREQ,d0
|
||||
addq.l #1,CLK_PACE.l
|
||||
.nf:
|
||||
move.w d0,CLK_ACC.l
|
||||
move.w (sp)+,d0
|
||||
rte
|
||||
@@ -0,0 +1,59 @@
|
||||
; Front-end for the frame clock (ROADMAP P3), for the rig.
|
||||
;
|
||||
; It exists to answer two questions that the streaming rig cannot answer on its
|
||||
; own, because there the clock is buried under a decoder:
|
||||
;
|
||||
; 1. does the tick actually come from the raster, and at exactly the rate
|
||||
; asked for -- measured over thousands of refreshes, not four;
|
||||
; 2. WHAT IT COSTS, in clocks, per interrupt. This project's currency is
|
||||
; 68000 clocks and the decoder already occupies 86.7% of the bus, so a
|
||||
; frame clock is not free until someone has priced it.
|
||||
;
|
||||
; THE INSTRUMENT, and why it is a busy loop. MAME's Lua only sees the machine
|
||||
; at frame boundaries, so it can time to 1/55.46 s and no finer -- 18 ms, where
|
||||
; the whole per-frame cost of this clock is microseconds. Differencing two
|
||||
; wall timings would measure nothing. So the 68000 counts instead: a loop with
|
||||
; ONE instruction in its body runs for a fixed number of refreshes, and the
|
||||
; iteration count is read out at both ends.
|
||||
;
|
||||
; clock off: iters0 * L = clocks in the window -> L
|
||||
; clock on: iters1 * L + ints * H = clocks in the window -> H
|
||||
;
|
||||
; The window is an exact number of raster frames, so its length in clocks is
|
||||
; exact and does not depend on the host at all. L is calibrated out by the
|
||||
; first run rather than assumed from a cycle table, which matters: the point of
|
||||
; the exercise is to price this code on the machine that will run it, and a
|
||||
; table is the thing being checked. With ~2.7e7 iterations behind it, L carries
|
||||
; enough digits that the interrupt cost -- 0.08% of the window -- survives the
|
||||
; subtraction.
|
||||
;
|
||||
; The body is `addq.l #1,CGCNT.l` and nothing else: no compare, no counter in a
|
||||
; register that an interrupt could be accused of disturbing, and a value that
|
||||
; the host can read at any moment without stopping the CPU.
|
||||
;
|
||||
; The gate does NOT decode anything. What the clock does to a real frame is
|
||||
; tools/bench/stream.lua's question, with DLX_PACE=2.
|
||||
|
||||
CGFLAG = $18070 ; 0 idle / 1 running / $EE clk_init refused
|
||||
CGON = $18074 ; 1 = arm the frame clock, 0 = leave it off
|
||||
CGCNT = $18078 ; <- loop iterations, read by the host at both
|
||||
; ends of the window
|
||||
|
||||
org $10000
|
||||
start:
|
||||
clr.l CGCNT.l
|
||||
move.l CGON.l,d0
|
||||
beq.s noclk
|
||||
bsr clk_init
|
||||
tst.l CLK_ERR.l
|
||||
bne.s bad
|
||||
noclk:
|
||||
move.l #1,CGFLAG.l ; the host starts its window here
|
||||
loop:
|
||||
addq.l #1,CGCNT.l
|
||||
bra.s loop
|
||||
bad:
|
||||
move.l #$EE,CGFLAG.l
|
||||
hold: bra.s hold
|
||||
|
||||
include "src/player/clock.i"
|
||||
@@ -4,7 +4,8 @@
|
||||
; does nothing the shipping player would not do, so that the bytes being
|
||||
; measured are the bytes that will ship. The player's own boot path will call
|
||||
; do_load once with the mode bits set to 3; this repeats it LITER times so a
|
||||
; host clock with 1/55.46 s granularity can time a job that takes milliseconds,
|
||||
; host clock with 1/56.69 s granularity (tools/bench/crtc_mode.lua) can time a
|
||||
; job that takes milliseconds,
|
||||
; and splits it by LMODE so the codebook expansion and the palette pack can be
|
||||
; priced apart. A player calls do_load with mode 7 once at boot -- the three
|
||||
; scratch tables describe the machine, not the scene -- and with mode 3 at every
|
||||
|
||||
@@ -81,6 +81,18 @@ PACE = $18034 ; producer -> decoder: frame ticks elapsed since
|
||||
; release. Frame i may not START before tick i.
|
||||
PACEON = $18038 ; 1 = obey PACE. 0 leaves the loop free-running,
|
||||
; byte for byte the loop FINDINGS 49 measured.
|
||||
LATEFR = $18080 ; frames that reached the pace gate with their
|
||||
; tick ALREADY ARRIVED, i.e. did not idle for a
|
||||
; single poll -- the previous frame used its
|
||||
; whole slot. See the gate below.
|
||||
LATEMAX = $18084 ; the worst of those, in WHOLE ticks overrun
|
||||
LATE1ST = $18088 ; index of the FIRST such frame, so that a
|
||||
; count can be told apart from a start-up
|
||||
; transient without re-running anything
|
||||
CLKON = $1803C ; 1 = the 68000 paces ITSELF: src/player/clock.i
|
||||
; drives PACE off the CRTC's V-DISP instead of
|
||||
; the host writing it. Needs PACEON=1; the gate
|
||||
; below cannot tell the two apart and must not.
|
||||
DESC = $18100 ; DESCN x u32, record base addresses
|
||||
|
||||
DESCN = 64 ; power of two; the index is masked, not compared
|
||||
@@ -92,12 +104,27 @@ SPINMAX = 2000000 ; polls with no progress before giving up
|
||||
|
||||
org $10000
|
||||
start:
|
||||
; ---- the frame clock, if this run is asking the 68000 to keep its own time.
|
||||
; It goes here rather than inside the frame loop because clk_init CLEARS PACE:
|
||||
; tick 0 has to be the instant the decoder was released, exactly as it is when
|
||||
; the host writes PACE, or the first frame's deadline moves.
|
||||
tst.l CLKON.l
|
||||
beq.s noclk
|
||||
bsr clk_init
|
||||
tst.l CLK_ERR.l
|
||||
beq.s noclk
|
||||
move.l #$E2,FLAG.l ; the clock refused; CLK_ERR says why
|
||||
bra hold
|
||||
noclk:
|
||||
move.l #1,FLAG.l ; timer starts here
|
||||
outer:
|
||||
move.l NFR.l,SCR_N.l
|
||||
clr.l FR_TAIL.l
|
||||
clr.l STALLS.l
|
||||
clr.l SPINS.l
|
||||
clr.l LATEFR.l
|
||||
clr.l LATEMAX.l
|
||||
move.l #-1,LATE1ST.l
|
||||
frameloop:
|
||||
; ---- PACE GATE (FINDINGS 49.7.2, and it is the whole point of this session).
|
||||
; Free-running, this loop asks for record i the instant it finishes record i-1,
|
||||
@@ -117,8 +144,44 @@ frameloop:
|
||||
;
|
||||
; It also makes STALLS mean something. Free-running, a stall is EARLINESS
|
||||
; (49.6); paced, a frame that has to wait for its record is a real underrun.
|
||||
;
|
||||
; AND IT COUNTS THE FRAMES THAT WERE ALREADY LATE, which is a question only a
|
||||
; REAL frame clock raises. 12 fps on a 55.4577 Hz raster is 4.6215 refreshes
|
||||
; per frame, so the divider hands out slots of 4 refreshes (72.13 ms) and 5
|
||||
; (90.16 ms), 37.9% of them short -- and the SHORT one is 13.4% under the
|
||||
; 83.33 ms every budget in this project is priced against (FINDINGS 54). A
|
||||
; frame that does not fit its slot does not fail here: PACE has already moved
|
||||
; on, so the next frame starts the instant this one finishes and the clock
|
||||
; catches up by itself. What it costs is one late PRESENT, and nothing in this
|
||||
; tree counted those because until now the tick was a host model with no
|
||||
; cadence in it at all.
|
||||
;
|
||||
; The test is "did this frame have to WAIT", not "is it a whole tick behind".
|
||||
; Frame i waits while PACE < i; so PACE >= i on arrival means the decoder came
|
||||
; to the gate with slot i already open and idled for zero polls, which is the
|
||||
; same statement as "frame i-1 ran to the end of its slot". A whole tick of
|
||||
; overrun -- PACE - FR_TAIL >= 1 -- is the much rarer case where it ran past
|
||||
; the end of the NEXT one, and is reported separately as the worst seen.
|
||||
;
|
||||
; Frame 0 is excluded: it starts at tick 0 by definition and has no predecessor
|
||||
; to have overrun. The wait loop below is untouched -- all of this is ahead of
|
||||
; it, and the free-running path executes none of it.
|
||||
tst.l PACEON.l
|
||||
beq.s nopace
|
||||
move.l PACE.l,d0
|
||||
cmp.l FR_TAIL.l,d0 ; PACE < FR_TAIL: the slot has not come
|
||||
bcs.s pacewait ; round yet, so this frame is EARLY
|
||||
tst.l FR_TAIL.l
|
||||
beq.s pacewait ; frame 0 starts AT tick 0 by definition
|
||||
tst.l LATEFR.l
|
||||
bne.s .nf1
|
||||
move.l FR_TAIL.l,LATE1ST.l
|
||||
.nf1:
|
||||
addq.l #1,LATEFR.l
|
||||
sub.l FR_TAIL.l,d0 ; whole ticks overrun; 0 = inside the
|
||||
cmp.l LATEMAX.l,d0 ; slot but with nothing left of it
|
||||
bls.s pacewait
|
||||
move.l d0,LATEMAX.l
|
||||
pacewait:
|
||||
move.l PACE.l,d0
|
||||
cmp.l FR_TAIL.l,d0 ; d0 - FR_TAIL; carry = tick not reached
|
||||
@@ -176,9 +239,17 @@ nostall:
|
||||
bne frameloop
|
||||
subq.l #1,ITER.l
|
||||
bne outer
|
||||
; ---- leave the MFP as it was found. A rig that exits with a live interrupt
|
||||
; source and a lowered mask hands the next thing that runs an interrupt it
|
||||
; has no vector for, and the failure would land somewhere else entirely.
|
||||
tst.l CLKON.l
|
||||
beq.s noclk2
|
||||
bsr clk_stop
|
||||
noclk2:
|
||||
move.l #$FF,FLAG.l ; timer stops here
|
||||
hold: bra.s hold
|
||||
desync: move.l #$EE,FLAG.l
|
||||
bra.s hold
|
||||
|
||||
include "src/player/frame.i"
|
||||
include "src/player/clock.i"
|
||||
|
||||
Reference in New Issue
Block a user