Videopac G7000 BIOS
Last changed: 2003-07-22

Tips and tricks

Here I document some useful tricks I discovered while learning to program the G7000 / Odyssey² with asl. The code examples shown here are not directly taken from a real program, although I have used them all while experimenting with the machine. So there are no demo programs, just these code snippets.

Argh! Intel forgot the subtraction opcodes

Yes, the 8048 doesn't have any commands for subtraction. This is not a big problem, just basic knowledge about computer arithmetics. But it took me some time to figure it out after I saw it in the BIOS. The trick is to calculate -R2 + R1 instead of R1 - R2. For -R2 I have to use 2's complement: invert all bits and add 1. So the full code to calculate R1 - R2 is:


        mov     a,r2    ; second operand
        cpl     a       ; invert bits
        inc     a       ; add 1: a contains -R2
        add     a,r1

BCD arithmetic

One feature of the 8048 is quite useful for numbers which need to be displayed: The 8048 can use BCD arithmetics. All you need to do is interpret numbers in hex as decimal: so 010h represents 10. This way it is much easier to display the numbers, just take the upper and lower 4 bits and display them separately, you no longer have to divide by 10. To calculate in BCD first add the numbers as usual. The 8048 has an auxiliary carry flag that shows overflows from the lower to the higher nibble. This information is now used by the DA A opcode to correct any non-BCD range nibbles. The A register contains the correct result in BCD afterwards, the carry flag is set if necessary. So this is code to calculate 18 + 89:


        mov     a,#018h
        add     a,#089h ; now in a: 0a1h
        da      a       ; now in a: 007h, carry set

Not all commands change the carry flag

This trick works on nearly all microprocessors, not only on the 8048. The carry flag is only changed by some commands, mainly the addition commands. So you can insert some code statements between the carry changing command and the jc/jnc. For example:


        add     a,#010h         ; set carry, when a>=0f0h
        mov     r0,#020h        ; carry not changed
        mov     a,@r0           ; carry not changed
        jc      somewhere

This trick only works with the carry flag. There is no zero flag, jz jumps when A is 0.

Saving pointers: aligning internal & external RAM

If you run out of internal RAM and have to put some data into external RAM you should try to put related data into the same addresses. That way you only need one pointer register:


iram_x  equ 020h                ; x position
eram_y  equ 020h                ; y position

        mov     r0,#iram_x      ; pointer to position data
        mov     a,@r0           ; x position in internal ram
        mov     r3,a
        movx    a,@r0           ; y position in external ram
        mov     r4,a

Cutting off the top of chars

It is possible to only display the bottom parts of characters. To achieve this you have to fill in the char bytes per hand instead of calling printchar. Bytes 0 and 1 are simply just the y and x position. For bytes 2 and 3 you can call calcchar23. For every line you want to cut off from the character you have to increase the char-map pointer in r5 by 1. You may have to manipulate bit 0 in r6 also as this is the 9th bit of the char pointer. This depends on the exact y position on the screen and can be omitted if the char does not move around as in this example, which displays a vertical bar by cutting off the horizontal part of a T:


        mov     r4,#GROUNDLEVEL - 6 ; y position
        mov     a,r4
        movx    @r0,a
        inc     r0
        mov     a,r3
        movx    @r0,a
        inc     r0
        mov     r5,#014h            ; T
        mov     r6,#col_chr_yellow
        call    calcchar23          ; calculate byte 2/3
        mov     a,r5                ; get char-pointer
        inc     a                   ; advance one line...
        movx    @r0,a               ; ...to cut off first line of T
        inc     r0
        mov     a,r6                ; get byte 3
        movx    @r0,a               ; store it
        inc     r0

This trick is used in many of the commercial games, e.g. the enemies in Killer Bees are constructed by combining the lower parts of 8 and the feet of the running mans.

Cutting off for quads

Advancing the char-map pointer does work for quads, too. The number of lines that are drawn are controlled by the last sub-quad. The other 3 sub-quads draw exactly the same number of lines, the normal character ends are ignored. So you can cut off the bottom of the first 3 sub-quads, just set the last sub-quad to a space with the number of lines to display. The first 3 sub-quads are now cut off at the bottom when used without offset. With offset you can move freely inside the whole char bitmap, if one character is finished the next one is drawn with a one pixel empty line between them.

This trick does not work in o2em version 1.01.

Using P1.7 for bright background colors

If you clear port 1 bit 7 dark colors are made brighter. This means that the colors for background and dark grid are now identical to the colors for chars, quads, sprites and bright grid. Clearing P1.7 is not easy, all BIOS routines which touch P1 set it. Among these routines are vdcenable, extramenable, getjoystick and the interrupt routines. So to keep P1.7 cleared you have to replace a lot of BIOS routines with your own versions.

Sections and local labels

One feature of asl is the possibility to divide programs into sections. They start with section name and end with endsection name. All labels inside a section are local to the section, so you can reuse common names like loop etc:


        section code1
        mov     r2,#3
loop    ; do something here
        djnz    r2,loop
        endsection code1

        section code2
        mov     r2,#4
loop    ; do something different here
        djnz    r2,loop
        endsection code2

More info about this can be found in the documentation of asl. This reuse of local labels is something that I have found convenient. One could argue that having labels with the same name is a source of confusion, but I like them.

Using asl features to prevent page misses

One of the problems any programmer on the G7000 / Odyssey² has to fight is the page alignment of ROM data and code which accesses it. One way to provoke error messages when assembling works by using subtracting the high byte of the current ROM position multiplied by 256 from the start of the table to get the low-byte. But this only keeps the start of the table in the same page. So at the end of the table I need to compare the high-bytes of the first and last table entry. For that I use conditional assembly to generate an error message if they are not equal.


addscore section addscore
        add     a,#scoretable - hi($)*0100h
        movp    a,@a
        ; ... lines snipped
        ret
        endsection addscore

scoretable
        db      5,15,10

        if (hi(scoretable) <> hi($))
        error "scoretable crosses page border"
        endif

Defining and using an assert macro

A more versatile way to achieve the same result is the usage of an assert macro. It takes an expression included into "" as argument. At assemble time this argument is evaluated. If it is not true an error message is generated.


assert macro expr
	if (~~val(expr))
	error expr
	endif
	endm

Here is the example from above, this time using the assert macro.


scoretable
        db      5,15,10

        assert "hi(scoretable) == hi($)"