I have come across the first real design flaw in my CPU. By design flaw I mean a real design flaw, with something functioning not exactly as designed, not an accidentally swapped wire or a simple bug in schematics. The good news is that it going to be fairly easy to fix. The bad news is that it postponed the moment of boot celebration.
Here is the story. In my attempt to run a real program for the first time, I burned the Fibonacci routine code to boot-ROM chip. The first run failed. I fixed yet another schematics bug causing the MSW register to work incorrectly (swapped mux wires), and repeated the attempt. Then, I noticed that on some occasions the MDR register failed to load. It just didn’t want to latch the value from ALUBUS, and stored zero instead. On other occasions it worked fine. I checked the schematics, but it was fine. Then, I beeped out all connections with a continuity probe, and they were correct, too. I tested MDR chips with a TTL tester, but no luck. Already somewhat worried I reached out for an oscilloscope to check if it was not a fanout issue, or noise. The signals were not ideal, but they were acceptable. At that point I felt like I have bumped into a wall and knew no way out. I looked at the logic analyzer listings again and again, checking all sort of signal related to MDR, until finally I have noticed a regularity. The MDR error seemed to occur only when MDR register was used in the last cycle of an instruction.
For the MDR register I am using four 74’173 chips, to store four bits of 16-bit MDR in each. In addition to that I am using four 74’244s to drive either left or right ALU operand bus. The design choice to use 74’173 instead of a pair of 8-bit registers was that the ‘173 has a reset pin. My microcode relies on the fact that MDR was cleared on instruction boundary to speed up some microcode instructions. Here is an example:
// ADD AH, (SP)
0, *, HI(MDR) <- MEM(SP) // MDR is cleared on instruction boundary, no need to touch low byte of MDR
1, *, HI(A) <- A + MDR; SETFLAGS_HIBYTE; fetch
Compare it to the instruction’s 16-bit counterpart:
// ADD A, (SP)
0, *, MAR <- SP
1, *, HI(MDR) <- MEM(MAR); MAR++
2, *, LO(MDR) <- MEM(MAR); MAR++
3, *, A <- A + MDR; SETFLAGS_WORD; fetch
That’s two cycles instead of four. Resetting MDR on instruction boundary allowed me to speed up 8-bit instructions by 50% compared to their 16-bit versions. Since there is no 8-bit register in the TTL family I know of that would have a synchronous reset input, I used four quad flip-flops. So, what’s the point, you might think. Well, there is a problem with the ‘synchronous’ thing. I made a wrong assumption that the 74’173s CLR input was synchronous. It is *NOT* synchronous. It is an asynchronous master reset pin that zeroes the register contents whenever it is becomes active (it is high active). As a result the MDR register was cleared in last cycle of every instruction, because the NEXTOP signal was already asserted, preparing the machine to fetch the next instruction on following positive clock edge. Faulty piece of design looks as follows:
The NEXTOP signal is just (roughly) an inverted LOAD_IR, active on last cycle of each instruction, as part of the fetch routine. It was supposed to synchronously reset MDR on instruction fetch. I don’t know why I was so dumb and what made me thing the CLR input was a synchronous reset. I even incorporated this concept to my simulator code. That’s why the machine passed all my previous extensive simulations and tests. Here’s my 74LS173 implementation, obviously wrong:
void LS173::go(long t)
l1 = Logic(LO);
l2 = Logic(LO);
l3 = Logic(LO);
l4 = Logic(LO);
else if (!input("g1_bar") & !input("g2_bar"))
l1 = input("d1");
l2 = input("d2");
l3 = input("d3");
l4 = input("d4");
if (input("m_bar") | input("n_bar"))
The second nested “if” block is completely wrong. That’s not how 74’173s behave. Here is a real truth table for the device:
|CLR||CLK||Data enable G||Data D||Output Q|
Output is pulled low when CLR is active high, regardless of the clock pulses. In order to fix the problem I needed to find a TTL register device that has a synchronous clear, or give up the nice feature of instruction boundary MDR reset. The former seems a bit tricky, since there is no such device is 74-series family that would be easily available. There is a a 74’575 which would be an ideal fit (it is an octal D-flip-flop with synchronous reset and three-states output) but it is not available anywhere. Instead, probably I am going to use four 74’163 binary counters. They are presetable (may act as registers), and have a synchronous reset input. I will inhibit the count signal, and it should work. I know it is not the primary use for this chip but I have no better idea at the moment. Instead, I would have to add one extra clock cycle to every 8-bit arithmetic and logic instruction, making them only 25% faster that 16-bit versions. I think saving this one cycle is worth the rework effort I will have to face.
Meanwhile, since I don’t have 74’163 parts at hand anyway, I will temporarily change the microcode to explicitly clear MDR for 8-bit instructions, and cut off the CLR signal from 74’173 chips. This is just a workaround (and in fact the plan B I was considering) but at least I will be able to move forward. Besides, I already have one workaround in my ALU waiting to be fixed (the missing carry lookahead generator). After all, temporary workarounds are not a bad thing when prototyping. So, I will order the parts but but I really hope to be able to boot successfully before they even arrive.