How I Started in Commodore 64 Assembly

Mar 17th, 2023

 

I am teaching a course in assembly language and computer organization this semester, and everytime I do that I whistfully recall my first computer. Namely, I started my journey in computer science on the venerable Commodore 64. My first language was BASIC, as was the case with so many young programmers of my generation. Like so many people back in the 80s, I hit a bit of a roadblock in my coding. See, I had learned how to put sprites on the screen. I had learned to make the SID chip produce sound. I had learned how to write to the VIC registers and make the screen do my bidding. I was poised to write a video game, but no matter how hard I tried I just could not get one to run fast enough to be playable. I knew the machine was capable of running games, I had quite a few of them after all! But whenever I would load these up from disk and list their programs, I would be greated with a mysterious “SYS” line and nothing more.

Not too long after I had hit my roadblock, I obtained a copy of the “Commodore 64 Programmer’s Reference Guide” which contained the answer to my performance conundrum. It introduced me to the concept of machine code, which it promised was faster than BASIC. I learned the basics of 6502 assembly from this guide, but there was one little problem. In order to enter assembly code and have it put into memory, the guide said I would need the “monitor” cartridge which I could get from my Commodore 64 dealer. My Commodore 64 dealer was a department store, and I was at the end of the C64’s lifetime. The clerks at the store had never heard of the monitor cartridge and so I was out of luck.

So there I was, knowing the language I needed to use but having no way to load it into the machine. So I just kept reading, maybe I could find a clue to get the machine around to my side. Finally, it dawned on me. A computer program in machine code was nothing but a long list of numbers! Also, the book had enough information to teach me how to translate the assembly code into these numbers. Through a little trial and error, I managed to translate one of the book’s examples into a string of hexadecimal numbers. (I learned hexadecimal and binary from this same book, BTW.) The tricky bits were the jumps, but the book explained how to compute the relative jumps and had an example of that. The first program I translated was one which had a little loop which printed out the alphabet:

Alphabet Code

Ok, so now I was getting somewhere! I had learned how to hand assemble a program, but now how to get it into the machine? The answer was pretty obvious. I could poke the program byte by byte into memory. Of course, there was just one little snag there. My machine code was in hexadecimal. However, Commodore BASIC only understands numbers in decimal! So I had to do a little more translation. I took my little program and wrote it out in hexadecimal and then converted it all into decimal:

Decimal Translation

From there, it was a simple matter to poke the program into memory and then execute a SYS call. AHA! So that’s what those mysterious SYS calls did. But where was the rest of the commercial programs? It would be a while before I found out. Anyway, I decided that I could write a fairly generic loader in BASIC, and then enter my program using the DATA keyword. I decided that my program format would go Start Address, Length, and then the bytes of the program. Here is what I arrived at for this program:

   10 read a
   20 read n
   30 for i=1 to n
   40 read b
   50 poke a,b
   60 a=a+1
   70 next i
   80 sys a-n
   90 data 5120, 17
  100 data 162, 65, 138, 32, 210, 255
  110 data 232, 224, 91, 208, 247, 169
  120 data 13, 32, 208, 255, 96

(Note that I generated this by taking the file from a d64 disk image and running it through petcat. This is why it is in lower case even though on the machine it would be in upper case.)

I entered the program, and then I ran it. Wouldn’t you know it? I saw the alphabet appear on the screen! I had cracked the code! I could now get down to business exploring my machine’s real capabilities. I was the happiest 10 year old on the block!

Alphabet Running

The thing of it is, this was a very tedious process. I decided the part I hated the most was that translation into decimal. It felt, well, not computery. I wanted to stay in hexadecimal, and I noticed that the monitor cartridge seemed to have that capability. It also had a built in assembler, but ultimately I figured out that what the monitor really did was insert and read memory for you. So I set out to work out a way to do just that. So how could you convert hexadecimal into integers? Well I knew the process from doing it by hand. A little time later I worked out a method to convert a string of hexadecimal digits into a binary number suitable for printing or, more importantly, poking. I wrote this little program:

   10 input h$
   20 h=0
   30 for i=1 to len(h$)
   40 d=asc(mid$(h$,i,1))-48
   50 if d>9 then d=d-7
   60 h=h*16+d
   70 next i
   80 print h

The key observation here came by thinking about the input as a string. The programmer’s reference had a wonderful chart of ASCII codes, and looking at this I realized that subtracting 48 from a character would convert the digits 0-9 into their numeric values. A-F would be 7 too big hence if you get a number bigger than 9, subtracting 7 would turn it into a hex digit. From there, it was as simple matter of multiplying and adding.

Hex-Decimal Conversion

Thus armed, I decided to create a monitor program that had the ability to read memory, write memory, and call an arbitrary memory address. The result was the following program:

   10 print : print "monitor operation"
   20 print "  1.) read memory"
   30 print "  2.) write memory"
   40 print "  3.) run program"
   45 print "  4.) quit"
   50 input "your choice";c
   60 print 
   70 if c>=1 then  if c<=3 then  goto 90
   75 if c=4 then  end
   80 print "invalid choice. please try again.": goto 10
   90 input "address";h$
  100 gosub 2000
  110 a=h
  120 if c=3 then  sys a: goto 10
  130 if c=2 then  goto 300
  140 input "how many bytes (in hex)";h$: gosub 2000
  150 n=h
  160 i=a:e=a+n-1: gosub 250
  170 h=peek(i)
  180 gosub 2500: print " ";
  190 i=i+1
  200 if i>e then  print : goto 10
  210 if i/8 - int(i/8) = 0 then print : gosub 250
  220 goto 170
  250 h=i
  260 gosub 2500
  270 print ": ";
  280 return 
  300 print "enter bytes. '.' to stop."
  310 input h$
  320 if h$="." then  goto 10
  330 gosub 2000
  340 poke a,h
  350 a=a+1
  360 goto 310
 2000 rem convert hex string h$ to integer h
 2010 h=0
 2020 for i=1 to len(h$)
 2030 d=asc(mid$(h$,i,1))-48
 2040 if d>9 then d=d-7
 2050 h=h*16+d
 2060 next i
 2070 return 
 2500 rem print the integer h in hex
 2510 q=16
 2520 if h>255 then q=4096
 2530 d=int(h/q)
 2540 h=h-d*q
 2550 q=q/16
 2560 if d>9 then d=d+7
 2570 print chr$(d+48);
 2580 if q>=1 then  goto 2530
 2590 return 

Now I could enter my hand assembled program in hexadecimal:

My C64 Monitor

Better still, I could explore memory listing the KERNAL or even working out how BASIC programs are stored in memory. With this little program I could really get down to exploring my magical machine.

I wrote quite a few little games and programs in machine code. I would write them by hand, and then I would enter them into the machine with my monitor. I had a few routines that I would use to write the machine code to disk, but I have lost those programs. They wouldn’t read off of my 30+ year old floppy. I don’t have most of the source code I wrote back then because it was written on legal pads and spiral bound notebooks. What I do have is many fond memorys of hacking code using this program, the first truly useful program I ever wrote.

For quite a while, this was my concept of how programs were written. You used BASIC if you cared more about ease of writing and could sacrifice performance. When you needed speed, you wrote in machine code. This was the state of affairs through my later calculator and PC hacking days. In fact, I think I was in college before I reluctantly acknowledged that performance was possible in C. All of that is a story for another day though!

I have made a little disk image with my examples which is linked below. If you want you can play with my code. Feel free to explore your own C64 whether it be real or emulated. I really miss this period in computing; after all, could you bootstrap your way to machine level control on modern hardware? I think not! And that is why the old days were more fun.

Disk Image: monitor.d64

Computers   |   Home   |   Humor   |   Links

Visit me on Mastodon.