We have reached Letter V of the Amiga Machine Code course.
The program examples are now so big, that we have to read them from the course disk Disk 1
Below is a listing of the program mc0501 that shows a sprite of a flag of Norway, that scrolls down the screen. I have annotated the program and below the listing I will dive into a couple of details.
; mc0501 move.w #$4000,$dff09a ; POTINP - clear external interrupt or.b #%10000000,$bfd100 ; CIABPRB stops drive motors and.b #%10000111,$bfd100 ; CIABPRB move.w #$01a0,$dff096 ; DMACON clear bitplane, copper, sprite move.w #$1200,$dff100 ; BPLCON0 one bitplane, color burst move.w #$0000,$dff102 ; BPLCON1 scroll move.w #$003f,$dff104 ; BPLCON2 video move.w #0,$dff108 ; BPL1MOD bitplane modulo odd planes move.w #0,$dff10a ; BPL2MOD bitplane modulo even planes move.w #$2c81,$dff08e ; DIWSTRT upper left corner of display ($81,$2c) move.w #$f4c1,$dff090 ; DIWSTOP enable PAL trick move.w #$38c1,$dff090 ; DIWSTOP lower right corner of display ($1c1,$12c) move.w #$0038,$dff092 ; DDFSTRT Data fetch start move.w #$00d0,$dff094 ; DDFSTOP Data fetch stop lea.l sprite,a1 ; put sprite address into a1 lea.l copper,a2 ; put copper address into a2 move.l a1,d1 ; move sprite address into d1 move.w d1,6(a2) ; transfer sprite address high to copper swap d1 ; swap move.w d1,2(a2) ; transfer sprite address low to copper lea.l blanksprite,a1 ; put blanksprite address into a1 lea.l copper,a2 ; put copper address into a2 add.l #10,a2 ; add 10 to copper address in a2 move.l a1,d1 ; move blanksprite address into d1 moveq #6,d0 ; setup sprite counter sprcoploop: ; set all 7 sprite pointers swap d1 ; high and low to point to blanksprite move.w d1,(a2) addq.l #4,a2 swap d1 move.w d1,(a2) addq.l #4,a2 dbra d0,sprcoploop ; loop trough all 7 sprite pointers lea.l screen,a1 ; put screen address into a1 lea.l bplcop,a2 ; put bplcop address into a2 move.l a1,d1 ; transfer screen address to bplcop move.w d1,6(a2) swap d1 move.w d1,2(a2) lea.l copper,a1 ; put copper address into a1 move.l a1,$dff080 ; COP1LCH (also sets COP1LCL) move.w $dff088,d0 ; COPJMP1 move.w #$81a0,$dff096 ; DMACON set bitplane, copper, sprite wait: ; wait until at beam line 0 move.l $dff004,d0 ; read VPOSR and VHPOSR into d0 as one long word asr.l #8,d0 ; shift right 8 places and.l #$1ff,d0 cmp.w #0,d0 bne wait ; if not equal jump to wait wait2: ; wait until at beam line 1 move.l $dff004,d0 ; read VPOSR and VHPOSR into d0 as one long word asr.l #8,d0 and.l #$1ff,d0 cmp.w #1,d0 bne wait2 ; if not equal jump to wait bsr movesprite ; branch to subroutine movesprite btst #6,$bfe001 ; test left mouse left mouse click bne wait ; if not pressed jump to wait move.w #$0080,$dff096 ; reestablish DMA's and copper move.l $04,a6 move.l 156(a6),a1 move.l 38(a1),$dff080 move.w #$8080,$dff096 move.w #$c000,$dff09a rts movesprite: ; movesprite subroutine lea.l sprite,a1 cmp.b #250,2(a1) ; sprite bottom line at 250 bne notbottom ; if not go to notbottom move.b #30,(a1) move.b #44,2(a1) notbottom: add.b #1,(a1) ; move sprite top line by 1 add.b #1,2(a1) ; move sprite bottom line by 1 rts ; return from subroutine copper: dc.w $0120,$0000 ; SPR0PTH dc.w $0122,$0000 ; SPR0PTL dc.w $0124,$0000 ; SPR1PTH dc.w $0126,$0000 ; SPR1PTL dc.w $0128,$0000 ; SPR2PTH dc.w $012a,$0000 ; SPR2PTL dc.w $012c,$0000 ; SPR3PTH dc.w $012e,$0000 ; SPR3PTL dc.w $0130,$0000 ; SPR4PTH dc.w $0132,$0000 ; SPR4PTL dc.w $0134,$0000 ; SPR5PTH dc.w $0136,$0000 ; SPR5PTL dc.w $0138,$0000 ; SPR6PTH dc.w $013a,$0000 ; SPR6PTL dc.w $013c,$0000 ; SPR7PTH dc.w $013e,$0000 ; SPR7PTL dc.w $2c01,$fffe dc.w $0100,$1200 bplcop: dc.w $00e0,$0000 ; BPL1PTH dc.w $00e2,$0000 ; BPL1PTL dc.w $0180,$0000 ; COLOR00 black dc.w $0182,$0ff0 ; COLOR01 yellow dc.w $01a2,$0f00 ; COLOR17 sprite0 red dc.w $01a4,$0fff ; COLOR18 sprite0 white dc.w $01a6,$000b ; COLOR19 sprite0 blue dc.w $ffdf,$fffe ; wait($df,$ff) enables waits > $ff vertical dc.w $2c01,$fffe ; wait($01,$12c) - $2c is $12c dc.w $0100,$0200 ; BPLCON0 unset bitplanes, enable color burst ; needed to support older PAL chips dc.w $ffff,$fffe ; end of copper screen: blk.b 10240,0 ; allocate 1 kb of memory and set it to zero sprite: dc.w $1e8c,$2c00 dc.w $FB7F,$0780 ; %1111 1011 0111 1111, %0000 0111 1000 0000 dc.w $FB7F,$0780 ; %1111 1011 0111 1111, %0000 0111 1000 0000 dc.w $FB7F,$0780 ; %1111 1011 0111 1111, %0000 0111 1000 0000 dc.w $FB7F,$0780 ; %1111 1011 0111 1111, %0000 0111 1000 0000 dc.w $FB7F,$0780 ; %1111 1011 0111 1111, %0000 0111 1000 0000 dc.w $0300,$FFFF ; %0000 0011 0000 0000, %1111 1111 1111 1111 dc.w $FFFF,$FFFF ; %1111 1111 1111 1111, %1111 1111 1111 1111 dc.w $FFFF,$FFFF ; %1111 1111 1111 1111, %1111 1111 1111 1111 dc.w $0300,$FFFF ; %0000 0011 0000 0000, %1111 1111 1111 1111 dc.w $FB7F,$0780 ; %1111 1011 0111 1111, %0000 0111 1000 0000 dc.w $FB7F,$0780 ; %1111 1011 0111 1111, %0000 0111 1000 0000 dc.w $FB7F,$0780 ; %1111 1011 0111 1111, %0000 0111 1000 0000 dc.w $FB7F,$0780 ; %1111 1011 0111 1111, %0000 0111 1000 0000 dc.w $FB7F,$0780 ; %1111 1011 0111 1111, %0000 0111 1000 0000 dc.w $0000,$0000 ; %0000 0000 0000 0000, %0000 0000 0000 0000 dc.w $0000,$0000 ; %0000 0000 0000 0000, %0000 0000 0000 0000 blanksprite: dc.w $0000,$0000 ; an empty sprite
The sprite it self is defined as 16 pixels in width, while you define the length. A sprite can have four colors, one of which is a transparent color, and can be placed at any position on the screen. The first color register in the group of four color registers is ignored and thus is the transparent color.
A sprite is build by a series of longs.
long 1 ; position and height long 2 ; sprite data long 3 ; sprite data ... long N ; zeros denotes the last line of the sprite.
The first long of a sprite defines the position and height of the sprite. It’s a little bit convoluted becasue we can’t fit any screen position into 8 bits. Here’s a schematic.
The schematic shows that the sprite starts at line $1e and ends at line $2c. The horizontal position of the left side starts at $8c * 2 = $118. We multiply with two because the first control bit is bit 0.
The sprite graphics data works the same way as bitplanes. Each line of sprite graphics data takes two words, where each word defines a bitplane of 16 pixels width. With this setup we can have four colors.
The color register assignments can be seen in the Amiga Hardware Reference Manual.
The next example in the letter is mc0502, which is also found on Disk 1. It is twice as long as the above program, so I won’t list it here. It’s also almost similar in principle to the first example.
However, the mc0502 program has more sprites and a background image. The sprites move according to a pattern defined in a table. Both background and table should be loaded into Seka.
SEKA>ri FILENAME>screen BEGIN>screen END> SEKA>ri FILENAME>movetable BEGIN>movetable END>
Here’s a screenshot.The sprites spells “letter course” in norwegian.
The movement pattern is stored in a table, because it’s quite expensive to calculate.
Trigonomic functions like sine and cosine are also non-trivial to implement in assembler. Hence, there grew a need for wave generator programs, that could produce such tables. One such program is called wavegen and can be found on Disk 1.
These days, we all have access to floating point hardware, so we don’t see the lookup table optimization technique so much anymore. Functions like sine and cosine has the interesting property of being pure functions. That is, a function that given the same input produces the same output. E.g. sine to 90 degrees is always the same. Such class of functions can be completely replaced by table lookups.
There’s an interesing note about using tables to store movements over at coppershade.org. Go and take a look, it’s a fantastic site.
It’s expensive to calculate sine and cosine, but not impossible. Many used an algorithm from 1959 called CORDIC or Volder’s algorithm that only relied on addition, subtraction, bitshift and table lookups. This algorithm has found it’s way into many chips, includning the floating point coprocessors from Motorola, the 68881 and 68882.