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:
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
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
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!
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
10 input h$
30 for i=1 to len(h$)
50 if d>9 then d=d-7
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.
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
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
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
160 i=a:e=a+n-1: gosub 250
180 gosub 2500: print " ";
200 if i>e then print : goto 10
210 if i/8 - int(i/8) = 0 then print : gosub 250
220 goto 170
260 gosub 2500
270 print ": ";
300 print "enter bytes. '.' to stop."
310 input h$
320 if h$="." then goto 10
330 gosub 2000
340 poke a,h
360 goto 310
2000 rem convert hex string h$ to integer h
2020 for i=1 to len(h$)
2040 if d>9 then d=d-7
2060 next i
2500 rem print the integer h in hex
2520 if h>255 then q=4096
2560 if d>9 then d=d+7
2570 print chr$(d+48);
2580 if q>=1 then goto 2530
Now I could enter my hand assembled program in hexadecimal:
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