____ __ __
\ // / -- ------------------------------------------- - -__/ \
\ \ Fixed-tacts routines by mike/zeroteam / \__/
--\ -------------------------------------------------- - \__/ \
\__/
Recently, our graphician broke one part of a demo by ordering a multicolour in the border in the centre of the screen.
This halved the time available for other effects, as half a frame was spent waiting for the beam to reach the correct position on the screen. But that is an enormous waste of time.
That's why I rewrote the effects (nothing fancy - just drawing sprites onto the screen) as fixed-tacts routines.
The basic routine for drawing on the screen is DOWNDE, which calculates the address of the next row of pixels in videoram. A typical routine looks something like this:
DOWNDE inc d ;4T
ld a,d ;4T
and 7 ;7T
jr nz,DOWNDE1 ;12/7T
ld a,e ;4T
add a,32 ;7T
ld e,a ;4T
jr c,DOWNDE1 ;12/7T
ld a,d ;4T
sub 8 ;7T
ld d,a ;4T
DOWNDE1
;min: 27T, max: 59T
Two conditional jumps. Is it even possible to rewrite this as a routine with a constant time complexity? Let's give it a go:
DOWNDE_T
inc d ;4T
ld a,d ;4T
and 7 ;7T
sub 1 ;7T
sbc a,a ;4T
and -8 ;7T
ld (DOWNDE_D+1),a ;13T
and 32 ;7T
add a,e ;4T
ld e,a ;4T
ccf ;4T
sbc a,a ;4T
DOWNDE_D and -8 ;7T
add a,d ;4T
ld d,a ;4T
;=84T
Instead of using a self-modifying code, we could sacrifice one of the regular registers and save 12T, but this way the routine behaves exactly the same as the original one. Also what should we do about the routine for drawing sprites?
The sprite routine is very simple. Besides the data address and the address in the video RAM, it also expects the mask address (I mask objects on the screen) and the sprite width (maximum 13 characters). The main loop looks like this:
;input: hl - sprite data
; de - address in videoram
; bc - address of mask
; c' - width of sprite
exx
ld b,c
S_LOOP1 exx ;4T
ld a,(bc) ;7T
and (hl) ;7T
ld (de),a ;7T
inc hl ;6T
inc c ;4T
inc e ;4T
exx ;4T
djnz S_LOOP1
;=43T
To ensure that the routine takes the same number of clock cycles for every sprite width, we'll add another loop that extends the routine's duration to match the rendering time of the widest sprite. One pass through the new loop should take 43 clock cycles. However, the problem here is that the instruction ld (de),a accesses slow memory and does not always take the same number of clock cycles. But the instruction ld a,(de) does the same thing! So let's try expanding our routine:
exx
ld b,c
S_LOOP1 exx ;4T
ld a,(bc) ;7T
and (hl) ;7T
ld (de),a ;7T
inc hl ;6T
inc c ;4T
inc e ;4T
exx ;4T
djnz S_LOOP1
ld a,13+1 ;we don't like 0 as a loop count
sub c
ld b,a
S_LOOP2 exx ;4T
ld a,(bc) ;7T
and (hl) ;7T
ld a,(de) ;7T
ld iy,23610 ;14T
exx ;4T
djnz S_LOOP2
Does this routine take exactly the same amount of time every time? I doubt it. But it's enough to time a stable multicolour in the border. The instruction ld a,(de) is essential; without it, it didn't work for me.
I wish you lots of fixed-tact routines mike/zeroteam
Share your thoughts about the article