It’s been a few months since I’ve posted, but I’ve been making steady progress. I’ve mostly been heads down capturing schematics and testing/debugging. I’m up to 14 modules integrated (1-12 and 14-15; module 13 is Alarms and it would probably be quite unhappy that half of the modules are still gone!). While this is technically only halfway done; it represents a pretty major milestone: all of the central logic is completed! All remaining modules now are I/O focused. This means that in theory the computer should be capable of executing most programs, so long as they don’t rely on any I/O or interrupts.
In addition to the above, I’ve got a few other exciting developments that I think deserve their own posts. There’s a couple of things I want to cover before I get there, so stay tuned!
For now, I want to give a brief overview of the build system I’ve arrived at. It’s far from perfect, but it accomplishes its goals reasonably well.
Each page of the module gets its own dedicated hierarchical sheet. The contents of these sheets match pretty closely with the original sheets; for the most part they only differ if I’ve opted to move related logic to the same place (logic for a single function was often split up among multiple modules in order to not exceed chip or backplane pin limits for any given module). Pin I/O for these sheets is also very close to the originals. Intramodule nets that run between sheets are drawn explicitly, while intermodule nets are given global names.
The top-level sheet also holds the backplane connector. I’ve opted to keep the backplane as clean as possible; new modules are initially introduced into the system with only input pins on their connectors. I then go through existing modules and assign any nets that drive inputs on the new module an output pin on the containing module(s)’s backplane connector(s). Similarly, inputs on older modules that have remained undriven that are produced by the new module are added to the new module’s connector. This minimizes both the number of pins used on each module’s connector as well as the number of nets running across the backplane, which is going to be populated enough as it is with just the nets that are actually used.
The hierarchical sheet contents also all look mostly the same:
Components are numbered with the scheme ((Module number x 1000) + part number). Each part can also be tagged with code generation fields; the most useful and by far the most common of these is the initial condition flag.
The component selection has surprisingly remained unchanged since I started out, and is as follows:
- 74HC04 — 1-input NOR gates (NOT gates)
- 74HC02 — 2-input NOR gates
- 74HC27 — 3-input NOR gates
- 74HC4002 — 4-input NOR gates
- 74LVC07 — Open drain buffers
And that’s pretty much it! Aside from passives and whatnot. The open-drain buffers allow me to create NOR gates with a higher fan-in than 4, as well as buses that are driven by multiple modules. While they’re not technically NOR gates, they’re not inaccurate; as I mentioned in Interpreting the Schematics, the NOR gates used by MIT were effectively all open-collector. There aren’t a whole lot of open-drain NOR gate chips out there, so it is much easier to just use regular NOR gates and slap open-drain buffers on when necessary.
Conversion to Verilog for simulation starts in the KiCAD netlist exporter window. KiCAD allows you to add new tabs for custom exporters, and so I’ve done just that:
When “Generate” is clicked with the AGC Verilog tab selected, KiCAD exports a generic XML netlist, and then invokes the netlist command (in this case, the Verilog generator). Upon completion, a little popup indicates success.
The generated Verilog looks a bit like this:
// Module declaration (the module I/O is everything on the backplane // connector, plus VCC, GND, and the simulation SIM_RST (sim reset) module parity_s_register(VCC, GND, SIM_RST, GOJAM, PHS4_n, T02_n, T07_n, T12A, TPARG_n, TSUDO_n, FUTEXT, CGG, CSG, WEDOPG_n, WSG_n, G01, G02, G03, G04, G05, G06, G07, G08, G09, G10, G11, G12, G13, G14, G15, G16, WL01_n, WL02_n, WL03_n, WL04_n, WL05_n, WL06_n, WL07_n, WL08_n, WL09_n, WL10_n, WL11_n, WL12_n, WL13_n, WL14_n, RAD, SAP, SCAD, OCTAD2, n8XP5, MONPAR, XB0_n, XB1_n, XB2_n, XB3_n, CYL_n, CYR_n, EDOP_n, GINH, SR_n, EXTPLS, INHPLS, RELPLS, G01ED, G02ED, G03ED, G04ED, G05ED, G06ED, G07ED, GEQZRO_n, RADRG, RADRZ, S11, S12); // I/O wire declarations input wire SIM_RST; input wire CGG; input wire CSG; output wire CYL_n; output wire CYR_n; output wire EDOP_n; output wire EXTPLS; // Internal wire declarations wire NET_106; wire NET_107; wire NET_108; wire NET_109; wire NET_110; wire NET_111; wire NET_112; // Component list pullup R12001(__A12_1__GNZRO); pullup R12002(RELPLS); pullup R12003(INHPLS); pullup R12004(__A12_1__PALE); U74HC04 U12001(G01, __A12_1__G01A_n, G02, __A12_1__G02_n, G03, __A12_1__G03_n, GND, __A12_1__PA03_n, __A12_1__PA03, NET_196, G04, NET_190, G05, VCC, SIM_RST); U74HC27 U12002(G01, G02, G01, __A12_1__G02_n, __A12_1__G03_n, NET_182, GND, NET_181, __A12_1__G01A_n, G02, __A12_1__G03_n, NET_177, G03, VCC, SIM_RST); U74HC27 U12003(__A12_1__G01A_n, __A12_1__G02_n, G04, G05, G06, NET_183, GND, NET_186, G04, NET_190, NET_195, NET_180, G03, VCC, SIM_RST); U74HC4002 U12004(__A12_1__PA03, NET_177, NET_182, NET_181, NET_180, NET_179, GND, NET_178, NET_183, NET_186, NET_185, NET_184, __A12_1__PA06, VCC, SIM_RST); U74HC04 U12005(G06, NET_195, NET_183, NET_133, __A12_1__PA06, __A12_1__PA06_n, GND, NET_189, G07, NET_127, G08, NET_174, G09, VCC, SIM_RST);
Above this is a top-level module called agc.v, that instantiates each of these modules and forms a sort of virtual backplane, connecting all of the modules together via intramodule nets.
Initially the backplane file was written by hand, but somewhere around the fourth module, I realized that keeping track of what signals went where was untenable, and wrote a second python script to do it automatically for me: the backplane generator. This script reads in the verilog sources for all modules in a directory, and processes all of their I/O nets. Nets that are inputs for one module and outputs of another are marked as ‘internal’, while nets that have no source module or no destination module are considered ‘external’. The required I/O for the backplane is thus determined, and a new module implementing the determined backplane, and instantiating each of the modules, is generated.
With these two scripts, the entirety of the AGC is automatically generated. The only things that need manual editing are test scripts that instantiate it, as I/O changes — but it is very easy to keep track of I/O the backplane needs, since it’s automatically determined and presented in a nice, sorted list. The list is also waaaay shorter than the list of all nets in the system.
The last piece of the build system that I haven’t talked about is my Makefile. KiCAD unfortunately doesn’t have any command-line arguments, and not only would they be hard to add for netlist generation, but the KiCAD developers have rejected patches to do exactly that in the past. Because generating code for over a dozen modules is extremely tedious, I used xdotool to script it all. Xdotool is kind of like AutoHotkey for X Windows; the Makefile steps through a list of modules for which to generate code, opens KiCAD, and uses keyboard shortcuts to navigate to and begin code generation.
It’s super hangly, but it works. Put it all together, and an executable Icarus Verilog simulation binary can be produced from scratch simply by typing make.
Next time I’ll go back to looking at hardware design, with an overview of the start/stop logic!