Buckle up, this one gets deep into the weeds.
I've got the schematic in hand, close as I can tell. And I need to figure out how I'm going to identify what is wrong with this card. It won't boot my hacked-together ProFile emulator card: why? Is it a problem in my hardware implementation? I know the interface card wouldn't boot a genuine ProFile 10, so there's something wrong there - how do I find it, or know when it has been found and fixed?
With the ProFile itself this would be easier: use the known-good ProFile and debug away. But without one I'm fighting two simultaneous problems. If I want to solve this system, I'm going to have to know that when the Apple does , the ProFile should do , which means that the card did - and then find cases that violate those conditions, reverse engineer what failure on the card would cause that situation, and finally attempt some repairs.
My schematic tells me how everything is connected, but not what the card is thinking.
So I thought: I should start with the ROM. A disassembly of it will at least expose how the machine is supposed to use the drive; and from that, maybe I can build a reproducible test for any single part of the functionality. So here we go: 2048 bytes of code -
$C800 STR '(C) APPLE COMPUTER INC. 1984' $C81C LC81C PHP $C81D SEI $C81E JSR LC829 $C821 BCC LC826 $C823 PLP $C824 SEC $C825 RTS $C826 LC826 PLP $C827 CLC $C828 RTS $C829 LC829 STA $3F $C82B LC82B LDA #$00 $C82D STA $3E $C82F LDA $C082,X $C832 LSR A $C833 BCC LC838 $C835 LDA #$28 $C837 RTS $C838 LC838 LDY $42 ...
... and so on, for a good while. It's data, to be sure, but we need it to be information.
Some of those entry points are known - ProDOS uses this as a standard block drive device, so we can annotate those entry points and generate a call flow graph to try to understand what it's actually doing and why:
$C800 STR '(C) APPLE COMPUTER INC. 1984' $C81C DRIVER PHP $C81D SEI $C81E JSR DRV_DISPATCH $C821 BCC RET_OK $C823 PLP $C824 SEC $C825 RTS $C826 RET_OK PLP $C827 CLC $C828 RTS $C829 DRV_DISPATCH STA $3F $C82B LC82B LDA #$00 $C82D STA $3E $C82F LDA CARD_STAT,X $C832 LSR A $C833 BCC LC838 $C835 LDA #$28 $C837 RTS $C838 LC838 LDY CMD ...
Nicer to read, but still difficult to interpret. Maybe the call flow will help:

Okay, maybe that's something, but it's clearly not enough.
The ROM dump gives us useful information about command behavior and timing. It corroborates things we already know (block $FFFFFF is informational, block $FFFFFE is the hard drive's scratch RAM). So it's possible to gin up a test like this -
Apple //e DB-25 ProFile Teensy emulator +-----------------+ slot 5 +--------+ (stands in for the drive) | 6502 + card ROM |==============>| card |========> logs every byte, every | 341-0299-B | $C0D0-$C0D3 | 820- | handshake, and can be | probe ML at |<==============| 5006-B |<======== told to assert or wait | $0300 / MLI | +--------+ for a specific state +-----------------+
Testing was slow - boot the computer, watch the Teensy emulator, and do a PR#5 - the Apple should try to read blocks, and it does - `00 FF FF FF 0A 03` is "READ $FFFFFF" (10 retries, spare threshold 3). Then what I see after is `02 AA` - the drive enters command mode with 02, and then the card NAKs it with AA. What I should see is `02 55`. Hmm... what does that routine look like?
$CB80 LDA #$03 / STA $06F8 ; progress marker = phase 1 (loop top) $CB85 LDA #$01 / JSR $CDAC ; (1) COMMAND handshake, expect $01 $CB88 BCC $CB9F ; carry clear = completed $CB9F BNE $CB80 ; A != 0 (NAK) -> retry; A==0 (ACK) -> on $CBA1 JSR $CE4A ; (2) send the 6-byte frame 00 FF FF FF 0A 03 $CBA4 BCS $CC21 ; (frame error) $CBA6 LDA #$0A / STA $06F8 ; progress marker = second confirm $CBAB LDA #$02 / JSR $CDAC ; (3) CONFIRM handshake, expect $02 <-- here $CBB0 BCS $CB8C ; carry set = timeout -> error path $CBB2 BNE $CB80 ; A != 0 = NAK -> RETRY from top $CBB4 JSR $CE7D ; ACK'd: read 4 status/header bytes...
Well, that looks correct. We can see the confirm attempt at step (3), with a carry check after JSR $CDAC:
$CDAC PHA ; save expected ($02)
$CDAD JSR $CE16 ; wait for /PBSY ready (timeout -> carry
set)
$CDB2 LDA ($3E),Y x3 ; read $C505,$C507,$C504 soft switches:
; set READ direction, release the '374,
assert /PCMD
$CDC3 LDA $C082,X / BPL $CDE0 ; poll status bit7 until the byte is ready
$CDE0 LDA #$00 / STA $3E / PLA ; restore expected ($02)
$CDE5 CMP $C081,X ; <== compare $02 against the byte on the
read node
$CDE8 BNE $CDF2 ; mismatch -> NAK path
; --- match path (not taken on the fault) ---
$CDEA LDA #$55 / JSR $CEB8 ; ACK: send $55, return A=0 (success)
; --- NAK path (taken) ---
$CDF2 LDA #$02 / ORA $0778 / STA $0778 ; set error bit 1 ($CDAC mismatch)
in the screen hole
$CDFA ORA $04B8,Y / STA $04B8,Y ; paint it into the status hole too
$CE02 LDA #$AA / JSR $CEB8 ; <== EMIT $AA on the wire (JSR $CEB8 =
STA $C080,X)
$CE07 ... ; return with A != 0 / carry set (= NAK
to the caller)
So the card should only NAK if the byte on the wire (02) isn't $02 (at $CBAB).
If the drive believes it was successful, and we see success on the wire, and the code is checking for the exact success condition we are seeing, why are we seeing a failure? Something must be wrong on the wire. Maybe it's timing of what we're reading (this isn't a real ProFile drive, after all); or maybe we're close to finding something wrong in the card's logic chips somewhere.
We'll cut it in half again: if we drive a constant value onto the bus from the emulator (say, $02), and then read it back from the computer side, then we have no timing issue to contest with. Can we read a value as put on the wire?
Well, $C0D1 (as long as the card is in slot 5) is the data-in read register. We just need a little magic ritual to read it. Johnny Carson did it with a sealed envelope he'd hold to his head as Carnac the Magnificent: "$20.20." Rips it open and reads: "What will a gallon of gas cost by next year." He's got the answer and has to understand the question, which is pretty much where we are, with a slightly more technical ritual. First we have to understand how this card should work so we can build the ritual to find the question.
The ProFile card doesn't just magically put the bus in $C0D1 when you read it. It has no idea when you're reading that memory location. We're talking about a dance between four chips on the card: a '374, '244, '259, and '279.
The 374 is the data latch that holds our value outbound, what we write to the bus. It's paired with a '244 that reads data back from the bus. The '374 drives the bus only while it is enabled, and the '259 holds the one bit that decides that - flip it one way and the '374 is writing such that the '244 can only see the '374's value; and the other way the '244 can see what the drive is actually saying.
To get to that point we also need to describe a couple other chips that decode the address lines. This is the whole data flow:

which means that our ritual to read has to be:
- unlock the '279 direction latch (Y3 -> pin 10)
- Drive the '259 Q3=0 to enable the '374
- Clock $FF into the 374 as a baseline test
- Read what's on the '244 right now (should be $FF)
- Release the '259 Q3=1 to decouple the '374 from the bus
- Read the '244 to see that the bus is now released, should read our target data from the bus
- Re-assert the '259 Q3=0 for confirmation
- Read the '244 to see that we are now seeing our $FF driven value again
This is what we want to see:
] CALL -151 * C0D3: 00 * C503 C503- xx * C0D0: FF * C0D1 C0D1- FF * C507 C507- xx * C0D1 C0D1- A5 * C503 C503- xx * C0D1 C0D1- FF
The xx are inconsequential reads of soft switches which are passed through as the switching signals to the card. It's really the reads to C0D1 that we expect: in this sequence, we're driving $A5 on the bus from the emulator, so we should read FF / A5 / FF (as written). Which is indeed what we saw. Drive a $02 from the emulator, we read FF / 02 / FF.
And that, my friends, is the sanitized version. It didn't go nearly that smoothly. The first time, I read back 03 instead of FF. PD2 through PD7 were being dragged low, repeatably, though it wandered with how the card was seated. That's not logic or code; it's a copper failure somewhere.
The 74LS38 was stuck low. This is an open-collector NAND gate in the data-read decode path, sitting on the data-in lines. The IC was fried; I replaced it, and started reading the FF that I expected. And yet the card still NAKed; this was not the only failure.
At this point the ritual passes as written; FF / A5 / FF as expected. The 374 lets go when told to, the 244 can read properly, and I can switch back and forth between the two.
Which is the worst possible answer, because it means the new failure isn't in the copper. It's dynamic. The same switch that flips perfectly when I single-step it from the monitor is not landing when the ROM fires it in the middle of a live transaction, with real bus timing, a real /IOSEL window and a real PH1 edge. The suspects become the stateful parts: the '279 lock, the 74LS109 flip-flops, and the RC networks that shape the strobe. At this point I have little choice: what I really need is to know what's on some of the pins, live, while the machine's running.
But that's my last resort. The last thing I want to do is blow out the card or //e logic board due to a stray popped-off clip during debugging. So I started with a looong continuity campaign: beeping every net against my as-documented circuit map. And I found something interesting: with the ROM out of its socket, pin 18 (the 2716's /CE) had no continuity to the '279 that drives it. The ROM had been running on luck, or fumes, or capacitive coupling, who knows. I'd read the ROM fully and correctly, twice, and missed it.
Now there's a choice to make. The ROM is in a socket, which is great for rescuing the part; but the socket is soldered on to the PCB and would require some surgery to replace. But maybe we don't have to: the reason it's failed is almost certainly fatigue on one of the socket holes - a flat-pinned leaf-contact socket operates by spring force and, with a fatigued leaf, won't hold true against the IC's leg. But a wider piece of metal might work fine. A modern, machine pin socket has exactly this sort of profile and, for the cost of rising the ROM up one IC higher, gives us back the continuity that was lost. At least for now. Also sobering: every dynamic observation before that moment had been made on a card whose ROM was only mostly enabled, so all of it was suspect.
And yet... it still NAKs. Two problems found and fixed, and we're still seeing exactly the same failure we started with.
In retrospect, the ritual lost me some time here. I was mostly testing with $A5 driven, which has bit 0 set, which left me blind to the next domino in our chain.
So I built a bigger hammer. Serial console into the //e, the emulator driven from a script, and a new emulator knob that answers the confirm with any byte I choose. Then sweep it, in the real boot path. The result was strange and, briefly,
thrilling: the card ACKed the confirm only when the emulator answered $01, three times out of three, and NAKed $00, $02, $03, $41 and $81. The identical
LDA #$02 / JSR $CDAC
sequence, copied into RAM and executed from there, ACKed $02 exactly like the source says it should. Same compare, same drive, same card; the only variable was where the instructions were fetched from. Conclusion, written
down with great confidence: the 2716 is delivering a wrong byte under live timing. Maybe the ROM is sick and needs to be replaced. Replacements can be found, that's not a problem, but I have none in stock. So, after ordering a few replacements for a permanent solution, I dug deeper.
Spoiler: the ROM was fine.
The next morning an in-situ dump of the ROM at live bus timing came back byte-perfect. What the sweep had actually found was a trap in the ROM itself: when a confirm mismatches, $CDAC's error path re-handshakes expecting $01, the phase-one byte. So a sweep that answers $01 gets "accepted" because it matched the error path's retry, not the confirm. I had measured the failure mode and called it success.
Two things narrowed the gap.
The first was the 74LS109. This was new stock from 2021 that had seemingly died: on close measurement I found about 70 ohms to ground on multiple input pins in both polarities, where a healthy input reads one-way. Its FF1 is the card's parity-error latch, and a leaky FF1 behaves exactly the way this card had: statically clean, then set by anything, drifting between good and bad with handling and heat, probably degrading progressively. But somehow the original 1981 chip's inputs measured healthy, so it went back in. With a 100 pF cap from its preset pin to ground soldered at the socket as glitch insurance.
The second wasn't on the card at all. All the manhandling of my emulator board eventually snapped the BSY wire between the Teensy and the DB-25. The card's status bit 7 stopped tracking BSY, so the ROM's response-wait loop timed out. The Teensy could prove it was driving the pin, by reading back its own pad, while the card never saw it.
And finally, grudgingly, the //e read some blocks from the emulator and promptly crashed. Because the image I was using - the original 2021 artifact - is indeed not even bootable. It has its own corruption issues, and I suspect it was taken out of service specifically because the contents were somewhat corrupt. The filesystem looks intact enough but a few hours of side work showed a corrupted boot sector and some parts of the catalog hierarchy that were internally inconsistent.
Just look at the shape of all that. The card had two faults of its own: the '38 and the open /CE leg, where the NAK survived both repairs. Another part I replaced that didn't need it, I'd also broken it of my own testing; and the value sweep test that felt conclusive had confidently implicated an innocent chip. Many theories along the way were 100% reasonable. And wrong. Including the assumption that this hard drive image should boot cleanly when everything is working.
Which brings us to these very valuable reads - after getting to this point, I'd amassed a lot of behavioral analysis of the ROM and could build full flow diagrams of how the ROM behaves. Which becomes really important in the next entry...







Jorj Bauer
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.