I found the "last" bug in my Verilog implementation of the SAP-1 computer. It looks like I did a cut-and-paste of the instantiation of the 74LS83 adder for the high-order 4 bits (C16) to create the instantiation of the adder for the low-order 4 bits (C17). I then connected the carry-in and carry-out signals correctly for both parts, but I forgot to change the bit numbers for C17. Thus the low-order bits weren't being driven.
Fixing this allows the SAP-1 to execute all 6 instructions of its program and produce the correct output on the emulated LEDs.
As satisfying as this is, it is not the purpose of recreating the SAP-1. The purpose is to help me understand more clearly how a computer fetches, decodes, and executes instructions.
When I entered university as an undergraduate student, the Assembly Language class was taught using the big mainframe computer shared by most of the universities in the state. This was a Univac 1100 series system, a 36-bit, word-addressable computer that performed one's complement math and used a 6-bit character set known as FIELDATA. Since everything about this computer was so very different than the 8-bit, byte addressable, two's complement, ASCII-speaking 8080 and Z80 microcomputers that were beginning to become very popular, the academic staff felt it would be more valuable to their students if they taught 8080 assembly language instead.
With only one Z80-based computer available and 25 to 30 students in the class, however there was no way they could try their code on a real microcomputer. Instead, projects were coded on punch cards and assembled using an 8080 assember a university staff employee had written (in COBOL, no
less!). Once they assembled cleanly, they were turned in untested and the teacher graded them on whether he thought they'd run. Clearly this was not a practical way to teach a class.
For my Univac Assembly Language class project I had proposed writing an 8080 interpreter in Univac assembly language. This I mostly got working, but another student wrote an 8080 interpreter in Pascal which was more maintainable. At the same time a professor wrote a series of macros for the Univac assembler that allowed it to assemble much of the Z80 assembly language and generate Z80 machine code. These were then turned over to me. Over the next few semesters I completed the support for the full Z80 assembly language in the assembler, added support for the missing Z80 instructions in the interpreter, and even added a programmable timer "ROM call" so the operating systems class could time-slice multiple tasks in response to an interrupt. Suffice it to say that I am intimately familiar with the Z80 microprocessor.
However... that does not mean that I fully learned how a real CPU fetches an instruction, decodes it, and performs the many steps needed to execute it. Working on the 4004 emulator and discrete recreation has given me some insight into this process, but the 4004 schematic does not label the signals in a way that is helpful to understanding its operation.
Let's take a simple example. The program counter in the 4004 is a 4x12-bit DRAM array. One of the signals driving the program counter is labeled "INH(X11+X31)Φ1". While this accurately describes how the signal is derived, it does nothing to explain the purpose of the signal. It's only through analysis of the workings of the DRAM array that I was able to determine that this signal precharges all the column sense lines to ones in preparation for reading a DRAM row. I really haven't learned the purpose of most of the signals that come out of the 4004's instruction decoder.
The benefit of working with the SAP-1 is that all of the signals are labeled sanely and their purposes explained in great detail. Even the process of chasing down the error in the adder aided in my understanding as I walked through each of the phases of execution in this computer. In the simulator I can see which signals are asserted in each phase, which outputs drive the internal "W" bus, and which inputs latch the contents of the bus at what time. This helps me understand the purpose of signals in the 4004 like "POC•IO•Φ1+(IO+POC)•X22•Φ2", which gates the internal data bus onto the ALU inputs.