Often when I write in machine language, I start by writing small proof of concept programs in BASIC. The maze program I wrote in the last post is a good example of that. It works as a sort of flow chart for this assembly language program.

This post will be taking that BASIC maze builder and writing an assembly language program to do the same thing, more or less. I did make some changes because sometimes things don’t translate exactly how I would like them to, for me at least.

In part 1, we’ll tackle copying the ROM character data into RAM and adding the custom characters used by the maze itself. We’ll also take a look at the series of bytes at the beginning of the program that create the BASIC SYS statement that launches the machine language program.

In part 2, we’ll look at making the grid on screen to start, my solution to random numbers that don’t fit nicely as a power of 2 number and the making of the maze.

Download the maze2.d64
The full listing of BASIC program
The full listing of the ASM program

The Program

The assembly program starts with some variables and registers we use throughout the program. Most of these are explained in the program and can be found in the Commodore 64 Programmer’s Reference Guide. For example, vicscn is the start of screen memory, in decimal it is 1024, in hex, $0400. You will notice that we are also using voice 3 of the sid chip. This is to generate random numbers for our wanderer in the program.

cROM    =       $d000   ;character ROM start location
cRAM    =       $3000   ;location for new character RAM
cchar   =       $3280   ;start of custom chars
vncsb   =       $d018   ;VIC-II Memory Chip Control Register
ciacra  =       $dc0e   ;Control register A
vicscn  =       $0400   ;starting location of graphics screen
ColorR  =       $d800   ;starting location of color ram
r6510   =       $01     ;6510 chip memory selector
chrout  =       $ffd2   ;kernal print routine
cls     =       $93     ;Clear screen character
vcreg3  =       $d412   ;SID V3 CONTROL REGISTER
frelo3  =       $d40e   ;SID V3 frequence LO
random  =       $d41b   ;READ SID V3 OSCILLATOR
stack   =       $3800   ;start of stack space
charc   =       $0f     ;charactrer color of maze (grey)
charb   =       $0e     ;maze frame color (light blue)

The BASIC Line.

The first line of code is a series of bytes at the beginning of the program.

*=$0801

        ; 10 SYS (2064)
        BYTE    $0E, $08, $0A, $00, $9E, $20, $28,  $32, $30, $36, $34, $29, $00, $00, $00

These bytes make up the the BASIC program you see when you list the program. When run, it will start the machine language program. Here’s the breakdown of the bytes:

$0801  $0E, $08          - The address (lo/hi) of the next BASIC
                           program line. In this case, $080e which is 
                           two $00 bytes telling the BASIC
                           interpreter that it is at the end of the
                           program.
$0803  $0A, $00          - The lo/hi bytes of the BASIC line number. In
                           this case, line 10.
$0805  $9E               - The token byte for the SYS command. You can
                           find all the tokens on the C64-Wiki page
                           (link below).
$0806  $20               - A space character - Equivalent to CHR$(32) 
$0807  $28               - Open parentheses character        CHR$(40) 
$0808  $32               - The number 2 character            CHR$(50)
$0809  $30               - The number 0 character            CHR$(48)
$080A  $36               - The number 6 character            CHR$(54)
$080B  $34               - The number 4 character            CHR$(52)
$080C  $29               - Close parentheses character       CHR$(41) 
$080D  $00               - End of BASIC line marker.
$080E  $00, $00          - The start of the next BASIC program line.
                           The two zero bytes indicate that it is the
                           the end of the BASIC program. If there was
                           another BASIC program line, these two bytes
                           would have the address of the following 
                           BASIC program line.

C64-Wiki token conversion page

The Assembly

The next free address for programming is $0810, or 2064 in decimal and where the BASIC program points to. This is where we start our machine language code. Things are out of order verses our BASIC program, but the same things are getting done.

        cld
        lda #$06                ;blue
        sta $d020               ;screen and border
        sta $d021

The first line clears decimal mode. It’s a habit I’ve gotten to at the beginning of my programs. Then the accumulator is loaded with the number 6 and stored in the VIC-II register for border and screen color. The equivalent BASIC command is on line 215 of the program.

215 poke53280,6:poke53281,6

Then the next few lines are jmps to subroutines to do much of the work of the program. This is different from the BASIC program which has more of a top down style. I could have done the same thing with this assembly program but felt this was a little more organized.

        jsr CopyChars:          ;copy the Character Set to Ram
        jsr CustomChars:        ;program the maze characters
start:  jsr MakeGrid:           ;draw the graph on the screen
        jsr MakeMaze:           ;Make the maze

You can see how the routines are broken down with the jsr commands. The jsr MakeGrid: command has a start: label attached to it so that there is a location to go to to make a new maze after the previous one was finished. The next few lines after the jsr commands set a time delay to pause the program so the user can view the maze.

        clc                     ;clear carry for addition
        lda $a1                 ;load jiffy clock 4.2 seconds incr.
        adc #$02                ;add 2 (approximately 8.4 seconds delay)
clock:  cmp $a1                 ;compare with current $a1 value
        bne clock:              ;haven't reached the 8 seconds yet - keep looping        

        jmp start:

The clc command clears the carry bit for some addition we’re about to do. The accumulator is loaded with the value in $a1. $a1 is one of three bytes that make up the jiffy clock in the system. As described in Mapping the Commodore 64, $a1 increases approximately every 4.2267 seconds. Once in the accumulator, 2 is added to it (adc #$02). The next line (cmp $a1) compares the accumulator to $a1. If they don’t match (bne clock:), the program loops back to the cmp statement. Once they match, the program continues and jumps back to start (jmp start:) to make a new maze. It creates an approximate 8 to 10 second delay to look at the maze once it is complete.

CopyChars:

The next bit of code tackles copying the character set from ROM to RAM. The first thing to do is stop the keyboard and make the character set data visible to the 6510 chip. This is what it looks like in BASIC.

150 poke56334,peek(56334)and254
160 poke1,peek(1)and251

Here is my assembly equivalent.

CopyChars:
                                ;turn off keyboard
        lda ciacra              ;get value from register
        and #%11111110          ;remove bit 0
        sta ciacra              ;store new value in register

                                ;set 6510 to see character ROM
        lda r6510               ;get 6510 value
        and #%11111011          ;remove bit 2
        sta r6510               ;store value in register
        

ciacra was defined at the top of the program as $dc0e which is equivalent to 56334 in decimal. We load the current value from that location using the lda ciacra statement into the accumulator. This is equivalent to the peek statement embedded in the poke statement in line 150 of the BASIC program. Then, an and #%11111110 statement removes the zero bit from the accumulator. Binary notation was used to show the single bit to be removed. This is the and254 from line 150 of the BASIC program. The result is then stored back into the same register with the sta ciacra. The same exact thing was done with the 6510 (line 160 in BASIC) register except the 3rd bit was removed instead of the 0 bit.

Next is the actual copying of data. In BASIC, it’s a single line.

170 fori=0to2023:pokei+nc,peek(i+cr):nexti

In assembly, it’s a little more complicated.

                                ;now ready to copy character data from ROM to RAM
        lda #<cROM              ;lo byte of cROM
        sta $fb                 ;store on zero page for indirect indexing
        lda #>cROM              ;hi byte of cROM
        sta $fc                 ;store on zero page for indirect indexing

        lda #<cRAM              ;lo byte of cRAM
        sta $fd                 ;store on zero page for indirect indexing
        lda #>cRAM              ;hi byte of cRAM
        sta $fe                 ;store on zero page for indirect indexing
        
        ldy #$00
        ldx #$08
loop1:
        lda ($fb),y             ;indirect index from character ROM
        sta ($fd),y             ;store in indirect index of RAM
        iny                     ;increment our index
        bne loop1:              ;have we looped around to 0 again?
                                ;no, keep looping to loop1:

        inc $fc                 ;increment hi byte for ROM Reference
        inc $fe                 ;increment hi byte for RAM Reference
        dex                     ;decrement x
        bne loop1:              ;has x hit zero? No, loop1:     
                                ;yes exit out

We start by grabbing the starting address of the character rom (cROM) and putting into two byte on zero page of the computer.

        lda #<cROM              ;lo byte of cROM
        sta $fb                 ;store on zero page for indirect indexing
        lda #>cROM              ;hi byte of cROM
        sta $fc                 ;store on zero page for indirect 

To do this we load the low byte and hi byte of the starting address into the accumulator. Using lda #<cROM does this. The # in the command indicates that we are assigning an immediate number to the accumulator instead of a value from a memory location. For example, lda #$01 will load the number 1 into the accumulator whereas lda $01 will load the value that is stored in memory location 1 into the accumulator. The less than symbol indicates that we want the low byte of a 16 bit number. In this case, cROM is storing the number $d000, $d0 is the high byte and $00 is the low byte. $00 will be pulled into the accumulator and then stored in location $fb with the sta $fb command.

The same thing is done with the high byte of cROM. This time, the greater than symbol is used to pull the high byte from cROM. It is then stored in $fc.

Next we do the same thing with the ram memory where we are going to copy the character data to. This time, we store the data into memory locations $fd, $fe.

        lda #<cRAM              ;lo byte of cRAM
        sta $fd                 ;store on zero page for indirect indexing
        lda #>cRAM              ;hi byte of cRAM
        sta $fe                 ;store on zero page for indirect 

Next we set up our x as a counter and our y register as an index.

        ldy #$00
        ldx #$08

Y being the index, it will count from $00 to $ff or 255 in decimal. X will loop the entire routine 8 times. This will cause 2K of data to be copied from ROM to RAM (256 * 8 = 2048).

Here’s the loop that makes it all happen.

loop1:
        lda ($fb),y             ;indirect index from character ROM
        sta ($fd),y             ;store in indirect index of RAM
        iny                     ;increment our index
        bne loop1:              ;have we looped around to 0 again?
                                ;no, keep looping to loop1:

For this loop, we are using an indirect indexing version of lda. This only works with zero page addresses. Since we haven’t done anything in this program to turn off BASIC, there are not many zero page addresses available to us. Luckily, $fb through $fe are so we can use them without causing any weirdness in BASIC.

The command lda ($fb),y looks at the values in $fb and $fc to find a 16 bit address. We previously stored $00 in $fb and $d0 in $fc. Like most things in 6502 machine language, the processor works from low byte to high byte giving us an effective address of $d000. It then adds the y index to the address. This loop starts at $00, but counts up to $ff. So if the y register had a $09 in it for example, the command lda ($fb),y would load the accumulator with the value that is currently stored at location $d009.

The command sta ($fd),y works exactly the same way as the command before it except that it is storing the value from the accumulator instead of loading it and it is using a different point of reference, namely the block of RAM we are copying the character set to.

The iny command increments the y register by 1. This is followed by the bne loop1: command. The bne command will branch when the y does not equal zero. Since we started at $00 and incremented by 1, the loop will continue through all 256 iterations before it will pass through to the code below. Once the y register loops all the way around to $00, it will pass through the loop and adjust some memory before looping again.

        inc $fc                 ;increment hi byte for ROM Reference
        inc $fe                 ;increment hi byte for RAM Reference
        dex                     ;decrement x
        bne loop1:              ;has x hit zero? No, loop1:     
                                ;yes exit out

With 256 bytes copied, we need to adjust the indirect index bytes so the computer can copy the next 256 bytes of memory. This is done using the inc $fc and inc $fe commands. This tells the computer to increment those two memory locations by 1. Those memory locations represent the high byte of the addresses the indirect index command is looking at. For example, the character ROM starts at $d000. In the loop above, the computer copied $d000 to $d0ff. By incrementing the high byte of the indirect index base, the next byte to be copied will be $d100.

Next we reduce the x register by 1 with the dex command and check to see if it has reached zero with the bne loop1: command. If not, it loops back to loop1: and copies the next 256 bytes. Once all 8 iterations of x are completed and 2048 bytes have been copied from ROM to RAM, the program restores the two registers we set earlier and change where the VIC-II chip looks for character data. To restore the registers, we grab the byte out of the register and instead of using and to remove one bit, we use ora to insert a bit. We make sure just the bit we want added is set to one, leaving all the other bits alone. This is the command in BASIC,

180 poke1,peek(1)or4
190 poke56334,peek(56334)or1

And the code in assembly.

                                ;restore registers
        
                                ;set 6510 to see I/O
        lda r6510               ;get 6510 value
        ora #%00000100          ;add bit 2
        sta r6510               ;store value in register

                                ;turn keyboard back on

        lda ciacra              ;get value in register
        ora #%00000001          ;add bit 0
        sta ciacra              ;store new value in register

Notice that we restore the registers in the opposite order we set them. The final step is changing where the VIC-II looks for character data. In BASIC, it’s a poke to 53272, as listed below.

148 poke53272,29

I used a hard number (29) to do this. What I really should have done is just adjust bits 0-3. The real BASIC command should look like this:

poke53272,(peek(53272)and240)+12

Normally the byte in 53272 is 21. If you use the formula above, (21and240)+12, the result is 28. So why did I poke 29 into 53272? The reason is the 0 bit does not do anything in 53272 and the computer keeps it high. Poking 28 into 53272 has the same effect as poking 29 into it. As a matter of fact, if you poke 28 into 53272 and then do a peek command on it, the result will be 29.

Want to check it out? Do a peek of 53272 on your c64 right now. If you are in caps/graphics mode, you should get 21 as the result. If upper/lower case mode, 23. Whichever you, subtract 1 from the result and poke that number into 53272. Then do a peek of the location and the result will still be an odd number. (on a side note, if you want to change the character set from graphics to upper/lower case, just poke53272,22 [or 23], and it will switch it for you, in case you don’t want to use the PRINT CHR$ method.)

Anyway, the assembly method for changing 53272. Again, we load the accumulator with the value from the memory location. After that, we start with an and #%11110000 to preserve bits 4-7 and remove bits 0-3. This is the equivalent to the and240 of the BASIC command listed above, and then did an ora #%00001100 which is the equivalent to the +12 in the BASIC statement. We then store that value back into the register and the computer is now using RAM for its character data. An rts command gets us back to our main program at the beginning of the code to do the next bit.

                                ;set character pointer to RAM
        lda vncsb               ;get Vic-II Register info
        and #%11110000          ;keep the values of bits 4-7
        ora #%00001100          ;add bits 2 and 3 to change where Vic-II sees
                                ;character matrix data.
        sta vncsb               ;store new value
        rts     

The Next Bit, or CustomChars:

In the BASIC program we read data statements and poke them into the memory locations of the characters we want to change. We did this for both the regular characters and reverse character. In our BASIC example, nc is the start of where the characters are stored in RAM, i is a count from the start we want to poke our new data into and a is the data to be poked into the memory location. We also poke the inverse of character data 1024 bytes forward to make the reverse characters.

200 fori=640to767:reada:pokei+nc,a:pokei+nc+1024,255-a:nexti
10000 data0,0,0,0,0,0,0,0
10010 data1,1,1,1,1,1,1,1
10020 data0,0,0,0,0,0,0,255
10030 data1,1,1,1,1,1,1,255
10040 data128,128,128,128,128,128,128,128
10050 data129,129,129,129,129,129,129,129
10060 data128,128,128,128,128,128,128,255
10070 data129,129,129,129,129,129,129,255
10080 data255,0,0,0,0,0,0,0
10090 data255,1,1,1,1,1,1,1
10100 data255,0,0,0,0,0,0,255
10110 data255,1,1,1,1,1,1,255
10120 data255,128,128,128,128,128,128,128
10130 data255,129,129,129,129,129,129,129
10140 data255,128,128,128,128,128,128,255
10150 data255,129,129,129,129,129,129,255

In assembly we are doing basically the same thing. Our data is simply a series of bytes (in hex) with a label (custom:) at the start of it. cchar is a variable we set up at the beginning of the program to identify the address ($3280) that starts our custom characters. The x register is loaded with $80 because we have 128 ($80) bytes to transfer (16 characters * 8 bytes per character) and x will be our index and counter.

Then we use the lda custom:,x to load the byte into the accumulator and sta cchar,x to store that byte into the new address. A dex command reduces x by one and the bne loop2: sees if x has reach zero, and if not, goes back up to loop2: to load and save the next byte of data. The way this loop is written, the data copy starts at the end of the data, and works its way backwards toward the beginning.

CustomChars: 
                                ;Now program custom characters
        ldx #$80                ;set x to 80 for index (last byte)
loop2:            
        lda custom:,x           ;get custom byte
        sta cchar,x             ;store into RAM character data
        dex                     ;reduce x by 1
        bne loop2:              ;not zero, keep looping
        lda custom:             ;get 1st custom byte
        sta cchar               ;store into RAM character data
        rts

Once x reaches zero, it passes through to a final lda and sta command that copies the very first byte of data. I did this because the zero byte of data from the index doesn’t get written by the loop so it had to be done afterwords. There are ways I could have written it into the loop, but this was the easiest to get the job done. An rts brings us back up to the main program.

;custom Character data 
custom:
        byte    $00,$00,$00,$00,$00,$00,$00,$00
        byte    $01,$01,$01,$01,$01,$01,$01,$01
        byte    $00,$00,$00,$00,$00,$00,$00,$ff
        byte    $01,$01,$01,$01,$01,$01,$01,$ff
        byte    $80,$80,$80,$80,$80,$80,$80,$80
        byte    $81,$81,$81,$81,$81,$81,$81,$81
        byte    $80,$80,$80,$80,$80,$80,$80,$ff
        byte    $81,$81,$81,$81,$81,$81,$81,$ff
        byte    $ff,$00,$00,$00,$00,$00,$00,$00
        byte    $ff,$01,$01,$01,$01,$01,$01,$01
        byte    $ff,$00,$00,$00,$00,$00,$00,$ff
        byte    $ff,$01,$01,$01,$01,$01,$01,$ff
        byte    $ff,$80,$80,$80,$80,$80,$80,$80
        byte    $ff,$81,$81,$81,$81,$81,$81,$81
        byte    $ff,$80,$80,$80,$80,$80,$80,$ff
        byte    $ff,$81,$81,$81,$81,$81,$81,$ff

That finishes things up for part 1. In part 2, we’ll look at drawing the grid on the screen and creating the maze, my solution to random numbers that can probably use some improvement, and some of the choices I made that were different from the BASIC program.