Programming in Assembly with VICMON on the VIC-20

VICMON is a machine language monitor released by Commodore in 1982 and is great for programming the VIC-20. Its interactive nature means that it can often be quicker to develop via this rather than using a fully fledged assembler because it's easy to enter, alter and test code without having to wait for an assembler to load source code, assemble it, write a binary and then load the resulting binary separately. This also makes it good for debugging our programs and understanding other people's better. Jeff Minter wrote in Compute! Gazette Issue 2, 1983 that he programmed all his games in the past with the VICMON cartridge before switching to a Commodore 64 assembler-editor.

This article will show how to write programs in assembly language with VICMON by creating a simple music player and then showing how to move code about to extend it. There is an accompanying video which should make the process easy to understand and show off the power of VICMON for creating assembly language programs on the Vic.

Contents

This is quite a long article and will make more sense if read from start to finish. However, once read it may be useful to go over certain sections. I would also recommend looking at the video as this should make the use of VICMON much clearer.

Designing A Simple Music Player

The simple music player we're going to create isn't an example of a good music player but it is complex enough to demonstrate how to use VICMON.

The code will use the following locations on the Vic.

LocationMnemonicNote
$A2TIMEJiffy clock - the actual RTC updates locations $A0-$A2, however we'll just use the last byte to count time in one-sixtieths of a second
$900CVICCRCSet frequency of sound oscillator 3, which has the highest pitch range. Bit 7 also enables/disables the oscillator.
$900EVICCRESet volume, it can take a value from 0-15 with 0 being off. Bits 7-4 also controls the auxiliary colour for multicolour mode but we'll ignore that here.

Unless the program we want to write is very simple it is best to write it out in assembly language first. It is generally best to keep all the data for the program together in one area. I find it easier, when using VICMON, to keep the data at the start of the code because this makes it much easier to keep track of when moving code around. I recommend breaking code into small subroutines which can be easily tested and where possible use position independent code so that they can be easily moved.

Our simple music player will start by looking like the following in assembly language.

TIME    = $A2             ; Jiffy clock
VICCRC  = $900C           ; Frequency of sound oscillator 3
VICCRE  = $900E           ; Volume

          JMP MAIN

          ;-----------------------------------
          ;            Data
          ;-----------------------------------

          ; Mary had a Little Lamb
          ; stored as note,length,note,length,...
          ; and terminated by a 0
NOTES     .byt $CC,$1E,$C6,$1E,$BF,$1E,$C6,$1E
          .byt $CC,$1E,$CC,$1E,$CC,$3C
          .byt $C6,$1E,$C6,$1E,$C6,$3C
          .byt $CC,$1E,$D5,$1E,$D5,$3C
          .byt $CC,$1E,$C6,$1E,$BF,$1E,$C6,$1E
          .byt $CC,$1E,$CC,$1E,$CC,$1E,$CC,$1E
          .byt $C6,$1E,$C6,$1E,$CC,$1E,$C6,$1E
          .byt $BF,$78,$00


          ;-----------------------------------
          ;            Code
          ;-----------------------------------
MAIN
          ; Set volume to maximum
          LDA #$0F
          STA VICCRE

          LDX #$00        ; Zero index for start of notes

          ; Load note
NXTNOTE   LDA NOTES, X
          BEQ END         ; If a 0, then finish

          ; Play note
          STA VICCRC      ; Turn on Speaker 3 with note in Accumulator
          INX             ; Move index to note length

          ; Delay for length of note
          LDA #$00
          STA TIME        ; Zero Jiffy Clock 1/60 of a second timer
WAIT      LDA TIME
          CMP NOTES, X    ; Check if clock has reached note length
          BNE WAIT

          ; Break between notes
          LDA #$00
          STA VICCRC
          STA TIME
WAIT2     LDA TIME
          BEQ WAIT2       ; Wait for 1/60 of a second

          ; Move to next note/length pair
          INX
          BNE NXTNOTE     ; If not 0, then loop and get next note

          ; Set volume to 0
END       LDA #$00
          STA VICCRE
          BRK

Video

The following video complements this article as it shows each of the steps used in creating the music player using VICMON and demonstrates the machine language monitor in action.

Starting VICMON

Vicmon can be downloaded from the zimmers.net 4k tool ROMS archive for use with emulators. If using an emulator attach the cartridge at $6000 or if using a physical machine plug in the cartridge and switch on the Vic. We can start the machine language monitor from BASIC with the SYS command.

SYS 24576

Virtual Zero Page

VICMON uses locations in zero page and we don't want those corrupted by our program when it runs so we can create a virtual zero page using the E command. On an unexpanded VIC we can use memory up to the screen map at $1E00. In this case we'll create a virtual zero page at location $1500.

.E 1500

Entering the Program

To enter this program use the A and M commands at a location just a little way into the BASIC memory area. As we enter the program we'll create a memory map and note down any addresses which we'll want to refer to later. Where there are references to forward addresses that we don't know yet we can note down the locations that need editing once they are known. I normally use the current address as the temporary address.

The following is how the first few lines of the above program would look if entered it into VICMON. We start with the A command to assemble from location $1100 and stop assembling by pressing RETURN without entering a mnemonic. In the jump we don't know the address of MAIN so specify the current address for the JMP and note it down for altering later.

.A 1100 JMP $1100
.A 1103

The tune we are going to use is 'Mary Had a Little Lamb' and is stored as note,length,note,length,etc and terminated with a 00. We enter the note/length pairs of the tune using the M command to alter memory directly following the JMP. Note down $1103 as the address of NOTES.

.M 1103
.:1103 CC 1E C6 1E BF
.:1108 1E C6 1E CC 1E
.:110D CC 1E CC 3C C6
.:1112 1E C6 1E C6 3C
.:1117 CC 1E D5 1E D5
.:111C 3C CC 1E C6 1E
.:1121 BF 1E C6 1E CC
.:1126 1E CC 1E CC 1E
.:112B CC 1E C6 1E C6
.:1130 1E CC 1E C6 1E
.:1135 BF 78 00

We can now enter the rest of the program using the A command, which starts as follows. Any forward references that are as yet unknown can be noted down and altered at the end. Here we will note down $1138 as the address of MAIN.

.A 1138 LDA #$0F
.A 113A STA $900E
.A 113D LDX #$00
.A 113F LDA $1103,X
.A 1142 BEQ $1142

The end of the code finishes as follows and we leave the A command by pressing RETURN without entering an assembly statement. We'll note $1161 as END and $1166 as the last instruction in the code.

.A 1161 LDA #$00
.A 1163 STA $900E
.A 1166 BRK
.A 1167

Finally we can alter the code in the locations we recorded by using the D command. This will disassemble the location specified and allow us to edit it using the cursors to correct the location.

Instruction LocationInstructionNew Address Operand
$1100JMP MAIN$1138
$1142BEQ END$1161
.D 1100
., 1100 JMP $1100

After editing and pressing RETURN it will look like:

.A 1100 JMP $1138
.A 1103

We'll also do this for location $1142, turning BEQ $1142 into BEQ $1161.

Saving/Loading the Program

Before running the program, save using the S command which takes a name, followed by device (01 for cassette, 08 for diskette), and then start and end address. Note that the end address has to be one past the last byte we want to save.

.S "MARY",01,1100,1167

To load the program we use the L command which takes an optional device number. If the device number isn't specified then it defaults to 01 - the cassette.

.L "MARY"

Running the Program

Our music player starts at $1100 and to execute it we simply use the G command.

.G 1100

Expanding the Program

It would be nice if the music player displayed the name of the piece of music it was playing. Altering this program is a chance to demonstrate some of the other powerful features of VICMON. The complete final program listing can be found at the end of the article.

We'll alter the code to create two subroutines, one to display the name of the piece of music and the other will be created from our current code to play the music. This will leave the start of the MAIN code block looking like this.

VICCRE  = $900E           ; Volume
CCHROUT = $FFD2           ; Output character to current output device

MAIN      JSR PNAME       ; Display the name of the music being played
          JSR PLAY        ; Play the music
          BRK

          ; PNAME subroutine
          ; Print the name of the piece of music
PNAME     LDX #$00
NAMELOOP  LDA NAME, X
          BEQ ENDPNAME
          JSR CCHROUT     ; Output character
          INX
          BNE NAMELOOP
ENDPNAME  RTS


          ; PLAY subroutine
          ; Play the piece of music

          ; Set volume to maximum
PLAY      LDA #$0F
          STA VICCRE

          LDX #$00        ; Zero index for start of notes

The subroutine PLAY would be altered to end with RTS rather than its former BRK.

The string 'MARY HAD A LITTLE LAMB', preceded by a CLR (clear screen), followed by RETURN and terminated with a 0 would be represented by the following 25 bytes.

93 4D 41 52 59
20 48 41 44 20
41 20 4C 49 54
54 4C 45 20 4C
41 4D 42 0D 00

For demonstration purposes it would be good to put the name of the music before its notes in memory and therefore we have to move the memory from the notes to the end of the code to create space. We know from above that this area occupies $1103-$1166. We'll use the T, transfer command, to move this area by 25 bytes to make room for the name string so that NOTES is now at $111C.

.T 1103 1166 111C

We can now enter our NAME string at location $1103.

.M 1103
.:1103 93 4D 41 52 59
.:1108 20 48 41 44 20
.:110D 41 20 4C 49 54
.:1112 54 4C 45 20 4C
.:1117 41 4D 42 0D 00

If we wanted to we could see our string in memory as reverse ASCII using the I, inspect command.

.I 1103 111B

We could also hunt for a string such as 'MARY' with the H command which would return $1104.

.H 1100 1200 'MARY

We'll need to find the new location of MAIN. An easy way to do this would be by finding the end of our NOTES as we know that MAIN comes straight after that. We can do this using the H to find the last three bytes of the NOTES. The command below will search from $111C to $1200 for the bytes BF 78 00.

.H 111C 1200 BF 78 00

This H command returns $114E so we can add 3 to that and get $1151 as the new address of MAIN which we can confirm with the D command.

.D 1151
., 1151 LDA #$0F
., 1153 STA $900E

We now need to make room for the JSR and BRK commands as well as the PNAME subroutine. We can quickly workout that we need 21 bytes for this and hence use the T command again to move the code a further 21 bytes. However, we need to find the end of the code, the final BRK. We could run through disassembling the code from $1151 until we found it but a quick way would be to use the H command again to find the last three bytes of the code representing $900E followed by BRK which is 0E 90 00 in machine code.

.H 1151 1200 0E 90 00

This returns $117D and hence by adding 2 we know that the BRK is at location $117F. We now have the information needed to move our code.

.T 1151 117F 1166

Next, we'll turn our music playing code into a subroutine by changing the final BRK to an RTS. We'll use the H command again to find it in its new position.

.H 1166 1200 0E 90 00

This returns $1192 so we can then use the D command to edit location $1194 and turn it into an RTS.

Next we want to repoint the PLAY subroutine to the correct location for NOTES. To do this we use the N command which will renumber absolute addresses in the code. Our subroutine runs from location $1166 to $1194 and we know that NOTES is now 25 ($19) bytes higher in memory. We only want to alter absolute addresses that fall in the range $1100 to $1200. I have included the disassembly before and after to illustrate what this does to the absolute addresses, but we wouldn't normally need this.

.D 116D
., 116D LDA $1103,X

.N 1166 1194 0019 1100 1200

.D 116D
., 116D LDA $111C,X

We can now enter our new code for the JSR and BRK commands along with the PNAME subroutine.

.A 1151 JSR $1151
.A 1154 JSR $1166
.A 1157 BRK
.A 1158 LDX #$00
.A 115A LDA $1103,X
.A 115D BEQ $115D
.A 115F JSR $FFD2
.A 1162 INX
.A 1163 BNE $115A
.A 1165 RTS
.A 1166

Finally we can use the D command to alter the code at the following locations:

Instruction LocationInstructionNew Address Operand
$1100JMP MAIN$1151
$1151JSR PNAME$1158
$115DBEQ ENDPNAME$1165

Before running this code it would be worth saving it once again in case we have made any mistakes.

.S "MARY2",01,1100,1195

We can now run our extended music player.

.G 1100

Stepping Through the Code

VICMON has a number of commands to step through the code. In order to do this we could start by using the W command to walk through the code one instruction at a time by pressing RETURN for each instruction to be run. If we come to a JSR instruction we may not want to go through each instruction of the subroutine it is going to call and instead use the J command to call it and then return at the instruction it returns to.

We can begin by walking through the code from the start using the W command. This will execute the initial JMP and look like the following if we keep pressing RETURN.

.W 1100
 1151 JSR 1158
 1158 LDX #00
 115A LDA 1103,X
 115D BEQ 1165
 115F JSR FFD2

At this point we could press RETURN to continue stepping through the CCHROUT subroutine at $FFD2 however this isn't our code and probably doesn't need testing so instead we can press J to jump to the subroutine, run it, and return without having to step through each of its instructions. In our code this would output the first character in the string which is the CLR character and hence it would clear the screen and we could continue walking through as follows.

 1162 INX
 1163 BNE 115A

We can return to the VICMON prompt by pressing STOP. If we wanted to inspect the registers we could use the R command. We can even change the registers by altering the line with their contents and pressing RETURN.

.R
   PC  SR AC XR YR SP
.;1163 20 93 01 00 F3

If we wanted to continue execution of the rest of the program we would just use the G command and execution would continue until the BRK was executed.

Breakpoints

We may decide that we want to continue execution of the rest of the PNAME subroutine without walking through each instruction. We could therefore set a breakpoint at $1154 which is the JSR PLAY instruction to call our PLAY subroutine. Then to continue execution we use G.

.B 1154
.G

This would display our tune name and then break at location $1154 without calling the PLAY subroutine. VICMON would display the registers and wait at its prompt. We could then decide how to proceed either by using W to walk through the subroutine, J to execute it and return or G to continue execution until a BRK is executed.

If we want to remove the breakpoint we can use the RB command. One nice feature of the B command is that we can follow the address with another argument to say how many times it will be reached before breaking out of the code, e.g. we could say B 1163,0005 and execution would stop the 5th time location $1163 is reached.

Using breakpoints and walking through code is a great way to test subroutines before running a whole program.

Quick Trace

If we want to run our program and be able to interrupt it at any point we can use the Q command to run the program at a slower pace and if we press STOP+X it will interrupt it and display the contents of the registers. The Q command will also check for a breakpoint and if reached will switch to walk mode. If we are using a virtual zero page it will run very slowly however and therefore we probably only want to use this when running very specific pieces of code.

Fill

The final command I want to mention is the F command which can be used to fill an area of memory with a value. e.g. to fill the area of memory from $1000 to $1200 with 00 we would use:

.F 1000 1200 00

Running the Program from BASIC

We can exit to BASIC using the X command but if we want to be able to run our program from BASIC we need to turn the BRK into a RTS as the BASIC SYS command will expect this in order to return to BASIC properly.

.A 1157 RTS
.A 1158
.X

We can run our program, at location $1100 (4352), from BASIC using the SYS command.

SYS 4352

VICMON Commands Table

Below are all the commands that can be used within VICMON. Operands can be separated with either a ' ' or ',' unless a ',' is specified. For more information have a look at the VICMON User Manual.

CommandParametersExplanation
Aaddress opcode operandAssemble code starting from address
Baddress

address,n
Set a breakpoint so that execution will stop if instruction at address is about to be executed. If n is specified then will only stop after execution has reached this point n times.
Daddress [address]Disassemble machine code at first address or between the two addresses to assembly language
EaddressEnable a virtual zero page at address so that VICMON doesn't corrupt zero page with its variables
Faddress address valueFill the memory between the two addresses (inclusive) with value
G[address]Execute a program at the current PC (Program Counter) or address if specified.
Haddress address dataSearch for data in memory range specified. data can be a number of bytes to search for or a string if we proceed it with a '
Iaddress [address]Interpret and display printable Commodore ASCII codes at address or in memory range supplied
JJump to a subroutine and return without single stepping while running under the 'W' command
L"filename",deviceLoad file called filename from device into memory at the location it was originally saved from.
Maddress [address]Display memory at first address or between the two addresses and allow us to alter it
Naddress address offet lowLimit highLimit

address address lowLimit highLimit W
Reassign absolute addresses used by instructions in memory range that occur between lowLimit and highLimit so that they have offset added to them. If W is used then the range is a word table.
Q[address]Run program at a slower pace. Can be interrupted with STOP+X
RDisplay registers and allow them to be edited
RBRemove breakpoint
S"filename",device,address,addressSave memory between the two addresses to device. The last address must be one bigger than the address at the last byte that we want to save.
Taddress address destAddressTransfer memory in range of first two addresses to destAddress
Waddress [address]Walk through program executing one instruction at a time
XExit to BASIC

The Final Program Listing

The final program would look like the following if it were written in Assembly language.

RTN_CH  = $0D             ; RETURN character
CLR_CH  = $93             ; CLR - Clear screen character
TIME    = $A2             ; Jiffy clock
VICCRC  = $900C           ; Frequency of sound oscillator 3
VICCRE  = $900E           ; Volume
CCHROUT = $FFD2           ; Output character to current output device

          JMP MAIN

          ;-----------------------------------
          ;            Data
          ;-----------------------------------

          ; Name of the piece of music
NAME      .byt CHR_CH
          .asc "MARY HAD A LITTLE LAMB
          .byt RTN_CH, 00

          ; Mary had a Little Lamb
          ; stored as note,length,note,length,...
          ; and terminated by a 0
NOTES     .byt $CC,$1E,$C6,$1E,$BF,$1E,$C6,$1E
          .byt $CC,$1E,$CC,$1E,$CC,$3C
          .byt $C6,$1E,$C6,$1E,$C6,$3C
          .byt $CC,$1E,$D5,$1E,$D5,$3C
          .byt $CC,$1E,$C6,$1E,$BF,$1E,$C6,$1E
          .byt $CC,$1E,$CC,$1E,$CC,$1E,$CC,$1E
          .byt $C6,$1E,$C6,$1E,$CC,$1E,$C6,$1E
          .byt $BF,$78,$00


          ;-----------------------------------
          ;            Code
          ;-----------------------------------

MAIN      JSR PNAME       ; Display the name of the music being played
          JSR PLAY        ; Play the music
          RTS             ; Use BRK if running from VICMON

          ; PNAME subroutine
          ; Print the name of the piece of music
PNAME     LDX #$00
NAMELOOP  LDA NAME, X
          BEQ ENDPNAME
          JSR CCHROUT     ; Output character
          INX
          BNE NAMELOOP
ENDPNAME  RTS


          ; PLAY subroutine
          ; Play the piece of music

          ; Set volume to maximum
PLAY      LDA #$0F
          STA VICCRE

          LDX #$00        ; Zero index for start of notes

          ; Load note
NXTNOTE   LDA NOTES, X
          BEQ END         ; If a 0, then finish

          ; Play note
          STA VICCRC      ; Turn on Speaker 3 with note in Accumulator
          INX             ; Move index to note length

          ; Delay for length of note
          LDA #$00
          STA TIME        ; Zero Jiffy Clock 1/60 of a second timer
WAIT      LDA TIME
          CMP NOTES, X    ; Check if clock has reached note length
          BNE WAIT

          ; Break between notes
          LDA #$00
          STA VICCRC
          STA TIME
WAIT2     LDA TIME
          BEQ WAIT2       ; Wait for 1/60 of a second

          ; Move to next note/length pair
          INX
          BNE NXTNOTE     ; If not 0, then loop and get next note

          ; Set volume to 0
END       LDA #$00
          STA VICCRE
          RTS
Creative Commons License
Programming in Assembly with VICMON on the VIC-20 by Lawrence Woodman is licensed under a Creative Commons Attribution 4.0 International License.

Share This Post

Feedback/Discuss

Related Articles

Getting the Address of BASIC Variables on the VIC-20

Getting the address of a BASIC variable can be useful if you want to pass data to a machine code routine or want to access the bytes of a variable directly to improve speed and reduce garbage collectio...   Read More

Saving and Loading Memory on the VIC-20

Saving and loading memory is quite easy on the VIC-20 once you know how. However, it isn't obvious how to do this and therefore this article will present a few simple ways of doing it from BASIC and A...   Read More

Storing Machine Code in REM Statements on the VIC-20

BASIC programs often contain machine code routines but they take up quite a lot of space in BASIC. An interesting way to reduce the amount of space that they take is to store the machine code in REM s...   Read More

Code and Data in Display Memory on the VIC-20

The unexpanded Commodore VIC-20 only had 5K of RAM and therefore creative ways had to be found to maximize the available RAM. The display memory would use some of this memory and therefore one option ...   Read More

Double-Width C64 Style Font on the Commodore VIC-20

The C64 used a fatter double-width font because the video quality of early VIC-IIs wasn't very good. Therefore they had to make the C64's font fatter than the VIC-20's. However, this fatter font look...   Read More