Weeks later than I had originally planned, the Amiga OCS is now greeted with a small Hello World sample. Since this is my first time coding the Amiga, most of the time was spent on reading various hardware documentation on what registers to set and why. As I have somewhat more experience with the Atari ST, I will comment on the difference between the two platforms as well as trying to explain what the Amiga code does.
After some trial and error coding, I decided to write a more system friendly version for the Amiga, to keep the code-build-test cycle as quick as possible. It took way too much time to restart the emulator after every test.
The Amiga and Atari are both based on the Motorola 68000 CPU family, so I am well-versed in the assembly language. The OS and other hardware on the other hand, that was not as easy to get the grips on.
The first thing to do in our little program is to save the current state of all the registers our program will change, so we can restore them to the same state when the program exist. For the address of the default copper lists (more info on the copper later) I load the graphics.library via the OldOpenLibrary system call, and get the addresses for the two lists.
The address to the exec library, which is the core OS module of the Amiga that handles loading of libraries and other basic needs, can be found at position $4 in the memory.
Load “graphics.library” by placing the address to library name string in CPU register a1, and jump to the “OldOpenLibrary” function at offset -408 from the exec library.
The return value will be in d0.
lea lib_name,a1 jsr -408(a6) move.l d0,a1
Now the handle to the graphics.library is in register a1, where we can use it as an pointer. Now it’s time to get the address to where we want to save the register data and save the copper list addresses in that memory area. When we are done with that, we can close the graphics library.
lea saved_regs,a2 move.l $26(a1),(a2)+ ; save old copperlist 1 move.l $32(a1),(a2)+ ; save old copperlist 2 jsr -414(a6) ; close lib
Since a2 pointer de-reference used the post-increment syntax, the content of a2 is counted up by the size of data that was written. We continue to use this syntax as we save some other registers that we need to change later on.
lea $dff000,a1 ; Base address to hardware registers move.w $1c(a1),(a2)+ ; Save IRQ enable flags. move.w $2(a1),(a2)+ ; Save DMACON register
Now that we have saved the registers, we can move to the core business - Setup the screen and draw some data to it.
First off, lets only enable DMA for bitplanes and copper. Writing to the DMA and IRQ registers with the highest bit (16) cleared, the functionality corresponding to the set bits will be disabled. To enable functionality, you must write the mask for the functionality you want to set plus the highest bit set. So writing $7fff will disable all, and $ffff will enable all.
move.w #$7fff,$9a(a1) ; Disable interrupts move.w #$7fff,$96(a1) ; Disable all DMA. move.w #$8380,$96(a1) ; Re-enable DMA for copper and bitplane data.
Reusing the graphics from ST version, lets copy our graphics to screen. The Amiga screen is a bit different than of the ST. The bitplanes of the Atari are interleave, where on the Amiga, each bitplane is stored in one continuous block of memory.
lea screen_mem,a0 ; Get address to screen memory lea gfx,a3 ; Get address to the graphics. moveq #5,d0 ; Loop 6 times. .draw_loop: move.w (a3)+,(a0)+ ; Move a word to graphics mem move.w (a3)+,(a0)+ move.w (a3)+,(a0)+ lea 34(a0),a0 ; Skip 40 bytes, which corresponds to on line dbra d0,.draw_loop ; Decrease d0 and jump to draw_loop if d0 not -1.
Set up the copper data. Most of the data in the copper list have been prepared beforehand, but the address the the screen memory must be updated once the code is executing.
lea vmem_ptr+2,a0 ; Offset to video register setup in copper list move.l #screen_mem,d0 ; Get address to screen memory move.w d0,4(a0) ; Move low word of address to copper data. swap d0 ; swap position of low and high word in d0 move.w d0,(a0) ; Write high word of address to copper data.
Write the address to our copper list data to the co-processor hardware register. Writing to the strobe register reloads the register and starts to execute the list.
move.l #copper_list,$80(a1) ; Start using our copper list. clr.w $88(a1) ; Strobe
Loop until the left mouse button is pressed.
.loop: btst #6,$bfe001 ; Test it left mouse button is pressed. bne.s .loop ; If nop - jump to .loop label.
Restore Registers. Like I mentioned before, the highest bit must be set to enable functionality.
move.l copper_1,$80(a1) ; restore copperlist 1 move.l copper_2,$84(a1) ; restore copperlist 2 move.w dmacon,d0 ; restore dma control register. ori.w #$8000,d0 ; Set high bit to enable 'set' move.w d0,$96(a1) move.w irqena,d0 ; Restore IRQ enabled register. ori.w #$8000,d0 move.w d0,$9a(a1) ; Re-enable IRQs. rts
Code is done! Now we have to add the graphics and data used by the program. The section statement below tells the assembler to put the data in the “chip memory”, which on the Amiga is memory that can be accessed by the DMA sub-system.
Define lib_name variable as a string containing the name of the graphics library that we load at the beginning of the code. The even statement informs the assembler that the statement should be placed on an even address, since reading 16- or 32-bit values from odd addresses result in an error.
lib_name: dc.b "graphics.library",0 even
Reuse graphics from the Atari ST version. Since both the Amiga and Atari ST both use bitplane graphics, the data can be reused without changes.
gfx: dc.w %0100010000000101,%0000000100010000,%0000000100001010 dc.w %0100010011100101,%0011000100010011,%0001100100111010 dc.w %0100010100010101,%0100100100010100,%1010010101001010 dc.w %0111110111110101,%0100100101010100,%1010000101001010 dc.w %0100010100000101,%0100100101010100,%1010000101001000 dc.w %0100010011110101,%0011000010100011,%0010000100111010
Finally we reach the copper list. The copper, with is kind of a good nick name for co-processor, is a processing unit that can execute a few simple types of instruction. The most common probably being writing data to a hardware register in the memory space of the hardware registers.
copper_list: dc.w $0100,$1200 ; Bitplane setup, 1 bitplane, color. dc.w $0102,$0000 ; Horisontal scroll dc.w $0108,$0000 ; modulo-register, odd scan lines dc.w $010a,$0000 ; modulo-register, even scan lines dc.w $008e,$2c81 ; Display window start (top left corner) dc.w $0090,$2cc1 ; Display window end (bottom right corner) dc.w $0092,$0038 ; Data fetch start dc.w $0094,$00d0 ; Data fetch stop. vmem_ptr: dc.w $00e0,$0000 ; high word of bitplane 0 address dc.w $00e2,$0000 ; low word of bitplane 0 address dc.w $0180,$0000 ; Write to color 0 register.
Just for fun I added a so called copper bar, or raster bar. To achieve the similar effect on Atari, a lot of more work is involved. Some day I might show you how it’s done on the ST.
dc.w $2b01,$ff00,$0180,$0012 dc.w $2c01,$ff00,$0180,$0123 dc.w $2d01,$ff00,$0180,$0234 dc.w $2e01,$ff00,$0180,$0345 dc.w $2f01,$ff00,$0180,$0234 dc.w $3001,$ff00,$0180,$0123 dc.w $3101,$ff00,$0180,$0012 dc.w $3201,$ff00,$0180,$0001 dc.w $3301,$ff00,$0180,$0000
The last entry in the copper list tells the copper to wait until it reaches a line that is below the end of the screen, and will therefore not do anything more.
cend: dc.w $ffff,$fffe
BSS section where we reserve some memory for the registers we save. We also reserve memory enough for one bitplane at a resolution of 320*256.
section bss_c saved_regs: copper_1: ds.l 1 copper_2: ds.l 1 irqena: ds.w 1 dmacon: ds.w 1 adkcon: ds.w 1 even screen_mem: ds.b 320*256/8
That’s it. Assemble and run.
Disclaimer: I don’t know enough of the Amiga to say that this code does everything it’s supposed to do. It might have some unknown side effects that I am not aware of. There might also be factual errors in the blog post. Feel free to point them out if you find any.
Here are some useful links if you are interested in learning more and test some Amiga programming yourself.