I selected the VICE, VIrtual Commodore Emulator, which runs on literally every platform. I am currently running this emulator on Mac OS X on my Mac Mini M1 CPU platform.
Installation and Software
I am not going to cover installation in detail here. The installation is straightforward, and the software has a lot of good sites and videos available on this. However, I am going to include the URLs of the installation software.
VICE Software
Choose the installations based on the platform you are running on.
VICE - the Versatile Commodore Emulator
Supermon+64
Download and install the Supermon+64 Machine Language Monitor.
Supermon+64 Machine Language Monitor
Note: I stored the supermon+64 image, sprmon64.64 in the Documents directory of my Mac Mini. This folder will be my virtual floppy drive mount for my C64 emulator.
Documents->Commodore C64 Disk Drive->sprmon64.d64
Running the Supermon+64 Machine Language Monitor
On my Mac Mini I am executing the Applications->vice-arm64-gtk3-3.6.1->x64sc emulator software.
If you made it this far, and see the C64 purple startup screen shown above. Let's fire up the machine code monitor!
Under the VICE File menu, there is an option to Smart-attach a file. I setup Smart-attach a file to look into my Documents->Commodore 64 Disk Drive Folder. There I Smart-attach the file, sprmon64.d64.
Select the sprmon64.d64 file to execute.Use the Autostart button on the Smart-attach a file dialog box to load and auto-execute the Supermon+64 software. Now you should see the Commodore 64 running the machine language monitor.
Playing in the Sand Box
Let's enter some code and do a simple Hello World program. I plagiarized this code from a Youtube video, Hello World in C64 / 6502 Assembly Tutorial Part-1, from someone who claims not to be a Wizard. I love their coding example because it covers many basics in programming, accessing an array (memory), basic code logic, and branching.Setting up the Hello World Array in Memory
Array Char Hex Address
----------------------------------------
Array[0] = "H" 0x08 0x1030
Array[1] = "E" 0x05 0x1031
Array[2] = "L" 0x0C 0x1032
Array[3] = "L" 0x0C 0x1033
Array[4] = "O" 0x0F 0x1034
Array[5] = " " 0x20 0x1035
Array[6] = "W" 0x17 0x1036
Array[7] = "O" 0x0F 0x1037
Array[8] = "R" 0x12 0x1038
Array[9] = "L" 0x0C 0x1039
Array[10] = "D" 0x04 0x103A
Array[11] = "!" 0x21 0x103B
Array[12] = 0 0x00 0x103C null byte end of string
M 1030 1040
>1030 08 05 0C 0C 0F 20 17 0F
>1038 12 0C 04 2E 00 FF 00
Let's use the monitor to examine the existing data in these memory locations.
Type the following command from the machine language monitor to see the contents of memory at this location.
M $1030 $1040 (in Supermon+64 both M $1030 $1040 and M 1030 1040 do the same thing).
If you'll notice there is a bug in my version of supermon+64 where the updated byte fields don't display the correct bytes in memory. We should see H E L L O W O R L D ! in the byte fields. Most likely a bug in this version.
Clearing the Screen
If you're screen becomes to convoluted with coding or memory dumps, the keyboard combination, "SHIFT+HOME KEY" clears your screen.
Implementing the Code
1000 A2 00 LDX #$00
1002 BD 30 10 LDA $1030,X
1005 F0 07 BEQ $100E
1007 9D 00 04 STA $0400, x
100A E8 INX
100B 4C 02 10 JMP $1002
100E 60 RTS
Break it down. What is the code doing? This is a really nice example code for learning. Simple but elegant in learning some basic 6502 machine code functionality. It demonstrates a simple loop operation using a memory pointer. We set a pointer to our array in memory located at $1030. We are reading a string of unknown size so we will terminate the string with a NULL byte 00.
1000 A2 00 LDX #$00 REM Set the Array pointer to index the first value in the array "H"
1002 BD 30 10 LDA $1030,X REM Using 6502 indexed addressing mode, LDA from $1030 + X (our index value offest)
Keep in mind, if you don't know already, the A2 00, and BD 30 10 values are the machine language opcodes. A2 means LDX to the 6502. BD is interpreted as LDA using indexed addressing. The above code retrieves the first byte of data from our arrayPtr = $1030 + 0 (index). We are using the 6502 processor's indexed addressing mode. We load the index offset of the memory we wish to access into the X register of the 6502 using the LDX (LoaDX) command. We load the 6502 accumulator (LDA) register using the indexed addressing mode.
LDA $1030 +X
X register = 0
LDA $1030 + 0 Accumulator register contains the BYTE "H" or we see 0x08
Now we have an indeterminate number of bytes to continue reading from memory. We know to stop reading BYTES when we read a NULL value of 00. If the BYTE value is not 00, then we want to write the BYTE to the screen.
1005 F0 07 BEQ $100E REM if the accumulaor contains 0, we have read a NULL byte and we're at the end of the array
1007 9D 00 04 STA $0400, x REM write the BYTE to screen memory (starting at $0400) + index offset.
We are testing for the NULL BYTE value using the 6502, BEQ, Branch on EQual to zero instruction. If the accumulator holds the value 0x00, then the code flow needs to branch to the instruction at 0x100E, which terminates our code with the RTS, Return from Subroutine, instruction.
If the BYTE value is not 00, then write the BYTE to the screen memory location, 0x0400+index, to display it. The BYTE 0x08, "H", is written to 0x0400. The BYTE 0x05, "E", is written to location 0x0401, and so on for each byte.
The final portion of code increments the index by 1, INX, with each iteration until all the HELLO WORLD! bytes are read. When we call INX, the value in the X register is incremented by 1. This is how we increment our arrayPtr index to the next value in the BYTE array. If we haven't read the NULL BYTE 00 we jump back to the instruction at 0x1002 to read the next BYTE. A very simple and eloquent example of 6502 machine code.
100A E8 INX
100B 4C 02 10 JMP $1002
100E 60 RTS
Here is the listening in entirety if this brings clarity. Again, this is not my code. It is from Not a Wizard channel on YouTube. I just really liked the simplicity and illustration value of this code.
1000 A2 00 LDX #$00 REM Set the Array pointer to index the first value in the array "H"
1002 BD 30 10 LDA $1030,X REM Using 6502 indexed addressing mode, LDA from $1030 + X (our index value offest)
1005 F0 07 BEQ $100E REM if the accumulaor contains 0, we have read a NULL byte and we're at the end of the array
1007 9D 00 04 STA $0400, x REM write the BYTE to screen memory (starting at $0400) + index offset.
100A E8 INX REM we haven't read a NULL byte, so increment the array index (index = index +1)
100B 4C 02 10 JMP $1002 REM JMP to instrunction at $1002 and read another BYTE from the array
100E 60 RTS REM we're done, we read a NULL BYTE. RTS - return from subroutine
Typing the Program into the Monitor
A 1000 LDX #$00
Here is the entire code listing to enter.
A 1000 LDX #$00
A 1002 LDA $1030,X
A 1005 BEQ $100E
A 1007 STA $0400, x
A 100A INX
A 100B JMP $1002
A 100E RTS
Using the Disassembler to Examine our Code
D 1000
Voila!
SYS 4096
Saving and Loading Code
Obviously as the code becomes longer and more complex we will have the need to save and load code from virtual disk into the monitor memory. The commands S and L are used for saving and loading images respectively.
To save our HELLOWORLD code, we will need to save the code and the array data. This spans from memory locations 0x1000 to 0x1040.
S "HELLOWORLD",8,1000,1040
To load HELLOWORLD from the virtual disk, we will need to specify the destination memory address to write the code to.
L "HELLOWORLD",8,1000
That's All Folks!
Hopefully, this shed some light on 6502 programming and using virtual emulators. You don't need a physical Commodore 64 to write code and games. The emulators work just fine. I wouldn't mine having a physical Commodore 64 for my home artifacts. But for coding, I can get by with VICE. Anyone know how to write a cartridge and burn a ROM for it? I need to get this running on my Raspberry Pi 4 too. To be legit!
Mike