Category Archives: archeology

Commodore Peripheral Bus: Overview

The well-known Serial Bus (aka Serial “IEC” Bus) of the Commodore 64 that connects to disk drives such as the 1541 is just one variant of a whole family of busses and protocols used by the line of 8 bit Commodore machines from the PET to the C65. This is the first article of a multi-part series on the Commodore Peripheral Bus family.

The following figure compares the different protocol stacks. There are three different connectors with their unique byte transfer protocols: IEEE-488, Serial, and TCBM. Fast Serial and JiffyDOS are optimized, but backwards-compatible protocols for existing cables, and CBDOS integrates the drive directly into the computer. The higher level protocols are the same across all Commodore 8 bit machines, including the “KERNAL” operating system APIs.

These are the properties and tradeoffs of the different variants of the protocol stack1:

IEEE-488 Serial Fast Serial JiffyDOS TCBM CBDOS
Data Wires 13 3 4 3 12
Speed (KB/sec) 2.1 0.4 2.1 2.1 2.4
Controller Code (bytes) 334 434 708 739 262 0
Comments compatible with industry standard very slow requires decidated bit shifting hardware point-to-point drive integrated into computer

All variants are based on the IEEE-488 standard and therefore share (mostly) the same basic architecture:
* All participants are daisy-chained.
* One dedicated controller (the computer) does bus arbitration of up to 31 devices.
* One-to-many: Any participant can send data to any set of participants.
* A device has multiple channels for different functions.
* Data transmission is byte stream based.

The different variants and layers will be described in multiple articles.

NOTE: I am going to release one part every week from now, at which time links will be added to the bullet points below. The articles will also be announced on my Twitter account @pagetable and my Mastodon account @pagetable@mastodon.social.

  • Part 0: Overview and Introduction
    That’s this part.
  • Part 1: IEEE-488 [PET/CBM Series; 1977]
    This part covers layers 1 (electrical) and 2 (byte transfer) of IEEE-488, an 8-bit parallel bus with three handshake lines, an ATN line for bus arbitration and very relaxed timing requirements.
  • Part 2: The TALK/LISTEN Layer
    This part talks about layer 3 (TALK/LISTEN), which is shared between all bus variants.
  • Part 3: The Commodore DOS Layer
    This part describes layer 4 (Commodore DOS), which is shared between all bus variants.
  • Part 4: Standard Serial (IEC) [VIC-20, C64; 1981]
    The VIC-20 introduced a serial version of layers 1 and 2 with one clock and one data line for serial data transmission, and an ATN line for bus arbitration. It has some strict timing requirements. This bus is supported by all members of the home computer line: VIC-20, C64, Plus/4 Series, C128 and C65.
  • Part 5: TCBM [C16, C116, Plus/4; 1984]
    The Plus/4 Series introduced a 1-to-1 bus between the computer and one drive, with 8 bit parallel data, two handshake lines, and two status lines from the drive to the computer. It was the short-lived planned successor of the Standard Serial bus, but was then replaced by Fast Serial.
  • Part 6: JiffyDOS [1985]
    JiffyDOS, a 3rd party ROM patch for computers and drives, replaces layer 2 byte transmission of Standard Serial by using the clock and data lines in a more efficient way. Bus arbitration is unchanged. The controller detects a device’s JiffyDOS support and can fall back to the Standard Serial protocol.
  • Part 7: Fast Serial [C128; 1986]
    The C128 introduced Fast Serial, which replaces layer 2 byte transmission of Standard Serial by using a previously unused wire in the Serial connector as a third line for data transmission. Bus arbitration is unchanged. The controller detects a device’s Fast Serial support and can fall back to the Standard Serial protocol.
  • Part 8: CBDOS [C65; 1991]
    The unreleased C65 added CBDOS (“computer-based DOS”) by integrating one or more drive controllers into the computer. There are no layers 1 and 2, and layer 3 sits directly on top of function calls that call into the DOS code running on the same CPU.

  1. The speeds have been measured by repeatedly reading the status channel of a disk drive. IEEE-488, Serial and JiffyDOS were measured on a 1 MHz C64 and Fast Serial on a C128, which executes all (Fast) Serial code in 1 MHz mode. TCBM was measured on a 1.77 MHz Plus/4 with the screen on, which makes the effective CPU speed similar to the C64. This is the code: a9 00 20 bd ff a9 01 a2 08 a0 0f 20 ba ff 20 c0 ff a9 08 20 b4 ff a9 6f 20 96 ff a2 00 20 a5 ff 9d 00 04 e8 d0 f7 60. Both Fast Serial and JiffyDOS can reach higher speeds in the special case of loading files using custom protocols. Controller code size was measured on CBM2 for IEEE-488, on C64 for Serial and JiffyDOS, and on C128 for Fast Serial. Code sizes are approximate and do not include the LOAD and SAVE code.

Archiving C64 Tapes Correctly

It’s pretty simple to archive Commodore 64 tapes, but it’s hard if you want to do it right. Creating the complete archive of the German “INPUT 64” magazine was not as easy as getting one copy of each of the 32 tapes and reading them. The tapes are over 30 years old by now, and many of them are hardly readable any more.

Good Tapes, Bad Tapes

Here is the overview of the tapes I have of each issue, how many I could read correctly, and what percentage that is:

Issue # Copies # Read OK % Read OK
8501 8 2 25%
8502 6 1 16.7%
8503 8 1 12.5%
8504 6 0 0%
8505 6 1 16.7%
8506 6 1 16.7%
8507 6 0 0%
8508 6 1 16.7%
8509 6 5 83.3%
8510 6 0 0%
8511 6 3 50%
8512 10 3 33.3%
8601 4 3 75%
8602 4 2 50%
8603 4 3 75%
8604 4 4 100%
8605 6 3 50%
8606 4 2 50%
8607 4 4 100%
8608 4 3 75%
8609 6 4 66.7%
8610 6 5 83.3%
8611 2 1 50%
8612 2 1 50%
8701 2 1 50%
8702 2 2 100%
8703 2 2 100%
8704 2 2 100%
8705 2 1 50%
8706 4 1 25%
8707 6 0 0%
8708 4 2 50%

As you can see, some tapes are pretty much unreadable these days, others are okay. For issues that were problematic, I kept buying more copies on eBay, hoping to eventually find one that reads correctly. (Interestingly, the distribution suggests that correlates more with the brand of tape (8707 seems to be a very bad one) than with age.)

By the way, all numbers of copies are divisible by two, because all INPUT 64 tapes have an identical copy of the data on the reverse side. So for issue 8702, for example, I only had a single tape, and both sides read correctly.

How to Dump Tapes

There are many ways to dump C64 tapes, and I’ll describe two.

If you have a C64 and an actual Datasette reader, you can use the 1541 Ultimate-II+ cartridge. The included dongle allows you to connect the Datasette to the cartridge, so you can directly record a tape onto a .TAP file on a USB storage device also connected to the cartridge.

If you are doing this, make sure that your Datasette has a clean head and is correctly aligned. HeadAlign by enthusi is very useful for this. If you have multiple Datasette recorders, try them all and start out with the best one before changing the alignment.

Another way is to record the tape into a WAV file using a regular tape recorder. Online shops are full of very cheap (but good) Walkman-like devices with a built-in analog-digital-converter that connect directly to USB and show up as an audio-in device. Using a tool like Audacity and one of the many WAV-to-TAP tools (my favorite is the ancient “tape64”, whose Windows binary works nicely with Wine), you can then convert it into a TAP file.

The advantage of this method is that you can still massage dumps of tapes that you have trouble with. In Audacity, you can use filters or split the two channels, and in conversion tools, you can adjust the threshold or the speed.

Interestingly, some tapes read well on a tape player, others read well on a Datasette. If you have trouble getting a correct dump and few copies, you might want to try different methods of reading the tapes.

Checksums

How do I know whether a tape has in fact been read correctly? Practically all recording formats use checksums, so a tool like the excellent tapclean can help:

$ tapclean -t 8702a.tap 

----------------------------------------------------------------------
TAPClean 0.34 - (C) 2006-2017 TC Team [Built Jun 12 2017 by ldf]
Based on Final TAP 2.76 Console - (C) 2001-2006 Subchrist Software
----------------------------------------------------------------------

Read tolerance = 10

Computer type: C64 PAL (985248 Hz)


Loaded: 8702a.tap
Testing...

Scanning...  Pauses  C64 ROM tape

TAPClean version: 0.34

GENERAL INFO AND TEST RESULTS

TAP Name    : 8702a.tap
TAP Size    : 1221451 bytes (1192 kB)
TAP Version : 1
Recognized  : 94%
Data Files  : 28
Pauses      : 186
Gaps        : 204
Magic CRC32 : A9F18B1C
TAP Time    : 8:44.16
Bootable    : YES (1 part, name: INPUT 64)
Loader ID   : n/a

Overall Result    : FAIL

Header test       : PASS [Sig: OK] [Ver: OK] [Siz: OK]
Recognition test  : FAIL [1153201 of 1221431 bytes accounted for] [94%]
Checksum test     : PASS [28 of 28 checksummed files OK]
Read test         : PASS [0 Errors]
Optimization test : FAIL [0 of 28 files OK]

Saved: tcreport.txt
Operation completed in 00:00:09.

The tool recognized 28 pieces of data on the tape, and all of them had correct checksums. That’s a good sign, but not good enough. And that’s not just because single-byte checksums might be too weak to rely on.

Completeness

It is possible that some pieces of data did not get recognized. If a header is unreadable, tapclean will treat it as an unrecognized area and warn about it. In the printout above, the tape failed the “recognition test”, because it only understood 94% of the tape (which includes silence). What are the other 6%?

Tapes often contain some garbage, and many INPUT 64 tapes have a minute of beeps at the end that don’t seem to encode any data. So these 6% may not be a problem.

Another data point on whether all data objects got recognized is by extracting everything and having a look at the listing:

$ tapclean -t 8702a.tap -doprg
$ ls prg/
007 (033C-03FB) [INPUT_64].prg
008 (033C-03FB) [INPUT_64].prg
010 (0318-09FF).prg
011 (0318-09FF).prg
013 (033C-0354) [CTEXTE].prg
017 (3000-3FBA).prg
021 (033C-0354) [RAHMEN_______COM].prg
025 (C440-CFF4).prg
029 (033C-0354) [TITELBILD].prg
033 (0801-1891).prg
037 (033C-0354) [l_O_H_N_S_T_E_U].prg
041 (0801-6BCE).prg
045 (033C-0354) [j_u_l_i_a].prg
049 (0801-4B99).prg
052 (033C-0354) [i_d___w_E_R_K_S].prg
056 (0801-3F16).prg
060 (033C-0354) [6_4_E_R__t_I_P_S].prg
064 (0801-59CE).prg
068 (033C-0354) [i_n_p_u_t___c_a].prg
072 (0801-2E7C).prg
076 (033C-0354) [l_A_B_E_L___t_O].prg
080 (0801-2AEC).prg
084 (033C-0354) [d_R_E_I___M_A_L].prg
088 (0801-5538).prg
091 (033C-0354) [eNGLISCHE_gramMA].prg
095 (0801-5B22).prg
098 (033C-0354) [v_O_R_S_C_H_A_U].prg
102 (0801-2A94).prg

Commercial Commodore 64 tapes usually don’t use the original encoding as supported by the C64’s operating system. More optimized schemes are often 5x-10x more efficient. Nevertheless, the first program on tape has to use the original encoding, so that the tape is bootable.

This tape contains a bootable program named “INPUT 64” at the beginning. The Commodore encoding saves the 192 bytes header (file name, type etc.) twice (007, 008), followed by the data, which is also saved twice (010, 011). On this tape, everything after this is in “SUPERTAPE” format. Every SUPERTAPE file consists of a single header (e.g. 013 for “CTEXTE”) and a single copy of the data (e.g. 017).

The printout above shows that after the boot program (2x header, 2x payload), there are 12 additional files. For each file, there is a header and there is payload. So this looks okay. Here is an example of a missing object:

$ tapclean -t 8707a.tap -doprg
[...]
Checksum test     : PASS [29 of 29 checksummed files OK]
[...]
$ ls prg/
066 (033C-03FB) [INPUT_64].prg
067 (033C-03FB) [INPUT_64].prg
069 (0318-09FF).prg
070 (0318-09FF).prg
072 (033C-0354) [CTEXTE].prg
076 (3000-4093).prg
080 (033C-0354) [RAHMEN_______COM].prg
084 (C440-CFF2).prg
088 (033C-0354) [TITELBILD].prg
092 (0801-1891).prg
096 (033C-0354) [i_n_p_u_t___w_I].prg
100 (0801-4A1D).prg
103 (033C-0354) [eNGLISCHE_gramMA].prg
107 (0801-5E09).prg
111 (033C-0354) [s_P_I_D_E_R].prg
115 (0801-471B).prg
118 (033C-0354) [a_S_S_E_M_B_L_E].prg
128 (033C-0354) [i_d___w_E_R_K_S].prg
132 (0801-3E4D).prg
135 (033C-0354) [i_c_i].prg
139 (0801-2DB0).prg
143 (033C-0354) [6_4_E_R__t_I_P_S].prg
147 (0801-4911).prg
151 (033C-0354) [p_I_N_G___p_O_N].prg
155 (0801-247F).prg
159 (033C-0354) [r___T_S_E_L_E_C].prg
163 (0801-22A5).prg
167 (033C-0354) [v_O_R_S_C_H_A_U].prg
171 (0801-0E89).prg

The checksum test passed, but the payload for the file with header 118 (“a_S_S_E_M_B_L_E”) is missing. In this case, it’s clear, but if two entries in sequence had been missing, it would have been tricky to detect this.

Comparing Multiple Copies

With just a single copy, we cannot really know whether the data is correct. Checksums are not reliable enough, and it’s hard to detect if a file was just not recognized. If we have two copies of the tape and they read the same, we can be very confident that the dumps are correct. tapclean’s “magic CRC32” that it prints after analyzing a tape is a strong checksum of all recognized data concatenated. So if two dumped copies have the same CRC32, we can assume that the dumps were correct.

It is quite unlikely that two dumps have the same file(s) missing, and it’s extremely unlikely that they had the same bit flips. Well, that is, if we assume that the tapes did not have mastering errors (or weaknesses), but also verifying the checksums and visual inspection of the contents should rule this out.

The following bash script shows the CRC32 values (e.g. “3D3C8936”), the number of correctly checksummed files and the number of total files (e.g. 30-31) for each tape:

for i in `find . -name \*.tap`; do
  echo $i
  (tapclean -t $i > $i.log)&
done
for i in `find . -name \*.log`; do
  issue=$(basename $i | cut -c 1-4)
  numfiles=$(cat $i | grep "^Checksum test" | cut -d "[" -f 2 | cut -d " " -f 1-3 | sed -e "s/ of /-/")
  crc32=$(cat $i | grep "^Magic CRC32" | cut -d ":" -f 2)
  echo $issue $crc32 $numfiles $(echo $i | sed -e "s/.log$//")
done

Here’s example output:

8708 00000000 0-0   ./8708a2.tap
8708 16979EE4 32-32 ./8708b2.tap
8708 16979EE4 32-32 ./8708b.tap
8708 3D3C8936 30-31 ./8708a.tap

8708b.tap and 8708b2.tap (the back sides of two different copies) are correct dumps: They have the same CRC32, they have all correct checksums, and the number of recognized items is even (header, data, header, data, …).

More Copies

Using this strategy, I was able to confirm correct dumps of 18 of the 32 tapes. 14 more to go.

The obvious way is to buy more copies on eBay until I have two that produce the same data. But luckily, there was another source: The C64 TOSEC Collection contains dumps of 20 of the tapes. Their time stamp says 1996, which is when then tapes were 10 years old instead of 30, so they should have been in much better shape. By looking at the checkums and the contents, we can get an idea of whether they are likely correct dumps. This is what the script from before says:

8508 8F9A4357 34-34 ./tosec/8508.tap
8511 F75CC32E 34-34 ./tosec/8511.tap
8605 200E286D 30-30 ./tosec/8605.tap
8510 D0534C58 34-34 ./tosec/8510.tap
8509 74B093F1 32-32 ./tosec/8509.tap
8606 DA4AF054 29-29 ./tosec/8606.tap
8502 8BDAF783 40-40 ./tosec/8502.tap
8512 764F2F59 31-31 ./tosec/8512.tap
8705 A103816A 30-30 ./tosec/8705.tap
8704 E84CC753 30-30 ./tosec/8704.tap
8602 B4F9D173 32-32 ./tosec/8602.tap
8612 547A7371 32-32 ./tosec/8612.tap
8506 78BC0D59 35-35 ./tosec/8506.tap
8603 E70B3E72 30-30 ./tosec/8603.tap
8507 22A807D1 32-32 ./tosec/8507.tap
8608 F9BD6BC9 34-34 ./tosec/8608.tap
8505 9C7FC3F2 35-35 ./tosec/8505.tap
8601 3F749A69 30-30 ./tosec/8601.tap
8611 48C4C35F 28-28 ./tosec/8611.tap
8703 C6757D2F 29-30 ./tosec/8703.tap

At least 8703 has an incorrect checksum. Several tapes have an odd number of recognized items, but sometimes, tapclean doesn’t seem to count correctly, so extracting all items (“-doprg”) and counting them makes sure what the right number is.

If we add these 20 copies to our collection and run the script again, we will be able to verify several more correct dumps.

8502 B76D97FE 35-39 ./1/8502a.tap
8502 8BDAF783 40-40 ./1/8502b.tap
8502 00000000 0-0   ./3/8502a.tap
8502 11D3CFC5 10-20 ./3/8502b.tap
8502 A7A10C6E 0-3   ./4/8502a.tap
8502 FC899939 0-0   ./4/8502b.tap
8502 8BDAF783 40-40 ./tosec/8502.tap

In this example, 1/8502b.tap and tosec/8502.tap contain the same data, so this verifies that these dumps are correct. Overall, this allowed me to verify the correctness of dumps of 8502, 8505, 8506, 8508, 8611, 8612 and 8705. Seven down, seven more to go!

Splice Verify

There are many issues where one dump looks okay, but we do not have an identical one to verify it. But it is not strictly necessary to have another identical dump. If we are certain that no files are missing, and that for every file, there is another dump with the identical file, we can be very certain that the dump is correct. I call this “splice verify”.

Let’s look at 8706:

8706 E6E7FCDA 28-28 ./1/8706a.tap
8706 8F259FA2 9-18  ./1/8706b.tap
8706 3BDC28F3 23-28 ./2/8706a.tap
8706 1B471853 8-17  ./2/8706b.tap

1/8706a.tap is a candidate for a correct dump. We can test whether there are identical copies of each item on the tape on other copies by hashing all items on the candidate and looking for them on the other copies. The following script will extract all tapes into subdirectories of “dir”:

rm -rf dir
mkdir dir
for i in `find . -name \*.tap | cut -c 3-`; do
(
  (
    cd $(dirname $i)
    rm -rf $(basename $i)-$(dirname $i).dir
    mkdir $(basename $i)-$(dirname $i).dir
    cd $(basename $i)-$(dirname $i).dir
    tapclean -t ../$(basename $i) -doprg
    mv prg/* .
    rmdir prg
    cd ..
    mv $(basename $i)-$(dirname $i).dir ../dir/
  ) &
)
done

Then the following script will search for extra copies of every item on the candidate tape:

cd dir
for md5 in $(md5 -q 8706a.tap-1.dir/???\ *); do
  echo
  echo $md5
  md5 -r */* | grep $md5
done

And this script makes sure that every correctly read file on the other tapes also exists on the candidate:

for md5 in $(md5 -r */???\ * | grep -v 8706a.tap-1.dir | grep -v BAD.prg$ | cut -d " " -f 1 | sort | uniq); do
  echo
  echo $md5
  md5 -r 8706a.tap-1.dir/* | grep $md5
done

Using this method, it is possible to verify 8503, 8507, 8510, 8701 and 8706. Only two more tapes need to be verified!

Splicing

We could keep buying more copies of 8504 and 8707 on eBay, but they don’t actually appear all that often. So let’s look at how bad our dumps are. Let’s take 8504:

12C4A3EA 30-30 ./8504a-1.tap
7C6D8541 34-35 ./8504b-1.tap
1337D0C8 20-25 ./8504a-2.tap
868DA7B6 38-38 ./8504b-2.tap
B80FD685 29-30 ./8504a-3.tap
8948C0B7 33-34 ./8504b-3.tap
C909C30F 26-27 ./8504a-4.tap
E3F7B698 8-24  ./8504b-4.tap

None of the dumps seems correct. 8504b-2.tap is the best, but the file count as printed by tapclean is wrong, and there is actually an item missing: The payload of “h_i_r_e_s_s_p_e”.

012 (033C-03FB) [INPUT_64].prg
013 (033C-03FB) [INPUT_64].prg
019 (0300-0303).prg
020 (0300-0303).prg
025 (033C-03FB) [GUTEN_TAG].prg
026 (033C-03FB) [GUTEN_TAG].prg
031 (0801-0C96).prg
032 (0801-0C96).prg
039 (033C-0354) [TEXTE].prg
046 (3000-3B76).prg
053 (033C-0354) [RAHMEN_______COM].prg
059 (C440-CF78).prg
066 (033C-0354) [TITELBILD].prg
073 (0A00-0AFF).prg
080 (033C-0354) [h_i_r_e_s_s_p_e].prg
093 (033C-0354) [k_a_l_e_n_d_e_r].prg
100 (0801-38A2).prg
106 (033C-0354) [r_e_v_e_r_s_i].prg
112 (0801-267D).prg
119 (033C-0354) [s_h_o_r_t_____s].prg
125 (0801-2075).prg
131 (033C-0354) [k_o_n_t_a_k_t_e].prg
137 (0801-57D4).prg
144 (033C-0354) [bits___bytes_im].prg
152 (0801-6A70).prg
159 (033C-0354) [einkommensteuert].prg
166 (0801-28CB).prg
172 (033C-0354) [h_i_l_f_s_p_r_o].prg
179 (0801-1BEC).prg
185 (033C-0354) [n_e_w_s].prg
191 (0801-2873).prg
198 (033C-0354) [6_4_E_R_____t_i].prg
205 (0801-4D89).prg
211 (033C-0354) [a_r_t_e_m___s].prg
217 (0801-2E30).prg
223 (033C-0354) [s_u_p_e_r_t_a_p].prg
230 (0801-1A4A).prg
236 (033C-0354) [l_a_s_t__n_o_t].prg
243 (0801-2106).prg

If it weren’t for the missing data, we could splice-verify this dump. Using the scripts from earlier, we can show that all files can be found on other copies, and all correct files from other copies are on this dump – except for the “h_i_r_e_s_s_p_e” payload.

Then why not splice in the correct object from a correct dump? 8504b-1.tap is one of the dumps that contains a correct copy. tapclean’s tcreport.txt file for the tape with the correct data shows us some information about the “h_i_r_e_s_s_p_e” payload:

---------------------------------
Seq. no.: 113
File Type: SUPERTAPE DATA
Location: $3DFA2 -> $3E13B -> $4E25E -> $4E269
LA: $0801  EA: $3000  SZ: 10239
Pilot/Trailer Size: 62/0
Checkbyte Actual/Expected: $7DA9/$7DA9, PASS
Read Errors: 0
Unoptimized Pulses: 12338
CRC32: CBF1C874

It is located between offsets $3DFA2 and $4E269 inside the file. Here’s the part from the hexdump:

$ hexdump 8504a-1.tap
0003df70  21 43 21 21 21 21 21 21  1e 21 21 21 43 fb 00 6c
0003df80  09 00 00 0b 19 00 00 ff  47 00 00 e5 2b 00 00 d9
0003df90  12 00 00 fe 3d 04 32 32  32 21 1e 21 37 32 2f 21
0003dfa0  21 21 21 43 1e 32 21 21  1e 32 32 2f 21 21 21 21
0003dfb0  40 21 32 1e 21 21 32 32  32 21 1e 21 21 43 21 32

Every byte in a TAP file (after the 20 byte header) represents the length of a pulse in units of about 8 microseconds. Zero is an escape code, after which a three byte little endian value follows which specifies pulse lengths above 255. So the zeroes here just before what tapclean identified as the beginning of the payload are areas of silence on the tape. Grouped correctly, these are the 6 very long pulses, which represent a pause of about half a second:

00 6c 09 00
00 0b 19 00
00 ff 47 00
00 e5 2b 00
00 d9 12 00
00 fe 3d 04

Just after this is where we want to cut.

The cutting position of the end can be found similarly, and by looking at the tcreport.txt of the tape with the broken file, we can find out what part to cut and replace with the good version.

At the end, we need to make sure the number of data bytes in the TAP file’s header at offset 16 (32 bit little endian) is correct.

By doing this, we can create correct versions of the two remaining issues, so now we have a verified correct dump of each of the 32 issues!

The Moral of the Story

Read your tapes early. If you have any tapes, read them now! It doesn’t matter if they have been dumped before: These dumps might be incorrect, because they might not have not been verified with a second copy.

Making Of “Murdlok”, the new old adventure game for the C64

Recently, the 1986 adventure game “Murdlok” was published here for the first time. This is author Peter Hempel‘s “making-of” story, in German. (English translation)

Am Anfang war der Brotkasten: Wir schreiben das Jahr 1984, oder war es doch schon 1985? Ich hab es über all die Jahre vergessen. Computer sind noch ein Zauberwort, obwohl sie schon seit Jahren auf dem Markt angeboten werden. Derweilen sind sie so klein, dass diese problemlos auf den Tisch gestellt werden können. Mikroprozessor! Und Farbe soll der auch haben, nicht monochrom wie noch überall üblich. Commodore VC20 stand in der Reklame der Illustrierten Zeitung, der Volkscomputer, wahrlich ein merkwürdiger Name, so wie der Name der Firma die ihn herstellt. C=Commodore, was hat dieser Computer mit der Seefahrt zu tun frage ich mich? Gut, immerhin war mir die Seite ins Auge gefallen.

Das Ding holen wir uns, aber gleich den „Großen“, der C64 mit 64 KB. Den bestellen wir im Versandhandel bei Quelle. So trat mein Kumpel an mich heran. Das war damals noch mit erheblichen Kosten verbunden. Der Computer 799 D-Mark, Floppy 799 D-Mark und noch ein Bildschirm in Farbe dazu. Damals noch ein Portable TV für 599 D-Mark.

Als alles da war ging es los! Ohne Selbststudium war da nichts zu machen, für mich war diese Technologie absolutes Neuland. Ich kannte auch niemanden, der sich hier auskannte, auch mein Kumpel nicht. Es wurden Fachbücher gekauft! BASIC für Anfänger! Was für eine spannende Geschichte. Man tippt etwas ein und es gibt gleich ein Ergebnis, manchmal ein erwartetes und manchmal ein unerwartetes. Das Ding hatte mich gefesselt, Tag und Nacht, wenn es die Arbeit und die Freundin zuließ.

Irgendwann viel mir das Adventure „Zauberschloß“ von Dennis Merbach in die Hände. Diese Art von Spielen war genau mein Ding! Spielen und denken! In mir keimte der Gedanke auch so ein Adventure zu basteln. „Adventures und wie man sie programmiert“ hieß das Buch, das ich zu Rate zog. Ich wollte auf jeden Fall eine schöne Grafik haben und natürlich möglichst viele Räume. Die Geschichte habe ich mir dann ausgedacht und im Laufe der Programmierung auch ziemlich oft geändert und verbessert. Ich hatte mich entschieden, die Grafik mit einem geänderten Zeichensatz zu erzeugen. Also, den Zeichensatzeditor aus der 64’er Zeitung abgetippt. Ja, Sprites brauchte ich auch, also den Sprite-Editor aus der 64’er Zeitung abgetippt. „Maschinensprache für Anfänger“ und fertig war die kleine abgeänderte Laderoutine im Diskettenpuffer. Die Entwicklung des neuen Zeichensatzes war dann eine sehr mühselige Angelegenheit. Zeichen ändern und in die Grafik einbauen. Zeichen ändern und in die Grafik einbauen………….und so weiter. Nicht schön geworden, dann noch mal von vorne. Als das Listing zu groß wurde kam, ich ohne Drucker nicht mehr aus und musste mir einen anschaffen. Irgendwann sind mir dann auch noch die Bytes ausgegangen und der Programmcode musste optimiert werden. Jetzt hatte sich die Anschaffung des Druckers ausgezahlt.

Während ich nach Feierabend und in der Nacht programmierte, saß meine Freundin mit den Zwillingen schwanger auf der Couch. Sie musste viel Verständnis für mein stundenlanges Hacken auf dem Brotkasten aufbringen. Sie hatte es aufgebracht, das Verständnis, und somit konnte das Spiel im Jahr 1986 fertigstellt werden. War dann auch mächtig stolz darauf. Habe meine Freundin dann auch später geheiratet, oder hatte sie mich geheiratet?

Das Projekt hatte mich viel über Computer und Programmierung gelehrt. Das war auch mein hautsächlicher Antrieb das Adventure zu Ende zu bringen. Es hat mir einfach außerordentliche Freude bereitet. Einige Kopien wurden angefertigt und an Freunde verteilt. Mehr hatte ich damals nicht im Sinn.

Mir wird immer wieder die Frage gestellt: „Warum hast du dein Spiel nicht veröffentlicht?“ Ja, im nachherein war es vermutlich dumm, aber ich hatte das damals einfach nicht auf dem Schirm. Es gab zu dieser Zeit eine Vielzahl von Spielen auf dem Markt, und ich hatte nicht das Gefühl, dass die Welt gerade auf meins wartete. War wohl eine Fehleinschätzung!

Sorry, dass ihr alle so lange auf „Murdlok“ warten musstet!

Zu meiner Person: Ich heiße Peter Hempel, aber das wisst ihr ja schon. Ich bin Jahrgang 1957 und wohne in Berlin, Deutschland. Das Programmieren ist nicht mein Beruf. Als ich 1974 meine Lehre zum Elektroniker angetreten hatte waren Homecomputer noch unbekannt. Ich habe viele Jahre als Servicetechniker gearbeitet und Ampelanlagen entstört und programmiert.

Das Spiel ist dann in Vergessenheit geraten!

Derweilen hatte ich schon mit einem Amiga 2000 rumgespielt.

Wir schreiben das Jahr 2017, ich finde zufällig einen C=Commodore C65. Ein altes Gefühl meldet sich in mir. Was für eine schöne Erinnerung an vergangene Tage. Aufbruch in die Computerzeit. Der C65 stellt sofort eine Verbindung zur Vergangenheit her. Die letzten Reste meiner C64 Zeit werden wieder vorgekramt. So kommt das Adventure „Murdlok“ wieder ans Tageslicht. Spiel läuft auch auf dem C65, was für ein schönes Gefühl.

Ich habe dann Michael kennengelernt. Ihm haben wir die Veröffentlichung von „Murdlok“ zu verdanken. Ich hätte nie gedacht, dass mein altes Spiel noch mal so viel Ehre erfährt.

Danke!

Ich wünsche allen viel Spaß mit meinem Spiel und natürlich beim 8-Bit Hobby.

Murdlok: A new old adventure game for the C64

Murdlok is a previously unreleased graphical text-based adventure game for the Commodore 64 written in 1986 by Peter Hempel. A German and an English version exist.

Murdlok – Ein Abenteuer von Peter Hempel

Befreie das Land von dem bösen Murdlok. Nur Nachdenken und kein Leichtsinn führen zum Ziel.


murdlok_de.d64

(Originalversion von 1986)

Murdlok – An Adventure by Peter Hempel

Liberate the land from the evil Murdlok! Reflection, not recklessness will guide you to your goal!


murdlok_en.d64

(English translation by Lisa Brodner and Michael Steil, 2018)

The great thing about a new game is that no walkthroughs exist yet! Feel free to use the comments section of this post to discuss how to solve the game. Extra points for the shortest solution – ours is 236 steps!

Commodore KERNAL History

If you have ever written 6502 code for the Commodore 64, you may remember using “JSR $FFD2” to print a character on the screen. You may have read that the jump table at the end of the KERNAL ROM was designed to allow applications to run on a all Commodore 8 bit computers from the PET to the C128 (and the C65!) – but that is a misconception. This article will show how

  • the first version of the jump table in the PET was designed to only hook up BASIC to the system’s features
  • it wasn’t until the VIC-20 that the jump table was generalized for application development (and the vector table introduced)
  • all later machines add their own calls, but later machines don’t necessary support older calls.

KIM-1 (1976)

The KIM-1 was originally meant as a computer development board for the MOS 6502 CPU. Commodore acquired MOS in 1976 and kept selling the KIM-1. It contained a 2 KB ROM (“TIM”, “Terminal Interface Monitor”), which included functions to read characters from ($1E5A) and write characters to ($1EA0) a serial terminal, as well as code to load from and save to tape and support for the hex keyboard and display.

Commodore asked Microsoft to port their BASIC for 6502 to it, which interfaced with the monitor only through the two character in and out functions. The original source of BASIC shows how Microsoft adapted it to work with the KIM-1 by defining CZGETL and OUTCH to point to the monitor routines:

IFE     REALIO-1,<GETCMD==1
        DISKO==1
        OUTCH=^O17240                   ;1EA0
        ROMLOC==^O20000
        RORSW==0
        CZGETL=^O17132>

(The values are octal, since the assembler Microsoft used did not support hexadecimal.)

The makers of the KIM-1 never intended to change the ROM, so there was no need to have a jump table for these calls. Applications just hardcoded their offsets in ROM.

PET (1977)

The PET was Commodore’s first complete computer, with a keyboard, a display and a built-in tape drive. The system ROM (“KERNAL”) was now 4 KB and included a powerful file I/O system for tape, RS-232 and IEEE-488 (for printers and disk drives) as well as timekeeping logic. Another 2 KB ROM (“EDITOR”) handled screen output and character input. Microsoft BASIC was included in ROM and was marketed – with the name “COMMODORE BASIC” – as the actual operating system, making the KERNAL and the editor merely a device driver package.

Like with the KIM-1, Commodore asked Microsoft to port BASIC to the PET, and provided them with addresses of a jump table in the KERNAL ROM for interfacing with it. These are the symbol definitions in Microsoft’s source:

        CQOPEN=^O177700
        CQCLOS=^O177703
        CQOIN= ^O177706         ;OPEN CHANNEL FOR INPUT
        CQOOUT=^O177711         ;FILL FOR COMMO.
        CQCCHN=^O177714
        CQINCH=^O177717         ;INCHR'S CALL TO GET A CHARACTER
        OUTCH= ^O177722
        CQLOAD=^O177725
        CQSAVE=^O177730
        CQVERF=^O177733
        CQSYS= ^O177736
        ISCNTC=^O177741
        CZGETL=^O177744         ;CALL POINT FOR "GET"
        CQCALL=^O177747         ;CLOSE ALL CHANNELS

(The meaning of the CQ prefix is left as an exercise to the reader.)

In hex and with Commodore’s names, these are the KERNAL calls used by BASIC:

  • $FFC0: OPEN
  • $FFC3: CLOSE
  • $FFC6: CHKIN
  • $FFC9: CHKOUT
  • $FFCC: CLRCHN
  • $FFCF: BASIN
  • $FFD2: BSOUT
  • $FFD5: LOAD
  • $FFD8: SAVE
  • $FFDB: VERIFY
  • $FFDE: SYS
  • $FFE1: STOP
  • $FFE4: GETIN
  • $FFE7: CLALL
  • $FFEA: UDTIM (advance clock; not used by BASIC)

At first sight, this jump table looks very similar to the one known from the C64, but it is indeed very different, and it is not generally compatible.

The following eight KERNAL routines are called from within the implementation of BASIC commands to deal with character I/O and the keyboard:

  • $FFC6: CHKIN – set channel for character input
  • $FFC9: CHKOUT – set channel for character output
  • $FFCC: CLRCHN – restore character I/O to screen/keyboard
  • $FFCF: BASIN – get character
  • $FFD2: BSOUT – write character
  • $FFE1: STOP – test for STOP key
  • $FFE4: GETIN – get character from keyboard
  • $FFE7: CLALL – close all channels

But the remaining six calls are not library calls at all, but full-fledged implementations of BASIC commands:

  • $FFC0: OPEN – open a channel
  • $FFC3: CLOSE – close a channel
  • $FFD5: LOAD – load a file into memory
  • $FFD8: SAVE – save a file from memory
  • $FFDB: VERIFY – compare a file with memory
  • $FFDE: SYS – run machine code

When compiled for the PET, Microsoft BASIC detects the extra commands “OPEN”, “CLOSE” etc., but does not provide an implementation for them. Instead, it calls out to these KERNAL functions when these commands are encountered. So these KERNAL calls have to parse the BASIC arguments, check for errors, and update BASIC’s internal data structures.

These 6 KERNAL calls are actually BASIC command extensions, and they are not useful for any other programs in machine code. After all, the whole jump table was not meant as an abstraction of the machine, but as an interface especially for Microsoft BASIC.

PET BASIC V4 (1980)

Version 4 of the ROM set, which came with significant improvements to BASIC and shipped by default with the 4000 and 8000 series, contained several additions to the KERNAL – all of which were additional BASIC commands.

  • $FF93: CONCAT
  • $FF96: DOPEN
  • $FF99: DCLOSE
  • $FF9C: RECORD
  • $FF9F: HEADER
  • $FFA2: COLLECT
  • $FFA5: BACKUP
  • $FFA8: COPY
  • $FFAB: APPEND
  • $FFAE: DSAVE
  • $FFB1: DLOAD
  • $FFB4: CATALOG/DIRECTORY
  • $FFB7: RENAME
  • $FFBA; SCRATCH
  • $FFBD: DS$ (disk status)

Even though Commodore was doing all development on their fork of BASIC after version 2, command additions were still kept separate and developed as part of the KERNAL. In fact, for all Commodore 8-bit computers from the PET to the C65, BASIC and KERNAL were built separately, and the KERNAL jump table was their interface.

VIC-20 (1981)

The VIC-20 was Commodore’s first low-cost home computer. In order to keep the cost down, the complete ROM had to fit into 16 KB, which meant the BASIC V4 features and the machine language monitor had to be dropped and the editor was merged into the KERNAL. While reorganizing the ROM, the original BASIC command extensions (OPEN, CLOSE, …) were moved into the BASIC ROM (so the KERNAL calls for the BASIC command implementations were no longer needed).

The VIC-20 KERNAL is the first one to have a proper system call interface, which does not only include all calls required so BASIC is hardware-independent, but also additional calls not used by BASIC but intended for applications written in machine code. The VIC-20 Programmer’s Reference Manual documents these, making this the first time that machine code applications could be written for the Commodore 8 bit series in a forward-compatible way.

Old PET Calls

The following PET KERNAL calls are generally useful and therefore still supported on the VIC-20:

  • $FFC6: CHKIN
  • $FFC9: CHKOUT
  • $FFCC: CLRCHN
  • $FFCF: BASIN
  • $FFD2: BSOUT
  • $FFE1: STOP
  • $FFE4: GETIN
  • $FFE7: CLALL
  • $FFEA: UDTIM

Channel I/O

The calls for the BASIC commands OPEN, CLOSE, LOAD and SAVE have been replaced by generic functions that can be called from machine code:

  • $FFC0: OPEN
  • $FFC3: CLOSE
  • $FFD5: LOAD
  • $FFD8: SAVE

(There is no separate call for VERIFY, since the LOAD function can perform this function based on its inputs.)

OPEN, LOAD and SAVE take more arguments (LA, FA, SA, filename) than fit into the 6502 registers, so two more calls take these and store them temporarily.

  • $FFBA: SETLFS – set LA, FA and SA
  • $FFBD: SETNAM – set filename

Two more additions allow reading the status of the last operation and to set the verbosity of messages/errors:

  • $FFB7: READST – return status byte
  • $FF90: SETMSG – set verbosity

BASIC uses all these functions to implement the commands OPEN, CLOSE, LOAD, SAVE and VERIFY. It basically parses the arguments and then calls the KERNAL functions.

IEC

The KERNAL also exposes a complete low-level interface to the serial IEC (IEEE-488) bus used to connect printers and disk drives. None of these calls are used by BASIC though, which talks to these devices on a higher level (OPEN, CHKIN, BASIN etc.).

  • $FFB4: TALK – send TALK command
  • $FFB1: LISTEN – send LISTEN command
  • $FFAE: UNLSN – send UNLISTEN command
  • $FFAB: UNTLK – send UNTALK command
  • $FFA8: IECOUT – send byte to serial bus
  • $FFA5: IECIN – read byte from serial bus
  • $FFA2: SETTMO – set timeout
  • $FF96: TKSA – send TALK secondary address
  • $FF93: SECOND – send LISTEN secondary address

Memory

BASIC needs to know where usable RAM starts and where it ends, which is what the MEMTOP and MEMBOT function are for. They also allow setting these values.

  • $FF9C: MEMBOT – read/write address of start of usable RAM
  • $FF99: MEMTOP – read/write address of end of usable RAM

Time

BASIC supports the TI and TI$ variables to access the system clock. The RDTIM and SETTIM KERNAL calls allow reading and writing this clock.

  • $FFDE: RDTIM – read system clock
  • $FFDB: SETTIM – write system clock

These functions use the addresses that used to be the BASIC commands SYS and VERIFY on the PET.

Screen

Machine code applications may want to know the size of the text screen (SCREEN) and be able to read or set the cursor position (PLOT). The latter is used by BASIC to align text on tab positions.

  • $FFED: SCREEN – get the screen resolution
  • $FFF0: PLOT – read/write cursor position

I/O

On the PET, the BASIC’s random number generator for the RND command was directly reading the timers in THE VIA 6522 controller. Since the VIC-20, this is abstracted: The IOBASE function returns the start address of the VIA in memory, and BASIC reads from the indexes 4, 5, 8 and 9 to access the timer values.

  • $FFF3: IOBASE – return start of I/O area

The VIC-20 Programmer’s Reference Guide states: “This routine exists to provide compatibility between the VIC 20 and future models of the VIC. If the I/O locations for a machine language program are set by a call to this routine, they should still remain compatible with future versions of the VIC, the KERNAL and BASIC.”

Vectors

The PET already allowed the user to override the following vectors in RAM to hook into some KERNAL functions:

  • $00E9: input from keyboard
  • $00EB: output to screen
  • $0090: IRQ handler
  • $0092: BRK handler
  • $0094: NMI handler

The VIC-20 ROM replaces these vectors with a more extensive table of addresses in RAM at $0300 to hook core BASIC and KERNAL functions. The KERNAL ones start at $0314. The first three can be used to hook IRQ, BRK and NMI:

  • $0314: CINV – IRQ handler
  • $0316: CBINV – BRK handler
  • $0318: NMINV – NMI handler

The others allow overriding the core set of KERNAL calls

  • $031A: IOPEN – indirect entry to OPEN ($FFC0)
  • $031C: ICLOSE – indirect entry to CLOSE ($FFC3)
  • $031E: ICHKIN – indirect entry to CHKIN ($FFC6)
  • $0320: ICKOUT – indirect entry to CHKOUT ($FFC9)
  • $0322: ICLRCH – indirect entry to CLRCHN ($FFCC)
  • $0324: IBASIN – indirect entry to CHRIN ($FFCF)
  • $0326: IBSOUT – indirect entry to CHROUT ($FFD2)
  • $0328: ISTOP – indirect entry to STOP ($FFE1)
  • $032A: IGETIN – indirect entry to GETIN ($FFE4)
  • $032C: ICLALL – indirect entry to CLALL ($FFE7)
  • $032E: USRCMD – “User-Defined Vector”
  • $0330: ILOAD – indirect entry to LOAD ($FFD5)
  • $0332: ISAVE – indirect entry to SAVE ($FFD8)

The “USRCMD” vector is interesting: It’s unused on the VIC-20 and C64. On all later machines, this vector is documented as “EXMON” and allows hooking the machine code monitor’s command entry. The vector was presumably meant for the monitor from the beginning, but this feature was cut from these two machines.

The KERNAL documentation warns against changing these vectors by hand. Instead, the VECTOR call allows the application to copy the complete set of KERNAL vectors ($0314-$0333) from and to private memory. The RESTOR command sets the default values.

  • $FF8D: VECTOR – read/write KERNAL vectors
  • $FF8A: RESTOR – set KERNAL vectors to defaults

Custom IRQ Handlers

If an application hooks the IRQ vector, it could just insert itself and call code originally pointed to by the vector, or completely replace the IRQ code (and return with pulling the registers and RTI). In the latter case, it may still want the keyboard and the system clock to work. The PET already had the UDTIM ($FFEA) call to update the clock in the IRQ context. The VIC-20 adds SCNKEY to handle the keyboard and populating the keyboard buffer.

  • $FF9F: SCNKEY – keyboard driver

CBM-II (1982)

The CBM-II series of computers was meant as a successor of the PET 4000/8000 series. The KERNAL’s architecture was based on the VIC-20.

The vector table in RAM is compatible except for ILOAD, ISAVE and USRCMD (which is now used), whose order was changed:

  • $032E: ILOAD – indirect entry to LOAD ($FFD5)
  • $0330: ISAVE – indirect entry to SAVE ($FFD8)
  • $0332: USRCMD – machine code monitor command input

There are two new keyboard-related vectors:

  • $0334: ESCVEC – ESC key vector
  • $0336: CTLVEC – CONTROL key vector (unused)

And all IEEE-488 KERNAL calls except ACPTR can be hooked:

  • $0346: ITALK – indirect entry to TALK ($FFB4)
  • $0344: ILISTN – indirect entry to LISTEN ($FFB1)
  • $0342: IUNLSN – indirect entry to UNLSN ($FFAE)
  • $0340: IUNTLK – indirect entry to UNTLK ($FFAB)
  • $033E: ICIOUT – indirect entry to CIOUT ($FFA8)
  • $033C: IACPTR – indirect entry to ACPTR ($FFA5)
  • $033A: ITKSA – indirect entry to TKSA ($FF96)
  • $0338: ISECND – indirect entry to SECOND ($FF93)

For no apparent reason, the VECTOR and RESTOR calls have moved to different addresses:

  • $FF84: VECTOR – read/write KERNAL vectors
  • $FF87: RESTOR – set KERNAL vectors to defaults

And there are several new calls. All machines since the VIC-20 have a way to hand control to ROM cartridges instead of BASIC on system startup. At this point, no system initialization whatsoever has been done by the KERNAL, so the application or game on the cartridge can start up as quickly as possible. Applications that want to be forward-compatible can call into the following new KERNAL calls to initialize different parts of the system:

  • $FF7B: IOINIT – initialize I/O and enable timer IRQ
  • $FF7E: CINT – initialize text screen

The LKUPLA and LKUPSA calls are used by BASIC to find unused logical and secondary addresses for channel I/O, so its built-in disk commands can open channels even if the user has currently open channels – logical addresses have to be unique on the computer side, and secondary addresses have to be unique on the disk drive side.

  • $FF8D: LKUPLA – search tables for given LA
  • $FF8A: LKUPSA – search tables for given SA

It also added 6 generally useful calls:

  • $FF6C: TXJMP – jump across banks
  • $FF6F: VRESET – power-on/off vector reset
  • $FF72: IPCGO – loop for other processor
  • $FF75: FUNKEY – list/program function key
  • $FF78: IPRQST – send IPC request
  • $FF81: ALOCAT – allocate memory from MEMTOP down

C64 (1982)

Both the KERNAL and the BASIC ROM of the C64 are derived from the VIC-20, so both the KERNAL calls and the vectors are fully compatible with it, but some extensions from the CBM-II carried over: The IOINIT and CINT calls to initialize I/O and the text screen exist, but at different addresses, and a new RAMTAS call has been added, which is also useful for startup from a ROM cartridge.

  • $FF87: RAMTAS – test and initialize RAM
  • $FF84: IOINIT – initialize I/O and enable timer IRQ
  • $FF81: CINT – initialize text screen

The other CBM-II additions are missing, since they are not needed, e.g. because BASIC doesn’t have the V4 disk commands (LKUPLA, LKUPSA) and because there is only one RAM bank (TXJMP, ALOCAT).

Plus/4 (264 Series, 1985)

The next Commodore 8 bit computers in historical order are the 264 series: the C16, the C116 and the Plus/4, which share the same general architecture, BASIC and KERNAL. But they are neither meant as successors of the C64, nor to the CBM-II series – they are more like spiritual successors of the VIC-20. Nevertheless, the KERNAL jump table and vectors are based on the C64.

Since the 264 machines don’t have an NMI, the NMI vector is missing, and the remaining vectors have been moved in memory. This makes most of the vector table incompatible with their predecessors:

  • $0314: CINV – IRQ handler
  • $0316: CBINV – BRK handler
  • (NMI removed)
  • $0318: IOPEN
  • $031A: ICLOSE
  • $031C: ICHKIN
  • $031E: ICKOUT
  • $0320: ICLRCH
  • $0322: IBASIN
  • $0324: IBSOUT
  • $0326: ISTOP
  • $0328: IGETIN
  • $032A: ICLALL
  • $032C: USRCMD
  • $032E: ILOAD
  • $0330: ISAVE

The Plus/4 is the first machine from the home computer series to include the machine code monitor, so the USRCMD vector is now used for command input in the monitor.

And there is one new vector, ITIME, which is called one every frame during vertical blank.

  • $0312: ITIME – vertical blank IRQ

The Plus/4 supports all C64 KERNAL calls, plus some additions. The RESET call has been added to the very end of the table:

  • $FFF6: RESET – restart machine

There are nine more undocumented entries, which are located at lower addresses so that there is an (unused) gap between them and the remaining calls. Since the area $FFD0 to $FF3F is occupied by the I/O area, these vectors are split between the areas just below and just above it. These two sets are known as the “banking routine table” and the “unofficial jump table”.

  • $FCF1: CARTRIDGE_IRQ
  • $FCF4: PHOENIX
  • $FCF7: LONG_FETCH
  • $FCFA: LONG_JUMP
  • $FCFD: LONG_IRQ
  • $FF49: DEFKEY – program function key
  • $FF4C: PRINT – print string
  • $FF4F: PRIMM – print string following the caller’s code
  • $FF52: MONITOR – enter machine code monitor

The DEFKEY call has the same functionality as FUNKEY ($FF75) call of the CBM-II series, but the two take different arguments.

C128 (1985)

The Commodore 128 is the successor of the C64. Next to a 100% compatible C64 mode that used the original ROMs, it has a native C128 mode, which is based on the C64 (not the CBM-II or the 264), so all KERNAL vectors and calls are compatible with the C64, but there are additions.

The KERNAL vectors are the same as on the C64, but again, the USRCMD vector (at the VIC-20/C64 location of $032E) is used for command input in the machine code monitor. There are additional vectors starting at $0334 for hooking editor logic as well as pointers to keyboard decode tables, but these are not part of the KERNAL vectors, since the VECTOR and RESTOR calls don’t include them.

The set of KERNAL calls has been extended by 19 entries. The LKUPLA and LKUPSA calls from the CBM-II exist (because BASIC has disk commands), but they are at different locations:

  • $FF59: LKUPLA
  • $FF5C: LKUPSA

There are also several calls known from the Plus/4, but at different addresses:

  • $FF65: PFKEY – program a function key
  • $FF7D: PRIMM – print string following the caller’s code
  • $FF56: PHOENIX – init function cartridges

And there are another 14 completely new ones:

  • $FF47: SPIN_SPOUT – setup fast serial ports for I/O
  • $FF4A: CLOSE_ALL – close all files on a device
  • $FF4D: C64MODE – reconfigure system as a C64
  • $FF50: DMA_CALL – send command to DMA device
  • $FF53: BOOT_CALL – boot load program from disk
  • $FF5F: SWAPPER – switch between 40 and 80 columns
  • $FF62: DLCHR – init 80-col character RAM
  • $FF68: SETBNK – set bank for I/O operations
  • $FF6B: GETCFG – lookup MMU data for given bank
  • $FF6E: JSRFAR – gosub in another bank
  • $FF71: JMPFAR – goto another bank
  • $FF74: INDFET – LDA (fetvec),Y from any bank
  • $FF77: INDSTA – STA (stavec),Y to any bank
  • $FF7A: INDCMP – CMP (cmpvec),Y to any bank

Interestingly, the C128 Programmer’s Reference Guide states that all calls since the C64 “are specifically for the C128 and as such should not be considered as permanent additions to the standard jump table.

C65 (1991)

The C65 (also known as the C64X) was a planned successor of the C64 line of computers. Several hundred prerelease devices were built, but it was never released as a product. Like the C128, it has a C64 mode, but it is not backwards-compatible with the C128. Nevertheless, the KERNAL of the native C65 mode is based on the C128 KERNAL.

Like the CBM-II, but at different addresses, all IEE-488/IEC functions can be hooked with these 8 new vectors:

  • $0335: ITALK – indirect entry to TALK ($FFB4)
  • $0338: ILISTEN – indirect entry to LISTEN ($FFB1)
  • $033B: ITALKSA – indirect entry to TKSA ($FF96)
  • $033E: ISECND – indirect entry to SECOND ($FF93)
  • $0341: IACPTR – indirect entry to ACPTR ($FFA5)
  • $0344: ICIOUT – indirect entry to CIOUT ($FFA8)
  • $0347: IUNTLK – indirect entry to UNTLK ($FFAB)
  • $034A: IUNLSN – indirect entry to UNLSN ($FFAE)

The C128 additions of the jump table are basically supported, but three calls have been removed and one has been added. The removed ones are DMA_CALL (REU support), DLCHR (VDC support) and GETCFG (MMU support). All three are C128-specific and would make no sense on the C65. The one addition is:

  • $FF56: MONITOR_CALL – enter machine code monitor

The removals and addition causes the addresses of the following calls to change:

  • $FF4D: SPIN_SPOUT
  • $FF50: CLOSE_ALL
  • $FF53: C64MODE
  • $FF59: BOOT_CALL
  • $FF5C: PHOENIX
  • $FF5F: LKUPLA
  • $FF62: LKUPSA
  • $FF65: SWAPPER
  • $FF68: PFKEY
  • $FF6B: SETBNK

The C128-added KERNAL calls on the C65 can in no way be called compatible with the C128, since several of the calls take different arguments, e.g. the INDFET, INDSTA, INDCMP calls take the bank number in the 65CE02’s Z register. This shows again that the C65 is no way a successor of the C128, but another successor of the C64.

Relationship Graph

The successorship of the Commodore 8 bit computers is messy. Most were merely spiritual successors and rarely truly compatible. The KERNAL source code and the features of the jump table mostly follow the successorship path, but some KERNAL features and jump table calls carried over between branches.

Which entries are safe?

If you want to write code that works on multiple Commodore 8 bit machines, this table will help:

PET

VIC-20

C64
C128
C65
Plus4

CBM-II

$FF80

KERNAL Version

$FF81

CINT

$FF84

IOINIT

$FF87

RAMTAS

$FF8A

RESTOR

$FF8D

VECTOR

$FF90

SETMSG

$FF93

SECOND

$FF96

TKSA

$FF99

MEMTOP

$FF9C

MEMBOT

$FF9F

SCNKEY

$FFA2

SETTMO

$FFA5

IECIN

$FFA8

IECOUT

$FFAB

UNTLK

$FFAE

UNLSN

$FFB1

LISTEN

$FFB4

TALK

$FFB7

READST

$FFBA

SETLFS

$FFBD

SETNAM

$FFC0

OPEN

$FFC3

CLOSE

$FFC6

CHKIN

$FFC9

CHKOUT

$FFCC

CLRCHN

$FFCF

BASIN

$FFD2

BSOUT

$FFD5

LOAD

$FFD8

SAVE

$FFDB

SETTIM

$FFDE

RDTIM

$FFE1

STOP

$FFE4

GETIN

$FFE7

CLALL

$FFEA

UDTIM

$FFED

SCREEN

$FFF0

PLOT

$FFF3

IOBASE

Code that must work on all Commodore 8 bit computers (without detecting the specific machine) is limited to the following KERNAL calls that are supported from the first PET up to the C65:

  • $FFCF: BASIN – get character
  • $FFD2: BSOUT – write character
  • $FFE1: STOP – test for STOP key
  • $FFE4: GETIN – get character from keyboard

The CHKIN, CHKOUT, CLRCHN, CLALL and UDTIM would be available, but they are not useful, since they are missing their counterparts (opening a file, hooking an interrupt) on the PET. The UDTIM call would be available too, but there is no standard way to hook the timer interrupt if you include the PET.

Nevertheless, the four basic calls are enough for any text mode application that doesn’t care where the line breaks are. Note that the PETSCII graphical character set and the basic PETSCII command codes e.g. for moving the cursor are supported across the whole family.

If you are limiting yourself to the VIC-20 and above (i.e. excluding the PET but including the CBM-II), you can use the basic of 34 calls starting at $FF90.

You can only use these two vectors though – if you’re okay with changing them manually without going through the VECTOR call in order to support the CBM-II:

  • $0314: CINV – IRQ handler
  • $0316: CBINV – BRK handler

VECTOR and RESTOR are supported on the complete home computer series (i.e. if you exclude the PET and the CBM-II), and the complete set of 16 vectors can be used on all home computers except the Plus/4.

The initialization calls (CINT, IOINIT, RAMTAS) exist on all home computers since the C64. In addition, all these machines contain the version of the KERNAL at $FF80.

References

The Ultimate Apollo Guidance Computer Talk @ 34C3

After The Ultimate Commodore 64 Talk (2008) and The Ultimate Game Boy Talk (2016), my third talk from the “Ultimate” series will take place at the 34th Chaos Communication Congress at Leipzig (27-30 Dec 2017):

The Apollo Guidance Computer (“AGC”) was used onboard the Apollo spacecraft to support the Apollo moon landings between 1969 and 1972. This talk explains “everything about the AGC”, including its quirky but clever hardware design, its revolutionary OS, and how its software allowed humans to reach and explore the moon. 

The talk will be presented by me (Michael Steil) and hessi. Date and time are subject to final scheduling. I will post updates as well as further information about the Apollo Guidance Computer on this blog in the next weeks.

You can read a more detailed abstract and vote on the talks on the 34C3 scheduling page.

62 Reverse-Engineered C64 Assembly Listings

Between 1992 and 1995, I reverse engineered Commodore 64 applications by printing their disassemblies on paper and adding handwritten comments (in German). These are the PDF scans of the 62 applications, which are 552 pages total.

File Author Date Description
Adv. Squeezer 1995-08-19 Decompression code
Amica Paint Schnellader Fast loader Amica Paint extension
Bef.-Erw. Diethelm Berens BASIC extension
Bitmap Manager Hannes Sommer 1994-10-26 AGSP map editor
Bitstreamer 1994-11-06 Decompression code
Blanking Screen blanker
Creatures 2 Autostart John Rowlands 1993-05-03
Creatures 2 Loader John Rowlands 1993-05-04
Delta Coder Nikolaus Heusler 1994-08-25
Drive Composer Chester B. Kollschen 1994-05-25 1541 drive head sample player, Magic Disk 07/1993
Drive Digi Player Michael Steil 1994-05-25 1541 drive head sample player
Drive ROMs 1995-04-19 Differences of the 1541/-II/-C/1571/1581 ROMs
EmBa Nikolaus Heusler 1992-08-08 Emergency BASIC
Errorline-Lister 1992-08-18 BASIC extension
Fast Disk Set
Fast Load Frank Deizner 1994-09-29
Fast Save Explorer/Agony 1995-08-24
File Copier 1993-07-22
Final Cartridge III Freezer Loader 1994-05-01 Fast load/decompression code of the FC3 unfreeze binary
Fred’s Back II Hannes Sommer 1994-12 Parts of the AGSP game
GEO-RAM-Test Mario Büchle 1996-05-21 GeoRAM detection code
GEOS Select Printer 1993-01-25 GEOS tool
GoDot A. Dettke, W. Kling 1994-05-17 Core code, ldr.koala, svr.doodle
Graphtool 1992
Heureka-Sprint 1994-01 Fast loader
IRQ Loader 2 Bit Explorer/Agony 1995-08-24 Fast loader
Kunst aus China Autostart 1993-03-31
MIST Entpacker Michael Steil 1995-08-21 Decompression code
MIST Load V2 Michael Steil Fast load
MSE V1.1 1992-08-08 Programm type-in helper from 64’er Magazin
MSE V2.1 1993-03-28 Programm type-in helper from 64’er Magazin
MTOOL Michail Popov 1992-08-14
Mad Code VSP 1994-10-21 VSP + DYSP code from the demo “Mad Code”
Mad Format 1995-04-20 Fast format
Magic Basic BASIC extension
Magic Disk Autostart 1992-07
Master Copy 1995-09-21 Disk copy
Master Cruncher 1994-05-25 Decompression code
Mayhem in Monsterland VSP John Rowlands 1994-12 VSP code from the game “Mayhem in Monsterland”
Mini-Erweiterung Marc Freese 1992-08-09
Mini-Scan The Sir 1993-07-22 Disk error scanner
Mini-Tris Tetris
Movie-Scroller Ivo Herzeg 1995-01
RAM-Check T. Pohl 1995-09-08 REU detection
SUCK Copy 1995-04-30 File copy
SUPRA 64 Loader
Shapes 64 R. Löwenstein 1994-09-05 Text mode windowing
Small Ass S. Berghofer 1993-08-07 Assembler
Spriterob Andreas Breuer 1992
Startprg.obj M. Hecht 1992-06
SuperinfoIRQ Nikolaus Heusler 1992-06
Swappers Best Copy Disk copy
TPP’s Screensplitter Armin Beck 1995-03
The Sampler 1994-04-30 $D418 sample player (high volume on 8580!)
Tiny Compiler Nikolaus Heusler 1994-09-06 BASIC compiler
Turrican 1 Autostart Manfred Trenz 1994-11-02
Turrican 1 Loader Manfred Trenz 1994
Turrican 2 Autostart Manfred Trenz 1994-10-03
Vectorset M. Strelecki 1995-02-05
Vis-Ass Mazim Szenessy 1992-08 Assembler and screen editor
Vocabulary Schindowski 1993-01-05 FORTH library
Warp-Load 1994-10-02 Fast loader

80 Columns Text on the Commodore 64

The text screen of the Commodore 64 has a resolution of 40 by 25 characters, based on the hardware text mode of the VIC-II video chip. This is a step up from the VIC-20’s 22 characters per line, but since computers in the professional segment (Commodore PET 8000 series, CP/M, MS-DOS) usually had 80 columns, several solutions – both hardware and software – exist to allow 80 columns on a C64 as well. Let’s look at how this is done in software! At the end of this article, I present a fast and full-featured open source implementation with several different character sets.

Regular 40×25 Mode

First, we need to understand how 40 columns are done. The VIC-II video chip has dedicated support for a text mode. There is a 1000 (= 40 * 25) byte “Screen RAM”, each byte of which contains the character to be displayed at that location in the form of an index into the 2 KB character set, which contains 8 bytes (for 8×8 pixels) for each of the 256 characters. In addition, there is a “Color RAM”, which contains a 1000 (again 40 * 25) 4-bit values, which represents a color for each character on the screen.

Putting a character onto the screen is quite trivial: Just write the index of it to offset column + 40 * line into Screen RAM, and its color to the same offset in Color RAM. An application can load its own character set, but the Commodore 64 already comes with two character sets in ROM: One with uppercase characters and lots of graphical symbols (“GRAPHICS”), and one with upper and lower case (“TEXT”). You can switch these by pressing the Commodore and the Shift key at the same time.

Bitmap Mode

There is no hardware support for an 80 column mode, but such a mode can be implemented in software by using bitmap mode. In bitmap mode, all 320 by 200 pixels on the screen can be freely addressed through an 8000 byte bitmap, which contains one bit for every pixel on the screen. Luckily for us, the layout of the bitmap in memory is not linear, but reminds of the encoding of text mode: The first 8 bytes in Bitmap RAM don’t describe, as you would expect, the leftmost 64 pixels on the first line. Instead, they describe the top left 8×8 block. The next 8 bytes describe the 8×8 block to the right of it, and so on.

This is the same layout as the character set’s: The first 8 bytes correspond to the first character, the next 8 bytes to the second character and so on. Drawing an 8×8 character onto a bitmap (aligned to the 8×8 grid) is as easy as copying 8 consecutive bytes.

This is what an 8×8 font looks like in memory:

0000 ··████··  0008 ···██···  0010 ·█████··
0001 ·██··██·  0009 ··████··  0011 ·██··██·
0002 ·██·███·  000a ·██··██·  0012 ·██··██·
0003 ·██·███·  000b ·██████·  0013 ·█████··
0004 ·██·····  000c ·██··██·  0014 ·██··██·
0005 ·██···█·  000d ·██··██·  0015 ·██··██·
0006 ··████··  000e ·██··██·  0016 ·█████··
0007 ········  000f ········  0017 ········

For an 80 column screen, every character is 4×8 pixels. So we could describe the character set like this:

0000 ········  0008 ········  0010 ········
0001 ··█·····  0009 ··█·····  0011 ·██·····
0002 ·█·█····  000a ·█·█····  0012 ·█·█····
0003 ·███····  000b ·███····  0013 ·██·····
0004 ·███····  000c ·█·█····  0014 ·█·█····
0005 ·█······  000d ·█·█····  0015 ·█·█····
0006 ··██····  000e ·█·█····  0016 ·██·····
0007 ········  000f ········  0017 ········

Every 4×8 character on the screen is either in the left half or the right half of an 8×8 block, so drawing an 4×8 character is as easy as copying the bit pattern into the 8×8 block – and shifting it 4 bits to the right for characters at odd positions.

Color

In bitmap mode, it is only possible to use two out of the 16 colors per 8×8 block, because there are only 1000 (40 * 25) entries for the color matrix. This is a problem, since we need three colors per 8×8: Two for the two characters and one for the background. We will have to compromise: Whenever a character gets drawn into an 8×8 block and the other character in the block has a different color, that other character will be changed to the same color as the new character.

Scrolling

Scrolling on a text screen is easy: 960 bytes of memory have to be copied to move the character indexes to their new location. In bitmap mode, 7680 bytes have to be copied – 8 times more. Even with the most optimized implementation (73ms, about 3.5 frames), scrolling will be slower, and tearing artifacts are unavoidable.

Character Set

Creating a 4×8 character set that is both readable and looks good is not easy. There has to be a one-pixel gap between characters, so characters can effectively only be 3 pixels wide. For characters like “M” and “N”, this is a challenge.

These are the character sets of four different software solutions for 80 columns:

COLOR 80 by Richvale Telecommunications
  

80COLUMNS
  

SCREEN-80 by Compute’s Gazette
  

Highspeed80 by CKtwo
  

Some observations:

  • Highspeed80 and SCREEN-80 have capitals of a height of 7 pixels (more detail, but very tight vertical spacing, making it hard to read), while COLOR 80 uses only 5 pixels (more square like the original 8×8 font, but less detail). 6 pixels, as used by 80COLUMNS, seems like a good compromise.
  • 80COLUMNS has the empty column at the left, which makes single characters in reverse mode more readable, since most characters have their stems at the left.
  • Except for Highspeed80, the graphical symbols are very similar between the different character sets.
  • All four character sets use the same strategy to distinguish between “M” and “N”.

The Editor

The “EDITOR” is the part of C64’s ROM operating system (“KERNAL”) that handles printing characters to the screen (and interpreting control characters), as well as converting on-screen contents back into a PETSCII string – yes, text input on CBM computers is done by feeding the keyboard input directly into character output, and reading back the screen contents when the user presses the return key. This way, the user can use the cursor keys to navigate to existing text anywhere on the screen (even to the output of previous commands), edit it, and have the system interpret it as input when pressing return.

The C64’s EDITOR can only deal with 40 columns (but has a very nice feature that allows using two 40 character lines as one virtual 80 character input line), and has no idea how to draw into a bitmap, so a software 80 characters solution basically has to provide a complete reimplementation of this KERNAL component.

The KERNAL provides user vectors for lots of its functionality, so both character output, and reading back characters from the screen can be hooked (vectors IBSOUT at $0326 and IBASIN at $0324). In addition to drawing characters into the bitmap, the character codes have to be cached in a 80×25 virtual Screen RAM, so the input routine can read them back.

The PETSCII code contains control codes for changing the current color, moving the cursor, clearing the screen, turning reverse on and off, and switching between the “GRAPHICS” and “TEXT” character sets. The new editor has provide code to interpret these. There are two special cases though: When in quote mode (the user is typing text between quotes) or insert mode (the user has typed shift+delete), most special characters show up as inverted graphical characters instead of being interpreted. This way, control characters can be included e.g. in strings in BASIC programs.

There are two functions though that cannot be intercepted through vectors: Applications (and BASIC programs) change the screen background color by writing the color’s value to $d020, since there is no KERNAL function or BASIC command for it, and the KERNAL itself switches between the two character sets (when the user presses the Commodore and the Shift key at the same time) by directly writing to $d018. The only way to intercept these is by hooking the timer interrupt vector and detecting a change in these VIC-II registers. If the background color has changed, the whole 1000 byte color matrix for bitmap mode has to be updated, and if the character set has changed, the whole screen has to be redrawn.

The Implementation

I looked at all existing software implementations I could find and concluded that “80COLUMNS” (by an unknown author) had the best design and was the only one to implement the full feature set of the original EDITOR. I reverse-engineered it into structured, easy to read code, added Ilker Ficicilar’s fast scrolling patch as well as my own minor cleanups, fixes and optimizations.

https://www.github.com/mist64/80columns

The project requies cc65 and exomizer to build. Running make will produce 80columns-compressed.prg, which is about 2.2 KB in size and can be started using LOAD/RUN.

The source contains several character sets (charset.s, charset2.s etc.) from different 80 column software solutions, which can be selected by changing the reference to the filename in the Makefile.

The object code resides at $c800-$cfff. The two character sets are located at $d000-$d7ff. The virtual 80×25 Screen RAM (in order to read back screen contents) is at $c000-$c7ff. The bitmap is at $e000-$ff40, and the color matrix for bitmap mode is at $d800-$dbe8. All this lies beyond the top of BASIC RAM, so BASIC continues to have 38911 bytes free.

In order to speed up drawing, the character set contains all characters duplicated like this:

0000 ········  0008 ········  0010 ········
0001 ··█···█·  0009 ··█···█·  0011 ·██··██·
0002 ·█·█·█·█  000a ·█·█·█·█  0012 ·█·█·█·█
0003 ·███·███  000b ·███·███  0013 ·██··██·
0004 ·███·███  000c ·█·█·█·█  0014 ·█·█·█·█
0005 ·█···█··  000d ·█·█·█·█  0015 ·█·█·█·█
0006 ··██··██  000e ·█·█·█·█  0016 ·██··██·
0007 ········  000f ········  0017 ········

This way, the drawing code only has to mask the value instead of shifting it. In addition, parts of character drawing and all of screen scrolling are using unrolled loops for performance.

Contributions to the project are very welcome. It would be especially interesting to add new character sets, both existing 4×8 fonts from other projects (including hinted TrueType fonts!), and new ones that combine the respective strengths of the existing ones.

80×33 Mode?

Reducing the size of characters to 4×6 would allow a text mode resolution of 80×33 characters. Novaterm 10 has an implementation. At this resolution, logical characters don’t end at vertical 8×8 boundaries any more, making color impossible, and the drawing routine a little slower. It would be interesting to add an 80×33 mode as a compile time option to “80columns”.

Building the Original Commodore 64 KERNAL Source

Russian Translation by HTR

Many reverse-engineered versions of “KERNAL”, the C64’s ROM operating system exist, and some of them even in a form that can be built into the original binary. But how about building the original C64 KERNAL source with the original tools?

The Commodore engineer Dennis Jarvis rescued many disks with Commodore source, which he gave to Steve Gray for preservation. One of these disks, c64kernal.d64, contains the complete source of the original KERNAL version (901227-01), including all build tools. Let’s build it!

Building KERNAL

First, we need a Commodore PET. Any PET will do, as long as it has enough RAM. 32 KB should be fine. For a disk drive, a 2040 or 4040 is recommended if you want to create the cross-reference database (more RAM needed for many open files), otherwise a 2031 will do just fine. The VICE emulator will allow you to configure such a system.

First you should delete the existing cross-reference files on disk. With the c1541 command line tool that comes with VICE, this can be done like this:

c1541 c64kernal.d64 -delete xr* -validate

Now attach the disk image and either have the emulator autostart it, or

LOAD"ASX4.0",8
RUN

This will run the “PET RESIDENT ASSEMBLER”. Now answer the questions like this:

PET RESIDENT ASSEMBLER V102780
(C) 1980 BY COMMODORE BUSINESS MACHINES

OBJECT FILE (CR OR D:NAME): KERNAL.HEX
HARD COPY (CR/Y OR N)? N
CROSS REFERENCE (CR/NO OR Y)? N
SOURCE FILE NAME? KERNAL

The assembler will now do its work and create the file “KERNAL.HEX” on disk. This will take about 16 minutes, so you probably want to switch your emulator into “Warp Mode”. When it is done, quit the emulator to make sure the contents of the disk image are flushed.

Using c1541, we can extract the output of the assembler:

c1541 c64kernal.d64 -read kernal.hex

The file is in MOS Technology Hex format and can be converted using srecord:

tr '\r' '\n' < kernal.hex > kernal_lf.hex
srec_cat kernal_lf.hex -MOS_Technologies \
-offset -0xe000 \
-fill 0xaa 0x0000 0x1fff \
-o kernal.bin -Binary

This command makes sure to fill the gaps with a value of 0xAA like the original KERNAL ROM.

The resulting file is identical with 901227-01 except for the following:

  • $E000-$E4AB is empty instead of containing the overflow BASIC code
  • $E4AC does not contain the checksum (the BASIC program “chksum” on the disk is used to calculate it)
  • $FFF6 does not contain the “RRBY” signature

Also note that $FF80, the KERNAL version byte is $AA – the first version of KERNAL did not yet use this location for the version byte, so the effective version byte is the fill byte.

Browsing the Source

An ASCII/LF version of the source code is available at https://github.com/mist64/cbmsrc.

;****************************************
;*                                      *
;* KK  K EEEEE RRRR  NN  N  AAA  LL     *
;* KK KK EE    RR  R NNN N AA  A LL     *
;* KKK   EE    RR  R NNN N AA  A LL     *
;* KKK   EEEE  RRRR  NNNNN AAAAA LL     *
;* KK K  EE    RR  R NN NN AA  A LL     *
;* KK KK EE    RR  R NN NN AA  A LL     *
;* KK KK EEEEE RR  R NN NN AA  A LLLLL  *
;*                                      *
;***************************************
;
;***************************************
;* PET KERNAL                          *
;*   MEMORY AND I/O DEPENDENT ROUTINES *
;* DRIVING THE HARDWARE OF THE         *
;* FOLLOWING CBM MODELS:               *
;*   COMMODORE 64 OR MODIFED VIC-40    *
;* COPYRIGHT (C) 1982 BY               *
;* COMMODORE BUSINESS MACHINES (CBM)   *
;***************************************
.SKI 3
;****LISTING DATE --1200 14 MAY 1982****
.SKI 3
;***************************************
;* THIS SOFTWARE IS FURNISHED FOR USE  *
;* USE IN THE VIC OR COMMODORE COMPUTER*
;* SERIES ONLY.                        *
;*                                     *
;* COPIES THEREOF MAY NOT BE PROVIDED  *
;* OR MADE AVAILABLE FOR USE ON ANY    *
;* OTHER SYSTEM.                       *
;*                                     *
;* THE INFORMATION IN THIS DOCUMENT IS *
;* SUBJECT TO CHANGE WITHOUT NOTICE.   *
;*                                     *
;* NO RESPONSIBILITY IS ASSUMED FOR    *
;* RELIABILITY OF THIS SOFTWARE. RSR   *
;*                                     *
;***************************************
.END