Sunday, September 4, 2022

What the KIM-1 really needs is bubble memory (plus: 20mA current loop for fun and profit)

It seems like everything has flash. Flash mobs, flash photography, Flash Gordon, flash memory. (Other than the past couple years, of course, which haven't been very flash.) And, because solid-state-all-the-things, you can get flash storage devices for tons of classic computers where even the tiny microcontroller in the SD cards is probably more powerful than the systems they're being interfaced to. Why, you can even connect one to an MOS KIM-1, the famous mid-1970s MOS 6502 single-board computer. Now at last you don't need to rekey everything in or screw around with an audio recorder.

But, of course, if you've read this blog for any period of time you know I don't go in for that sort of new-fangled nonsense around here. I like my retrocomputing frivolities period-correct. What the KIM-1 really needs as a mass storage medium ... is bubble memory.

Bubble memory was going to be the storage media of the 1980s, possibly even uniting RAM and external storage into one addressing space to rule them all (and proving Optane definitely wasn't a new idea). The concept was that loops of magnetized domains in a garnet film, on a non-magnetic crystalline substrate, could be nudged about by tiny embedded coils and guides and read and written to. An array of detectors sat on one side for reading, and the output was cycled about to electromagnets on the other side which would restore or change the data, thus maintaining the loop indefinitely. (For you chemistry nerds, the garnet film was a special synthetic garnet devised for the purpose using either gadolinium or iron and a selection of other rare earth elements.) Because large areal densities were possible with the right material, and because the magnetic patterns were shockproof and non-volatile, it was thought that bubble memory devices could replace pretty much any kind of memory except for very high performance applications.

As a result, by the mid-to-late 1970s many electronics companies were working feverishly on their own bubble memory devices. MOS Technology unfortunately wasn't one of them, but Texas Instruments was, and they got to market first. Shown here are the first commercial products to contain bubble memory, the Silent 700 Model 763 and Model 765 teletypes from 1977.

Recall from our earlier look at a Model 745 the general notion of the Silent 700 line, which are teleprinter terminals using a much quieter thermal head instead of a more typical impact printer. Instead of a screen, you have paper. The Model 763 ASR is on the left and the Model 765 Portable on the right, the most obvious (but not only) difference being that the 765 had an acoustic coupler you could jam a phone handset into as a crude modem. Otherwise, both units are actually rather portable for the time, exactly the same form factor as their 743/745 ancestors. The acronym ASR ("automatic send and receive") originated with the Teletype Corporation's Model 28 and came to be a term for any teletype that had storage and playback capability; compare with KSR ("keyboard send and receive") and RO ("receive only").

All Silent 700s of this era have a cloned Intel 8080 CPU handling the keyboard and printer, usually a TI but later an AMD, plus a 4K ROM and a pathetic 64 bytes of RAM for a workspace. In the 743/745, the 8080 handled everything, including external serial communications (using the unusual TMS 5504). However, for the added electronics present in the 763 and 765 — and, as we'll see, internal storage management software — Texas Instruments added a second board powered by a TMS 9980 microcontroller which acts as the master of ceremonies. This is the same CPU family as the TMS 9900 used in the TI 99/4A and uses the exact same instruction set, though limited to 16K and lashed to a slower 8-bit data bus (disadvantageous as 9900-series chips use a scratchpad RAM for most of their registers, but acceptable for this application). The 9980 has 20K of ROM (bank-switched into 4K pieces) and 12K of RAM all to itself, along with its own dedicated serial communications chip, a TMS 9902, making the 763/765 basically a two-CPU microcomputer disguised as a teletype. In this design the 8080 effectively becomes a terminal within a terminal, controlling the LEDs, monitoring the keyboard and driving the printer, and sending keystrokes for transmission and receiving commands to toggle LEDs and data to print via an internal 9600bps serial link with the 9980. Unlike the 743/745, where the 5504 handles the external serial connection, the 5504 in the 763/765 handles the 8080's internal one and the 9980's 9902 does the transmission to the outside world. The keyboard and printer is also how you enter commands to the lower board to manage the internal memory and communications settings (using a special CMD key to get its attention). The two CPUs don't see each other's addressing space and otherwise operate independently.

In these machines the bubble memory acts like punched tape, which was used for data storage on teletypes like the ASR-28 and ASR-33 (KSRs and ROs, conversely, have no tape punch). Like it says, punched tape uses a pattern of dots punched on paper tape or sometimes Mylar that serves as a storage medium. Unfortunately even carefully fanfolded punched tape can take up a fair bit of space and paper tape can't withstand heavy punishment, so for Texas Instruments' first ASR (the 1973 Models 732 and 733) they used standard audio cassette recorders instead. This yielded a lot of capacity but made the terminals bulky and non-portable; contrast with the 763/765, where using bubble memory meant everything could be small and solid-state.

On top of that, even though bubble memory was never very fast and access is essentially sequential waiting for the right address to come around, using it like punched tape is itself sequential and most communications were at low baud rates anyway, so in this application neither was a real disadvantage. Also, even though some types of bubble memory require coming up to a necessary operating temperature, most notoriously the Konami Bubble System arcade board which could take several minutes to start in winter, this was really only a factor for certain large bubble memory devices and these smaller TI modules don't seem to have that problem. (Fun fact: Konami's musical "WARMING UP NOW" countdown is actually loading data from bubble memory into RAM — by the time this message appears, the modules are already at the proper temperature.)

Bubble memory does have other unique quirks, primarily having to do with its internal organization. Inside the 763/765 are two boards, the upper board with the keyboard and printer being the 8080 terminal-within-a-terminal and the lower board being the 9980 master board. That lower board is shown here. Note the ROMs to the middle right, RAM in the centre, and to the middle left the 9980. In the lower left is a TMS 5502. This is the bubble memory controller (earlier boards used a TMS 9916). The two daughterboards in the middle marked "DISCRETE MEMORY" are the first model of the bubble memory cards, each card with a single bubble memory module. The modules have a unique flat form factor and because they are not DIPs are very easy to pick out visually (compare with the well-known Intel 7110 in a nearly identical package). They are magnetically shielded to prevent damage to the data within and are surrounded by their drive circuits.

Each module is organized into pages. Hardly unusual, except that in these TIB 0100 modules the pages are 18 bytes each (remember this number) and the device stores 641 of them, yielding a not-very-binary 11,538 bytes apiece. Initial lower yields and areal density are to blame for those figures; in fact, weird layouts like this were so typical of early bubble memory that Intel's 1982 datasheet for the 7110 (a 1Mbit/128KB module with 2048 64-byte pages) brags about its "true binary organization" as a feature. Additionally, bubble memory, like every other type of memory, has bad parts. The 120-bit hex code printed on each module's label is the bubble mask, which identifies which of the individual minor loops within are defective (up to 13) and is used by the 5502 controller to adjust data transfer accordingly. The bubble mask is determined by testing at the factory and every bubble memory part from every manufacturer has such a mask; larger capacity modules with more loops have a correspondingly longer one. The bubble mask is so critical for proper function that each of these modules has a copy of its own mask in its first page, which is guaranteed to be error-free. The 9980 reads this mask on startup. If the mask for a particular module is erased or corrupt, the machine will require the operator to reenter the mask code, using the last four hex digits as a CRC to guard against input errors. If an incorrect mask is ever entered and accepted, the module will be ruined, even if the proper mask is immediately re-entered!

Later in the relatively short lifespan of these machines TI upgraded to double-module cards, though the modules are the same TIB 0100s storing the same 11.27KB a pop to yield 22.5KB per module. This particular 765 has the maximum four installed (you can't mix double-module and single-module cards) for 90.14KB of total storage minus system overhead. That's less than even the floppy disks of the day, but it was a lot more rugged, and you didn't need a disk drive.
For completeness, here is the upper board of the 765, with the print mechanism removed and keyboard out. If you compare it with the Model 745 it's nearly the same, with the 8080 (here an AMD 8080) CPU and 5504 I/O chips just below the center, the ROM to the left and below them, and the printhead connector to the right and above. You can also see the piezo beeper. The only major difference is a thick cable coming up from below on the left south of the cooling fan, which is the serial link to the lower board.

Incidentally, there's another historical reason to use a Silent 700 with the KIM-1. Let's look at this page of the 1975 Rev D disassembly of its ROMs in the manual (shameless plug: remember when computers still came with source code and schematics? you can still buy one):

Besides the "CARD" orientation (where we'd call them lines) and the typos, notice anything about the printer typeface? Here, let me make it easy for you.
Yes, comparing the printed output from our teletype with the manual (most notably the zeroes, O's and the use of a diamond for the asterisk) it's obvious that the KIM-1 ROM disassembly was actually printed on a Silent 700. Alas, in 1975, this was likely a 730-series as the Models 743/745 had only just come out and the 763/765 didn't exist yet. For that matter, by 1977 Commodore would have already bought MOS Technology and Jack Tramiel, still smarting from TI screwing him over calculator chips, would have almost certainly smashed any remaining Texas Instruments gear he found to smithereens.

The first step is to connect the two. Unfortunately, the first Model 765 I tracked down for this purpose turned out to be DOA. When the lower board craps out the upper board goes into a loop waiting for instructions, which manifests as the COMMAND light immediately lighting as soon as you turn the machine on, and otherwise no teletype activity. The service manual indicates the only solution is to entirely replace the lower board, so I picked up a second 765 in the hopes I could get parts off it and it did the same thing. A third 765 didn't turn on the COMMAND light and seemed to act otherwise normally except for the fact the print head wouldn't move. I replaced it with the print mechanism from one of the other 765s, which got the print head moving, but the actual printing elements were also bad (you could see where characters were supposed to print but they were malformed). I replaced the print head and turned it back on, and the COMMAND light immediately lit like the others.

Three dead units later I can only conclude the 765s are cursed. However, I did track down a 763, and this unit works perfectly, so that's what we'll be using.

That was fortuitous, by the way, because it turns out the 763 is different from the 765 in another way apart from the coupler: it supports 20mA current loop communication. The 763 and 765 have both "internal" and "external" (EIA) serial interfaces. The external port on both presents RS-232 signals, or at least TI's version of them back then — and the pinout varies from model to model, so make sure you have the right manual — but the internal port differs. On the 765, this is ring and tip (see our T1 article) for the acoustic coupler. On the 763, it's a 20mA current loop interface.

When people nowadays think serial, we think RS-232, and we think of it as a well-established, well-defined standard because by now it is. In 1977, however, EIA-232 (as it was also called, for the now-defunct Electronic Industries Association) was rather less consistent and vendors had slightly different conventions about what signals meant what and in what combinations. In particular, what "online" or "ready" meant could vary from terminal to terminal in not always clearly defined ways.

Digital current loop, on the other hand, is a much simpler two or four-wire serial communication system and was supported by the majority of devices and terminals at that time. Whereas RS-232 uses voltage for signaling, current loop (as the name implies) uses amperage. This was particularly useful for long lines, where the line's resistance could be overcome by higher voltage to yield the same amount of current on the other end, and some runs could be miles apart with no signal loss. Since teletypes were the workhorses of wire reports, telexes and telegrams, current loop could reliably and inexpensively drive a long-distance communications network that would have been much more complicated with RS-232. The general principle is still used, with modifications, in modern process control systems.

Signalling is notionally 20 milliamps, though some systems used 60mA. Bidirectional communications require two loops, one transmit, one receive, and each loop has a signal and return side. The convention with terminals of the day is that they were passive devices: they did not generate the current, they merely sensed (for printing) or gated (for typing) the current generated by whatever device was controlling them. The controlling device puts current on the terminal's source lines for the keyboard and printer, and accepts current back from the gate (keyboard) or sink (printer). The pattern of bits thus sent and received constitutes the serial data stream.

As you know, the KIM-1 can be controlled either with its keypad or with a teletype, and the KIM-1's built-in support for teletypes is, in fact, simply 20mA current loop. RS-232 option boards like Bob Applegate's KIM-1 I/O card (not affiliated or sponsored, just a satisfied customer) just convert current loop to RS-232. Oddly, the KIM-1's documentation says it puts 20mA on the keyboard line ("source") and accepts coded signal pulses on the keyboard return line ("gate"), but puts pulses of 20mA on the printer return line ("source") and sinks those pulses on the printer line ("sink"). Wired up, it looks like this:

On the 763's side we have a DB-15 female breakout box and on the KIM-1's side we have a 22/44-pin extender, another one of Bob's boards, so that I don't have to muck about with the edge connector. In order, pins R, S, T and U from the KIM-1 go to pins 5, 6, 4 and 7 respectively on the Model 763 (R and T are the keyboard "receive" loop and S and U are the printer "transmit" loop). Please note that this may vary for other Silent 700 models. A ghetto jumper ties together pins 21 and V on the KIM-1 side. When these pins are connected, the KIM monitor will use the current loop instead of the keypad and LEDs (you can install a switch here, as Bob's I/O board has, but for now I just pull one side off when I need to).

The next step is to set the communication parameters. Current loop has no hardware flow control, which is good in the sense you don't have to worry about those signals (again, a definite source of consternation back in the day), but bad in that you have to choose a bitrate the system can keep up with. Even if you use an RS-232 converter, the KIM-1 still doesn't know what CTS, or DTR, or DCD, etc. are and will never set or check them. Because it's just a little 1MHz CPU bitbanging everything, your fastest reliable communication speed is 300 bps; the KIM-1 ROM can autosense most rates and faster speeds than that may work for small bursts, but they're guaranteed to drop characters sooner or later in inconvenient places. Parity, helpfully, is just ignored, so I set the 763 to 300 baud on the internal port with mark parity. Press either RUBOUT (CTRL + -) or SKIP (RETURN) and the KIM will answer.

Success! You can see the KIM responding (KIM) on the paper.
To clean it up I got the Dremel cutter out and made a little side hole in the DA-15 (damn pedants) connector's case for the wires.
A straight-thru extension cable between the interface and the 763 completes the picture.

Now that we're wired in, let's try to actually save and load data from bubble memory. The KIM-1 ROM also directly supports reading and writing from a tape punch using its own peculiar but easily understood hex format, but it's up to the operator to actually turn on and off the punch; all the KIM is doing is sending and receiving data as usual, just in a particular fashion. Let's demonstrate this by loading and saving this simple assembly language program, which I manually keyed in with the KIM-1's hex keypad:

     * = $0000

inh    = $f9
pointl = $fa
pointh = $fb
scands = $1f1f

lup    lda #$34    ; a9 34
       sta inh     ; 85 f9
       lda #$77    ; a9 77
       sta pointl  ; 85 fa
       lda #$00    ; a9 00
       sta pointh  ; 85 fb
       jsr scands  ; 20 1f 1f
       jmp lup     ; 4c 00 00

This will light up the KIM-1's segment LEDs with those hex values when run. We'll now set the KIM up to emit data from $0000 through $000F (the ending address is set at location $17F7 and we then set the KIM monitor to the desired starting address), and trigger it to emit to "punched tape" with the Q command. Where you see [space], [SKIP] or [CR], that means I pressed that key. This program's final byte is actually at $0012 and I'm totally an idiot for counting bytes wrong, but it doesn't matter for a reason I'll explain.


0001 34 17F7[space]
17F7 FF 0F.
17F8 FF 00.
17F9 FF 0000[space]
0000 A9 Q

The KIM-1 then emits records, which because we haven't done anything here on the 763 side will just go straight to the printer.

KIM-1 punched tape uses this format:


There are six embedded nulls also present before each line which we don't see here. Each line then "starts" with a semicolon character followed by a varying number of hexadecimal digits. First is a byte giving the number of bytes in this record (maximum 24), then the 16-bit address to store the data, 24 bytes of memory and finally a 16-bit checksum (the wrapped sum of all preceding bytes), terminated by a line feed. The final record has a length of zero data bytes and the total number of records (not counting this one) that were transmitted, plus its own checksum computed the same way.

The reason my error with the ending address didn't matter is that the KIM-1 will always print a 24 byte record no matter how many bytes are left to actually print (but it will accept records smaller than 24 bytes). This means to calculate the approximate overhead of a hex dump requires you to round up the total space to a multiple of 24 and multiply that by two for the hex digits. Then, for every group of 48 hex digits add 2 more bytes for length, 4 bytes for address, 4 bytes for checksum, one byte for the semicolon, two bytes for CR LF (just in case) and six bytes for the invisible nulls — or, said another way, reserve 67 bytes for every 24. The last record is always 6 nulls, one semicolon, 10 hex bytes and CR LF, so add on another 20.

ASR teletypes accomplish automated communication through recording and playback of sessions, so to emulate a tape punch, we'll have the 763 record the KIM-1's punched tape output, and then to load it back in, the 763 will play it back.

To talk to the 9980, we use the COMMAND mode. Pressing CMD lights the COMMAND light (this time usefully) and prints a unique slashed triangle prompt which cannot be generated by any keys on the keyboard, such that a command to the internal hardware can be easily identified on a transcript. I'll approximate this with the Unicode combined glyph ▹̶. Let's query the internal filesystem on this "clean" unit using the CATALOG command.


               MEMORY AVAIL =  250  80-CHAR LINES
               RECORD FILE:
               PLAYBACK FILE:

250 80-character lines equals 20,000 characters, so this is a 22.5KB unit minus reserved portions of the bubble memory. The internal filesystem is simple, just one level of files with no partitions or subdirectories, and filenames are six alphanumeric characters maximum (though they must always start with a letter and cannot be the words KEY or TO). Interestingly, the filesystem is case-sensitive, so we'll just use upper case for simplicity.

You might find "80-character lines" as a measurement of storage to be curious. This is because the system is highly record-oriented and most people were used to punched cards, which generally hold up to 80 bytes each. The filesystem has just two types, line (with fixed-width lines serving as records) and continuous (byte by byte). Line-oriented files are obviously easier to edit and certain features like its internal scripting language only work with line-oriented files, though we won't be demonstrating that feature in this post despite some sophisticated data entry capabilities. On the other hand, because lines in that format are fixed-width, anything longer than the record size is truncated (the maximum is in fact 80 characters) and anything shorter becomes wasted space. Continuous lines can span multiple records. Since we don't need the extra features of a line-oriented file, we'll use continuous files for better space efficiency.

Let's go ahead and try to record that punched tape session to a file. The CREATE command allows you to create a file of one of those two types, specifying the number of records and the record size. Note that you have to declare the file size in advance, though there are workarounds we'll explore. One might assume that the optimal record size for a continuous, byte oriented file is one, so let's do that. (One might also assume that we're doing that to demonstrate why you shouldn't do that. Shut up, smartypants, and play along.) A size of 128 bytes should be nice and small and more than enough to hold that amount of punched tape. We'll then set record and playback to point to that file.

We press the CMD key in front of every command as Command mode is not sticky; the prompt appears every time we do so.




Let's see what this has done to our filesystem.

▹̶ CATALOG                    MEMORY CATALOG

                              RECORDS  RECORD  RECORDS

               HELLOO  CONT     128       1       0

               MEMORY AVAIL =  224  80-CHAR LINES
               RECORD FILE:   HELLOO
               PLAYBACK FILE: HELLOO

What? How could we use over 2K just for 128 bytes?? Well, let's see if it works, anyway.

We have the end location all queued up just as previously, but before we press Q we'll activate recording. This is done by holding down FCTN and pressing 6 to rewind recording (a precaution) and then pressing FCTN-2 to start recording. The RECORD LED lights up, indicating the machine is ready to record. Now we'll press Q and the same contents will be typed out, but will also be stored in the file HELLOO. When the KIM-1 finishes emitting the tape records, we'll press FCTN-4 to turn off the recorder.

There's a built-in line editor as well. Let's see what the file looks like by giving it the command EDIT HELLOO. EDIT has its own little prompt, a tiny arrow (→). We'll press FCTN-4 to print it out and ... whoa:

The 763 is generally faithful to what it receives, up to and including control characters. You can see that the Q we typed was dutifully captured, but you'll also see an ☒ representing an EOL (configurable) and then six NUs representing nulls. However, every single byte gets spat out on its own separate line, our second clue that we did something wrong when we created the file. Nevertheless, all the characters are there. At the end, it prints ETX, indicating the end of text.


We press FCTN-8 to exit edit mode, to which it responds with ONE MOMENT PLEASE while it packs the file in memory, and then DONE.

Regardless of any wasted space, the file does seem to be intact. Let's see if we can successfully read it back to the KIM-1. We already set the playback to the same file, so let's also stomp on a few bytes of it beforehand as a check and then import it back using the L command. We don't need to give it a starting or ending address because the KIM can derive that from the tape data.




0001 34 0000[space]
0000 A9 12.
0001 34 34.
0002 85 56.
0003 F9 0000[space]
0000 12[CR]
0001 34 L

An explanation: SPACE tells the KIM monitor to switch to that address (equivalent of the AD key on the keypad), CR advances memory (the + key) and the period "." writes a hex byte to the current location and advances. In this transcript we went to location $0000 and wrote $12, $34 and $56 to the first three locations, rewound back, and then checked the first couple bytes to be sure they were written.

We finished by typing L and the KIM-1 is now waiting for the tape data to begin. We press FCTN-5 to rewind playback (again, paranoia) and FCTN-1 to begin playback. The PLAYBACK LED turns on and starts transmitting from the file and automatically extinguishes at the end. (It should be noted parenthetically that the bubble memory system has a maximum throughput of 320 characters per second. This only starts becoming significant at 2400bps and faster speeds; at 300bps we are well within specifications.)

0001 34 LQ
;0000010001 KIM

0001 34 0000[space]
0000 A9[CR]
0001 34[CR]
0002 85

The KIM-1 automatically resets itself at the end of a successful transmission; a transmission with an error will just hang. It also politely ignored the Q character we typed when the recorder was on, which is nice because then we don't need to edit that out. I checked a couple of bytes we munged and they look good. We'll start it by pressing G in the monitor (it doesn't make any difference to run it from $0002).

Viewing the LEDs upside down, our super secret message is revealed, proving the program was loaded successfully. Bubble memory rides again!

Now, let's do something about that unholy waste of space. Like every other storage medium, bubble memory is most space-efficient when you use its native page size. The user's manual doesn't really explain this, but both the systems manual and the maintenance manual agree you should use a multiple of its 18-byte page size for your record size wherever possible. (This is particularly interesting because the 80-character-line maximum isn't an integer multiple of 18. The manual admits that 80 character lines actually waste 10 characters each! Obviously the punched card mafia prevailed despite that.) What appears to have happened is that by using a one-byte record, every single byte ended up taking up a 18-byte page all to itself.

A quick CATALOG will tell us that the original file reserved 128 "one-byte records" of which it only used 85, so it must be 85 bytes long. We'll thus create a new file with 5 18-byte records to equal 90 bytes.


▹̶ CATALOG                    MEMORY CATALOG

                              RECORDS  RECORD  RECORDS

               HELLOO  CONT     128       1      85
               HELLO2  CONT       5      18       0

               MEMORY AVAIL =  223  80-CHAR LINES
               RECORD FILE:   HELLOO
               PLAYBACK FILE: HELLOO

Notice that despite creating a 90-byte file, we only used one "80-char line" (because that 80-character line actually takes up 90 characters). Very efficient!

The best news is that we don't need to recreate the file: we can just COPY HELLOO TO HELLO2 and the 763 will sort out spanning the records. As a check we'll then display the file and any control characters, which looks like this (here we will use Unicode for the nulls, even though the 763 renders it like NU):




The end-of-lines don't show up; the 763 internally stores those. Also notice my typo when I was entering the CHANGE PLAYBACK command. As you type a line, the ←CHAR→ key is active. It moves the carriage back one and advances the paper if needed so that the carriage is under the character you intend to replace, sort of like a backspace without actually erasing anything (because it's already printed out, silly).



The file is intact and we can test it by loading it again.

And that works too.

So let's think about bigger files. Since we have this miraculous dual-CPU teletype here with a wonderful full keyboard that can even edit files and correct our mistakes, it would be nice to be able to enter data on the teletype, edit it if needed, and transfer it to the KIM-1. (We're not plugging the teletype into another computer to preload it; that's cheating!) If we use the same keystrokes we would make typing it in, then it should work playing it back too ... right?



0000 A9.12.85.FB.A9.34.85.FA.A9.56.85.F9.20.1F.1F.4C.00.00.[SKIP]


The KIM-1 monitor can't handle us typing at the same time as it's typing back and the whole thing turns into a mess. (The *** 88 *** errors are even more ominous: those show the maimed characters are being interpreted by the teletype as playback control sequences! A little more about this at the end.) Worse, the previous examples demonstrate that entering data byte by byte into the KIM-1 monitor with the teletype uses a whole line for every byte, so even if it did work, we'd still need a better solution for anything non-trivial.

Some of you will have already said we should just make a punched tape file and key it in by hand. We can do that (and for a critical step below we will), except that we have to calculate the checksums too, and if we ever want to change the data we will have to regenerate them. Frankly we can do without the checksums with a direct connection to the KIM; there's unlikely to be any data corruption when it's sitting right next to us and it just becomes another source of data entry errors. Plus, if the KIM hangs because the checksums don't, it doesn't actually tell us where it stopped reading. That means you'll be rechecking the entire file anyway and the paper tape wall-of-hex format is hard to visually parse when you're checking for subtle mistakes.

At this point I had a forced lunch break because it ran out of paper while I messed around with some alternatives. Silent 700s can be fed with off-the-shelf thermal fax paper, but since fax machines are increasingly uncommon and many of them now use regular paper with inkjet or laser, thermal fax rolls are getting harder and harder to find. The little local office supply shop in the strip mall was closed for Labor Day weekend and the first Office Max I went to didn't have any. Eventually I tracked one down at another Office Max on the other end of town, where they had just a couple boxes on an out-of-the-way shelf. Newer thermal paper is much more "sensitive" at marking for the same level of voltage, so some adjustments were required:

The printhead intensity was set for its older original roll and on brand new paper it was far too dark and smudgy. There is a small potentiometer on the side of the machine you can get a jeweler's flathead into to adjust this. I turned the pot a touch counterclockwise to lighten it, but it was still smearing a bit, so I turned it just a little further and that made it nice and crisp. Plus, the lower voltage is probably better for the printhead anyway.

Back to work. If you looked at the catalogue in that last image, you will have noticed a new file I was working on called LOD700 ("Load-700"). It took a couple false starts to get it working right, but it does. This small program comes from this assembly source:

        * = $0102

getch   = $1e5a
getbyt  = $1f9d
pointl  = $fa
pointh  = $fb

        ; set the below to start address

sa      .word 0000

entry   lda sa
        sta pointl
        lda sa+1
        sta pointh
loop    ; skip characters until we get a semicolon
        ; this lets us have cr lf, etc.
        jsr getch
        cmp #";"
        bne loop
        ; get two hex characters, store in acc
        jsr getbyt
        ; write to memory
        ldy #0
        sta (pointl),y
        inc pointl
        bne loop
        inc pointh
        jmp loop

In essence Load-700 is just a very dumb hex loader. It waits for a delimiting semicolon, skipping any other characters, then reads two hexadecimal digits and writes them to memory, increments the memory pointer, and goes back for another semicolon (or until you hit RS to stop and reset it). It sits at the bottom of the hardware stack because many (but not all) KIM-1 programs avoid running there, and for good reason, since the 6502 doesn't have a lot of stack space and things can get stomped on. By doing so, however, that means it can load many (but not all) KIM-1 programs without them stomping on it. Almost all programs also avoid putting code at locations at $f8-$fb because the monitor uses those too, so we can use them for our zero page requirements. As a nice side effect, when we reset the KIM to halt it, the monitor will tell us the point where the hex loader stopped reading data, and the semicolon between hex bytes makes checking the transcript for errors less visually stressful.

This is short enough it could be keyed in manually, but it's critical enough we don't want any errors, so let's actually make this into paper tape. It is assembled with xa65 and turned into a punched tape image with kimtape, both of which I maintain:

% xa -o lod700.o lod700.s
% kimtape -adrs=0x102 lod700.o

Let's key it in and load it. It's 35 bytes and kimtape is more efficient than the KIM-1 is at generating paper tape data, so eight records is all we need ( ((2*67)+20)/18 is 8.555, but we know it will be smaller, so we needn't waste the extra record). I did make a couple typos, but you can correct those on the fly.

▹̶ CREATE LOD700 C 8 18


A quick COPY LOD700 TO PRINTER CTRL shows there are no lurking control characters and that the file is intact and error-free, and we are able to load it successfully from paper tape.

Let's try it out with a version of our HELLOO program, but this time we'll have the LEDs show 123456 to demonstrate we've successfully loaded something else. Load-700 format is very simple, a semicolon and two hex digits per byte. If we just multiply the bytes times 4 (in case we want to insert a new line), we'll have a good approximation of the expected size and can calculate from there. In this case five records are enough.




Since there is no address information in what we typed, we need to tell Load-700 where to start depositing data in memory. The 16-bit word at $0102 sets the starting address; for illustrative purposes we'll explicitly set that word to $0000 even though it already is by default. We will then naturally be at Load-700's entry point at $0104, so we'll start it with G and playback HELLO3.

0103 00 0102[space]
0102 00 00.
0103 00 00.
0104 AD G
When we press RS on the KIM's keypad and come back to the monitor, the monitor indicates that Load-700 stopped reading at $0012, the correct ending address. We page through a few bytes and it looks like it's good, so let's jump back to $0000 and execute it.

Let's go bigger. For that, we'll pick the game Bandit on page 34 from the First Book of KIM (by Jim Butterfield, Stan Ockers and Eric Rehnke), the basic book of all fun KIM-1 games and utilities. This is a simple slot machine game where you press any key on the keypad to start the "reels" spinning (middle set of three LEDs), and get paid to your "wallet" (two rightmost LEDs) if you win. It's easy to play, uses the LEDs cleverly and isn't especially long (209 bytes).

As convention I've started marking Load-700 format files by making the last character of the filename 7. With that in mind we'll create BANDI7, which at 209 bytes will require a worst case of 47 records (209 times 4 is 836, 836 divided by 18 is 46.444). Again, you'll notice I made a couple errors, but it was easy to back up and fix them.
I'm having a decent day at the pokies here (the machine advances you $25). Take my word for it that the animation is better than the "graphics."

However, because we have just 1KB of RAM in the unexpanded KIM-1, there will be programs that will want to use almost all of it and our bubble memory storage system should be able to handle those too. A great example is right there in the First Book and a classic game to boot: Hunt the Wumpus, on page 107. This is a rather dazzling implementation directly ported from the Creative Computing version. Although the map is necessarily simpler and it uses a non-moving "gas canister" instead of a flying arrow to hunt the beast, it faithfully has all the other aspects of gameplay (bats, pits, etc.) and actually displays scrolling text prompts on the KIM-1's LEDs.

Wumpus is in several discrete segments from $0000 to $003f, $0050 to $00bf, $0100 to $01a8, and $0200 to $03ff. Only the very top of the stack and the KIM monitor's zero page locations are unmolested. The section at $0200 is straightforward enough to enter. For the remainder, we can merge the $0000 and $0050 sections by just putting 16 dummy bytes at $0040, but we can't put anything from $00c0 to $0100 because we'll corrupt the KIM monitor's variables, we can't put anything from $01a9 to $01ff because we'll definitely stomp on any stack frames the monitor may be using, and worst of all the segment at $0100 is right on top of our hex loader.

The solution is to enter this in three separate files, one at $0200, one at $0120 (excluding the 32 bytes at $0100) and one at $0000. We'll call these WUMP27, WUMP17 and WUMP07, since they'll all be Load-700 format. For the section from $0100 to $011f which will destructively overwrite the hex loader, we'll hand-key it into the monitor directly as our last step. Since we won't be able to load the entire game in future with the hex loader, at the end we'll simply save those three sections as punched tape (but this time including everything starting from $0100) instead of Load-700 format, and the game can then be loaded that way.

Wouldn't you know it, but I got busted trying to skimp on the space for WUMP27, the biggest segment at $0200. Since the hex dump in First Book is broken into 16-byte lines I figured I could get away with 3 bytes per byte plus an EOL every 16th byte, and created the Load-700 file accordingly. I was typing merrily along when suddenly this happened:

Error 73 means out of space in the current file. Yikes! Where did it stop? Let's COPY WUMP27 TO PRINTER:
Despite the fact it let us enter that final line fully, it only stored a few characters of it. We can create a new bigger file and copy the old one over, but we'll need to remove those characters first before we append to the new file. Time to jump back into the editor. Since we ran out of space at $0300 halfway in, we'll just double what we used in WUMP27 for the new WUMP37.

▹̶ CREATE WUMP37 C 88 18


At the arrow prompt we will press FCTN-2 to search the file; we want to jump right to the last line. The string ;EA;EA;E appears nowhere else in that segment, so if we search for that, we will be in the right spot. The Find mode has a special diamond (◇) prompt. We type the search string (up to 30 characters) and press SKIP, and it will position the edit pointer at that location.

◇ ;EA;EA;E[SKIP] ◇

We press ←CHAR to back up the printhead to the semicolon just after the EOL. To tell the editor we actually want to delete those characters, we press FCTN-6 for each one to be removed. This prints a ▒ (shaded block) for every deleted character. I accidentally hit SKIP after doing so and inserted a spurious newline (which it redisplayed), so I had to remove that as well.

;ED;80;00☒;EA;EA;E[←CHAR x8, FCTN-6 x8]
          ▒▒▒▒▒▒▒▒☒ [oops]
;ED;80;00☒☒[←CHAR, FCTN-6]

I then pressed FCTN-8 to exit, to which it replied ONE MOMENT PLEASE and then DONE. A quick COPY WUMP27 TO PRINTER confirms we have a clean last line:

We then copy it over (COPY WUMP27 TO WUMP37), and then continue typing, directing the 763 to put future keystrokes at the end (COPY KEY TO WUMP37 END). I did make one running change which wouldn't have been possible if I were entering a checksummed paper tape image: the final instruction is a JSR, but it's right at $03fd, so if that subroutine ever returned we'd end up at $0400 and possibly crash. I changed it to a JMP, which when I checked the source code later was actually what was there in the first place, so I'm not sure why the hex dump was different.
It took over an hour to enter everything and another 10 minutes to check my typing. But hey, it's all on paper, so it was easy to review.
Playing back WUMP37 through Load-700.
And now saving the paper tape for that section, which we'll call WUMPU3 since it's no longer Load-700 format. I computed this would need 84 records.
Punched tape conversion of WUMP07 to WUMPU0 (31 records).
And finally loading the trailing section of WUMP17 through Load-700. It's time to hand-input the remaining 32 bytes.
You can see this got a little long and there were a couple errors, so I had to go back and verify what was actually written to memory. This is another reason Load-700 is easier to work with for projects of this length. I saved the whole section out to punched tape as WUMPU1 (28 records). Finally done.
Loading (as punched tape) WUMPU3, WUMPU0 and WUMPU1, in that order. Moment of truth! The game starts at $0305.
It runs! The opening prompt appears!
And, after I got dragged around by the superbat and smelled the Wumpus, I threw a gas can in his room and got a hug from the Wumpus on my first try! (The gas turns him into a lovable creature, you see. Suitable for kids.)

I consider this a, um, monstrous success. It took a bit to build the scaffolding but with just the KIM and the teletype we've proven we can construct any arbitrary program, even one using up almost all of the KIM's memory, and save it intact for later. With Load-700 as an aid we also cut down greatly on the overhead of typing and editing.

Ecologically, of course, the teletype is a disaster by modern standards. I went through yards and yards of paper working on this, not including my failed attempts, though I also intentionally printed a lot for the purposes of this post so you could see my work. In future when I load from files (either Load-700 or paper tape), before I start playback I'll press FCTN-0 to temporary turn off printer echo, start playback and wait for the playback light to go out, and then press FCTN-9 to turn the printer back on. That will at least cut back on a few lines of paper and wear-and-tear on the printer head.

Here's the final space tally, with Load-700, Bandit and both versions of Wumpus on board (I deleted the incomplete WUMP27 with DELETE WUMP27, as well as our HELLOO experiments). As it happens I was nearly dead-on predicting the required space for the punched tape versions of Wumpus and less so with the Load-700 files. Looking at actual space used, though, the Load-700 versions (excluding WUMP17) aren't greatly larger than their punched tape equivalents. We can recover the wasted space by creating a new, properly-sized file and COPYing the old file to it, but there's no urgent need to do so, because we still have 166 "80-char lines" left over. Hey, when you have just 1K of memory, 13KB free is luxury! A more important limitation is that the catalog is limited to just 16 files.

It should be possible with a more sophisticated loader to stream to and from the bubble memory device (and the host side can send playback control sequences to the teletype to select, open and operate on files automatically, which is all documented in the manuals). Theoretically, we could implement something like an on-board assembler or perhaps a primitive operating system even on the base 1K KIM this way, and a multi-loader system on the KIM side could mitigate the constraint on the number of files. I could also expand the system further with the larger-capacity modules from the defective 765s, but this 763 works well and I don't have a spare, and I'm gunshy enough to not want to risk letting the magic smoke out yet.

I should mention as conclusion why we're not using bubble memory today, and that's because we have ... flash. As both more typical DRAM and SRAM got cheaper and hard disks got larger and more affordable, the only market that bubble memory was still serving was those that needed a shockproof, solid-state solution (such as laptops; the GRiD Compass 1101 had 384K of it). When it first arrived in 1987 flash was already easier to work with and, as it got faster, cheaper and bigger, bubble memory just couldn't complete and faded away by the early 1990s. Although the related technology of racetrack memory had some experimental work in the 2000s and current magnetic technologies could make potential areal densities even greater, it's unlikely we'll see bubble memory returning in products anytime soon.

Anyway, there you have it: a period-correct mass-storage option for the KIM-1, as well as an interesting and exotic one. Now I'll admit there's nevertheless something mildly hinky about using a double-CPU teletype for this purpose (one of them a 16-bit CPU to boot) with over 12 times the memory of the single 8-bit CPU machine it's connected to, but everyone else out there is connecting all kinds of comparatively way overspec'ed ARM cores to 6502s and Z-80s and they're not even batting an eye. It just so happens that this overpowered unit is from 1977, so I win.

No comments:

Post a Comment

Comments are subject to moderation. Be nice.