Commodore PET/Reparatur
Info
An English translation of this article is available here: https://s3lph.me/restoring-a-commodore-cbm-3016.html
Wir sind kürzlich in den Besitz eines Commodore PET/CBM 3016 gekommen, Baujahr zwischen 1978 und 1980. Beim ersten Anschalten wurden wir allerdings von einem Bildschirm voll Zufallsdaten begrüsst:
Eine kurze Internetsuche nach diesem Problem hat ergeben, dass die häufigste Ursache für dieses Problem defekte ROM-Chips sind.
Die Memory- und ROM-Map
Die in der PET- / CBM-Reihe verwendete MOS 6502-CPU hat einen 8-Bit Datenbus und 16-Bit Adressbus.
Gemäss Schaltplänen des PET, die online zu finden sind, werden die obersten 4 dieser 16 Bit verwendet, um zu entscheiden, mit welchem Gerät die CPU kommuniziert:
Die gesamte untere Hälfte des Adressraums ($0000:$7fff
) ist dem DRAM zugeordnet. Da unser Modell (3016) aber nur mit 16KiB RAM ausgestattet ist, wird nur der Adressbereich $0000:$3fff
tatsächlich verwendet. Darauf folgt der Videopuffer im Bereich $8000:$8fff
, und darauf mehrere 4KiB ROM-Chips von $9000:9fff
bis $f000:$fff
. Auch hier wird nur ein Teil des Adressbereichs verwendet; der Bereich $9000:$bfff
ist leeren DIP-Sockeln zugeordnet, in die durch die Benutzer*in Software-ROMs hinzugefügt werden können.
Das Betriebssystem des PET ist in 4 ROMs abgelegt:
$c000:$cfff
: BASIC-Editor/-Interpreter/-Funktionen, Teil 1$d000:$dfff
: BASIC-Editor/-Interpreter/-Funktionen, Teil 2$e000:$e7ff
: Input/Output-Verarbeitung. Die oberen 2KiB ($e800:$efff
) sind direkt mit der Peripherie (z.B. der Tastatur) verbunden.$f000:$ffff
: Der KERNAL ("Kernel", aber mit einem Schreibfehler während der Produktentwicklung).
Zudem gibt es noch ein weiteres 2KiB-ROM, auf welchem der Zeichensatz gespeichert ist, der auf dem Bildschirm angezeigt werden kann. Dieses ROM ist allerdings nicht über den Adressbus der MOS 6502 adressierbar, sondern wird direkt von der Hardware verwendet, die sich um das Rendern des Bildschirms kümmert.
Wir haben alle ROMs aus dem PET entfernt und mit unserem MiniPRO TL866A programmer ausgelesen:
minipro -yp 27C32A@DIP24 -r basic1.bin minipro -yp 27C32A@DIP24 -r basic2.bin minipro -yp 27C32A@DIP24 -r kernal.bin minipro -yp M2716@DIP24 -r io.bin minipro -yp M2716@DIP24 -r chars.bin
Über die Lebensdauer der PET-Reihe wurden verschiedene Versionen von KERNAL+BASIC in den PETs verbaut. Wir wussten daher nicht, wie unsere ROM-Dumps eigentlich aussehen sollten. Glücklicherweise haben wir Dumps der meisten dieser Versionen online gefunden, und nur eine dieser Versionen stimmt mit unseren Dumps überein: Die Version BASIC 2.0.
Zumindest die meisten Dumps stimmen überein. Der Inhalt des IO-ROMs und des ersten BASIC-ROMs hatte nicht annährend Ähnlichkeit mit den Online-Dumps. Nachdem wir dieses ROMs erneut ausgelesen hatten, bekamen wir komplett andere Daten zurück. Nach mehrmaliger Wiederholung stellte sich heraus, dass diese beiden ROMs komplett defekt waren und weitestgehend zufällige Daten lieferten.
Erstellung neuer ROMs
Wir haben ein paar pinout-kompatible EPROMs gefunden, die wir als Ersatz verwenden wollten. Allerdings haben wir nach dem Löschen mit unserem UV-Erase festgestellt, dass unser Programmer nicht in der Lage ist, die Programmierspannung von 25V zu liefern. Wir haben zwar versucht, die Programmierspannung mit einer externen Stromversorgung zu liefern, und hatten beschränkten Erfolg (heisst: wir konnten einzelne Bits schreiben), aber konnten nie einen ganzen Schreibzyklus abschliesen.
Daher haben wir uns nach Alternativen umgeschaut und mehrere Rails einmalig programmierbarer AT27C040-PROMs im PLCC-Formfaktor gefunden. Diese PROMs verfügen über ganze 512 KiB Speicher. Unser Plan war, die untersten 2 resp. 4 KiB zu verwenden, und die nicht verwendeten Adresspins auf GND zu verdrahten. Wir haben ein Adapter-PCB von PLCC32 zu DIP24 entworfen, das sowohl das 2KiB- als auch das 4KiB-Pinout unterstützt.
Mit der Lötbrücke im Schaltplan kann zwischen der 2K- und 4K- Version gewählt werden; in der 4K-Ausführung ist der OE-Pin («Output Enable») dauerhaft auf logisch 0 gezugen, in der 2K-Ausführung, ist er mit A11 (dem 12. Adressbit) verbunden: Wenn dieses Bit 1 ist, wird der Ausgang deaktiviert und auf hochohmig gesetzt, um die Kommunikation zwischen CPU und Peripherie nicht zu stören, die in der oberen Hälfte des $e000:$efff
-Adressraums anliegt.
Schlussendlich haben wir aber (zumindest anfangs) eine unschöne, aber einfachere Lösung gewählt: Anstatt PCBs zu produzieren, haben wir die PROMs mit kurzen Drähten an DIP-Sockel gelötet und. Nachdem wir diese Ersatz-ROMs in die Sockel des PETs eingesetzt hatten, konnten wir zumindest einen kleinen Fortschritt beoubachten: Der Bildschirm war immer noch mit zufälligen Zeichen gefüllt, aber ein paar Sekunden nach dem Start wurde der gesamte Bildschirminhalt gelöscht, danach passierte allerdings nichts mehr. Es scheint, als würde die CPU irgendetwas tun, aber nicht das richtige.
Suche nach CPU-Fehlern
Um herauszufinden, was die CPU tatsächlich tat, haben wir einen Logic Analyzer an den Adressbus angeschlossen und jede Adresse aufgezeichnet, die zwischen dem Start und dem Leeren des Bildschirms am Bus angelegt war. Eine Sache, die sofort offensichtlich wurde, war, dass auf dem Adressbus keinerlei Aktivität mehr stattfand, nachdem der Bildschirm geleert wurde. Da die MOS 6502 CPU im Betrieb mindestens alle 1-6 Zyklen eine Instruktion aus dem ROM lädt, schien die CPU komplett angehalten zu haben. Dies tritt üblicherweise nur auf, wenn eine ungültige HCF-Instruktion ausgeführt wird.
Daher haben wir uns genauer angeschaut, welche Instrukionen die CPU direkt vor dem Einfrieren geladen hatte. Wir haben den Disassember dxa64 verwendet, um die ROM-Dumps in 6502-Assembly zu übersetzen und zu verstehen, welche Instruktionen an welchen Adressen stehen.
Und tatsächlich stellte sich heraus, dass die zuletzt von Adresse $f4e0
geladene und ausgeführte Instruktion, $d2
, eine ungültige Instruktion ist, die die CPU anhält.
Gemäss dem Disassembly befindet sich an dieser Adresse allerdings gar keine Instruktion, sondern eine Zeropage-Adresse, die als Argument einer vorigen Instruktion verwendet wird.
Ein paar Zyklen vorher konnten wir etwas Vielversprechendes finden: Eine rts
-Instruktion (Return from Subroutine) wurde geladen, gefolgt von zwei Zugriffen aus die Stack-Page ($01fe:$01ff
): Die Rücksprung-Adresse.
Danach wurde die nächste Instruktion von der Adresse $f4d8
geladen.
Das Seltsame hieran war, dass ein rts
immer nur an eine Adresse springen sollte, vor der eine dazugehörige jsr
-Instruktion (Jump to Subroutine) steht. Diese war hier jedoc nicht vorhanden.
Nun war klar, dass die CPU zu einer falschen Adresse springt, was defekten RAM nahelegt.
Um mehr darüber herauszufinden, haben wir in der Aufzeichnung des Logic Analyzers noch weiter zurückgesucht, und eine passende jsr
-Instruktion an Adresse $fcd5
gefunden.
Gemäss Spezifikation der jsr
-Instruktion müsste die Adresse $fcd7
auf den Stack gseschrieben werden (Adresse der jsr
-Instruktion + 2).
Vergleicht man diese mit der Adresse, die tatsächlich vom Stack zurückgelesen wurde, $f6d7
(Returnadresse - 1), fällt auf, dass sich diese Adressen in genau einem Bit unterscheiden, was den Verdacht auf fehlerhaften RAM weiter erhärtet:
$fcd7 = 1111 1100 1101 0111 $f4d7 = 1111 0100 1101 0111 ^
Suche nach defektem RAM
Um das fehlerhafte RAM-Modul zu finden, mussten wir erst das Layout der RAM-Chips verstehen.
Der DRAM des CBM 3016 besteht aus 16 MOSTEK MK4108-Modulen im DIP16-Formfaktor, organisiert in 2 Gruppen von 8 Chips. Gemäss dem Datenblatt des MK4108/4116 (Seiten 140ff) speichert jeder Chip 8192 einzelne Bits. Mithilfe eines Multimeters haben wir das folgende Memory-Layout herausgefunden:
D0 D1 D2 D3 D4 D5 D6 D7 __ __ __ __ __ __ __ __ | | | | | | | | | | | | | | | | | | | | | | |\/| | | | | | | | | $0000:$1fff | | | | | | |/\| | | | | | | | | |__| |__| |__| |__| |__| |__| |__| |__| __ __ __ __ __ __ __ __ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | $2000:$3fff | | | | | | | | | | | | | | | | |__| |__| |__| |__| |__| |__| |__| |__| VVVV Vorderseite des PET VVVV
Da das fehlerhafte Bit an der 4. Position war (vom least-significant bit des oberen Bytes aus gezählt), und der CPU-Stack (von wo aus die falsche Adresse gelesen wurde) im Adressbereich $0100:$01ff
liegt, müsste der fehlerhafte Chip das D3-Modul in der hinteren Reihe sein, oben mit einem X markiert. Um dies zu bestätigen, haben wir den Chip mit dem D3-Chip der vorderen Reihe getauscht - zum Glück sind diese nicht fest verlötet. Und tatsächlich startete der PET und zeigte den BASIC-Interpreter. Es wurden aber statt der erwarteten 15K freiem RAM nur 7K angegeben:
Now that the system had at least some working memory, I decided to write a small memtest program. I ended up writing this directly in 6502 assembly, written to a ROM plugged into the `$f000:$ffff` socket normally occupied by the KERNAL ROM, due to multiple reasons:
- The KERNAL actively uses the memory region `$0000:$2000`, so loading the memtest program from another location after the KERNAL has been loaded would corrupt this memory region.
- An entire memory page is occupied by the stack, but this page should be tested as well. Writing in assembly allowed me to avoid implicitly using the stack, or any other implicit memory allocations.
The memtest program only uses 7 bytes of zeropage memory to keep its state, the entire remainder of the memory is tested. For more efficient testing (and to not waste tons of single-write PROMs) I also ended up wrapping a MOS 6502 emulator with the CBM 3016's memory map and simulated memory failures. The memtest program can be found on [Gitlab][cbm3001-memtest].
Flashing the memtest16k image to a PROM and booting it on our CBM 3016 revealed memory errors for every single address in the upper half of the DRAM address space, `$2000:$3fff`. Most of these were indeed caused by the faulty memory module we had already identified earlier, however, there have also been some additional reports caused by a different module, as they had happened at a different bit.
Ersatz des defekten RAMs
We assumed it would be quite hard to find compatible memory modules. However, in our hackerspace I found a Commodore C64 which had already been scavenged for spare parts, so there were no hard feelings in taking its memory as well. The C64 has 8 memory modules, each holding 64Kib. These modules, [Micron MT4264-15][mt4264] were *mostly* pin-compatible to the MK4108/4116; they have an additional address pin, but fewer power pins (they run on 5V only, rather than the MK4108's triplet of -5V, +5V and +12V). Making an adapter socket was trivial, the hardest part was desoldering the DIP24 package from the C64 PCB; instead of using DIP sockets like in the PET/CBM series, the C64 has its RAM soldered directly to the PCB.
Once we had replaced the one RAM module that was entirely broken, we started another memtest run, and no errors were reported at all. This came as a surprise, since based on the results from previous memtests we assumed two modules had to be faulty. We then reinserted the KERNAL ROM, and finally the CBM 3016 started up with its full memory size available:
[cbm]: https://www.c64-wiki.com/wiki/PET_2001
[6502]: https://en.wikipedia.org/wiki/MOS_Technology_6502
[zimmersroms]: http://www.zimmers.net/anonftp/pub/cbm/firmware/computers/pet/
[at27c040]: https://ww1.microchip.com/downloads/en/DeviceDoc/doc0189.pdf
[hcf]: https://en.wikipedia.org/wiki/Halt_and_Catch_Fire_(computing)
[dxa65]: https://www.floodgap.com/retrotech/xa/#dxa
[mos6502jam]: https://www.masswerk.at/6502/6502_instruction_set.html#JAM
[mos6502rts]: https://www.masswerk.at/6502/6502_instruction_set.html#RTS
[mos6502jsr]: https://www.masswerk.at/6502/6502_instruction_set.html#JSR
[mk4108]: https://usermanual.wiki/Document/1979MostekMicrocomputerProductsDataBook.313031759/view
[cbm3001-memtest]: https://gitlab.com/s3lph/cbm3001-memtest
[mt4264]: https://www.datasheets360.com/pdf/-1862440600650638492