Final Wiring¶
Read time: 28 minutes (7024 words)
With the final design laid out in some detail, with all the needed components identified, and the wires we need to set up itemized, all that remains is to wire up this diagram, then place on top of everything a control unit that can direct the action. We have seen enough code and examples to do that job
To help in visualizing how we will interconnect all the parts we have constructed, here is a diagram showing how pointers are being used to hook up our circuit.

In this diagram, we see two generic parts, each with a set of input pins and
output pins. All parts provide a single tick
method, which we now set up
with a single integer parameter that will be set by our control unit.
Each part is connected to a wire. The attachments are through a
pin. Each part has a C++ vector
of pointers to pins, so we dynamically allocate
pins as needed, and store the address of each pin in the appropriate vector. In
the diagram, these pointers are simply numbered.
The pins maintain four pieces of information:
n
- a string naming the pin
p
- a pointer to the part thta owns the pin
w
- a pointer to the attached wire
v
- the current value of the signal on that pin
We obviously have a bunch of pointers in this design, but this scheme makes building our machine easy. Just watch!
The Simulator System¶
And, just for reference, here is the simulator system, as designed up to this point. It still needs work, but this is enough ot start working through the instruction set to make sure the machine is complete (it is not!)

Testing things¶
Throughout this course, we have emphasized testing the parts you need to build to set up this simulator. Here is a conceptual diagrm of the test setup we want to build. In hardware design, you hook up a part to a “test fixture” then fire it up and make sure it works. The fixture is designed so you can attach the inputs, and generate signals to be sent to the unit, then you record the outputs and decide if the part is working correctly.

Stage Design¶
Earlier, we describes each stage as a big component, with a bunch of parts and wires inside. Each stage “super-component” can have its own control unit that directs the actions needed for just that stage. As you may have noticed, execute and store may need some actions to happen back in the decode stage. The control unit can handle that, with a little thought.
It still seems a good idea to build our final design as a set of super-components with inputs and outputs defined. I added dashed lines at the points in our design where the stage boundaries logically should be placed.
One way to actually design this system is to simply place pins at those spots where wires cross the boundaries, and let those pins be shared by both stages. This would avoid adding more wires to the design. Our big master simulation loop them would only need to call “tick” on each super-component, and the data items would be recorder on the pins. There is no wire leaving that pin. So, when we run “tick” on the following stage, reading the pin would let that next stage see the new data value.
Visually, we simple glue one unit up to another unit with no wire in between. It should work!
Control Logic¶
We have talked about the control unit, and shown example code that we can use to make things happen. Logically, the control unit sits above this design, with the ability to trigger action in any component or wire when needed. The decode unit will let the control unit know which instruction we are processing, and the control unit will consult some kind of data store telling it exactly how to sequence parts or wires to make that instruction happen.
MicroCode¶
Many processors work this way. There is a table with bits for each component in the system.
Note
Wires are not really controlled. When signals change on an input to a wire,
the signals immediately travel to the attached output points with no
control needed. We could model this behavior by calling the tock
method on any attached output wire whenever we change the output value on
the driving pin.
The “microcode” table is often hard coded into the chip. There might be a bit for every controllable gadget in the machine. There will be one potentially large list of bits for each step in moving data through the machine for each instruction. It can take a different number of steps to complete processing of every instruction, so this table can be a bit complicated to build.
One solution to this design issue is to create a vector of micro-instructions for every assembly language instruction we need to process. That vector will hold a list of parts needing to be triggered in the correct order to sequence that machine. This vector will be used by the control unit to decide which parts to activate by calling the tick
method.
Since control nits provide additional signals to configure devices like
multiplexors, it makes sense to change our implementation of the `tick
method, and provide a simple parameter that can be used for this purpose. A
simple 8-bit data item will do for our machine.
void tick(uint8_t ctrl);
We will modify the tick
methods when needed to use this new signal properly.
The Wiring Job¶
Actually wiring the machine is just a chunk of boring code connecting wires to components using methods we have already set in place. This step can be automated, and “Hardware Description Languages” essentially do exactly that.
For our purposes, there is a simple way to do the tedious wiring.
Net Lists¶
Describing a circuit for some electronic gadget is often done by creating a “net list”. This is a simple text file containing a list of parts, and definitions of the wire connections needed between those parts. In our case, we have names for all the parts (classes) we have assembled, and we have names for each pin we attach to that component. Our “net list” can be pretty simple to write, and simple to implement.
Let’s give it a try.
Here is a “net list” file we can use:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | PARTS
Source reset
MUX2 mux1
PC pc
IM im
Decoder dec
RF rf
MUX2 mux2
MUX2 mux3
ALU alu
MUX2 mux4
MUX4 mux5
MUX4 mux6
DM dm
STK stk
IO io
END
WIRES
W1 mux5.OUT -> mux1.IN1 ;
W2 reset.OUT -> mux1.IN2 ;
W3 mux1.OUT -> pc.IN ;
W4 pc.OUT -> im.PC , dec.PC ;
W5 im.INS1 -> dec.INS1 ;
W6 im.INS2 -> dec.INS2 ;
W7 dec.RD -> rf.RD ;
W8 dec.RR -> rf.RR ;
W9 rf.CRD -> mux2.IN1 ;
W10 rf.CRR -> mux3.IN2 , io.IN , mux4.IN2 , mux6.IN1 ;
W11 dec.K -> mux2.IN2 , dm.MAR ;
W12 mux2.OUT -> alu.IN2 ;
W13 dec.PCN -> mux3.IN1 , mux5.IN1 , stk.IN ;
W14 mux3.OUT -> alu.IN1 ;
W15 dec.AOP -> alu.AOP ;
W16 alu.RES8 -> mux5.IN2 ;
W17 alu.RES16 -> mux4.IN1 ;
W18 dec.A -> io.MAR ;
W19 mux4.OUT -> dm.IN ;
W20 stk.OUT -> mux5.IN3 ;
W21 dm.OUT -> mux6.IN3 ;
W22 io.OUT -> mux6.IN2 ;
W23 mux6.OUT -> rf.MDR ;
END
|
All the pieces of this file are separated by spaces, making reading it using C++ easier.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | #include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include "Source.h"
#include "Pin.h"
#include "MUX2.h"
#include "PC.h"
#include "IM.h"
#include "RF.h"
#include "IO.h"
#include "MUX4.h"
#include "DM.h"
#include "STK.h"
#include "ALU.h"
#include "Decoder.h"
#include "Component.h"
// globals for parsing (UGH)
const std::string fname = "avrsim.net";
std::string token;
int lineno = 0;
std::vector<Component *> parts;
std::vector<Wire *> wires;
void build(std::string nfile) {
int num_wires = 0;
int num_parts = 0;
net_list = nfile;
if (debug) {
std::cout
<< "\tbuilding machine from: "
<< net_list
<< std::endl;
}
// build circuit from netlist
std::ifstream fin;
std::string token, name;
fin.open(net_list);
if (!fin) {
std::cout
<< "Netlist file not found: "
<< net_list
<< std::endl;
exit(1);
}
while (fin >> token) {
if (token == "PARTS") {
while (fin >> token) {
if (token == "END") break;
fin >> name;
num_parts++;
if (token == "Source") {
Source * src_part = new Source(name);
parts.push_back(reinterpret_cast<Component *>(src_part));
} else if (token == "MUX2") {
MUX2 * mux2_part = new MUX2(name);
parts.push_back(reinterpret_cast<Component *>(mux2_part));
} else if (token == "MUX4") {
MUX4 * mux4_part = new MUX4(name);
parts.push_back(reinterpret_cast<Component *>(mux4_part));
} else if (token == "PC") {
PC * pc_part = new PC(name);
parts.push_back(reinterpret_cast<Component *>(pc_part));
} else if (token == "IM") {
IM * im_part = new IM(name);
parts.push_back(reinterpret_cast<Component *>(im_part));
} else if (token == "DM") {
DM * dm_part = new DM(name);
parts.push_back(reinterpret_cast<Component *>(dm_part));
} else if (token == "STK") {
STK * stk_part = new STK(name);
parts.push_back(reinterpret_cast<Component *>(stk_part));
} else if (token == "Decoder") {
Decoder * dec_part = new Decoder(name);
parts.push_back(reinterpret_cast<Component *>(dec_part));
} else if (token == "ALU") {
ALU * alu_part = new ALU(name);
parts.push_back(reinterpret_cast<Component *>(alu_part));
} else if (token == "RF") {
RF * rf_part = new RF(name);
parts.push_back(reinterpret_cast<Component *>(rf_part));
} else if (token == "IO") {
IO * io_part = new IO(name);
parts.push_back(reinterpret_cast<Component *>(io_part));
} else {
Component * part = new Component(name);
parts.push_back(part);
}
}
}
if (token == "WIRES") {
while (fin >> token) {
if (token == "END") break;
num_wires++;
Wire * new_wire = new Wire(token);
wires.push_back(new_wire);
// connect this wire
fin >> token;
attach_driven(new_wire, token);
fin >> token; // skip ->
do {
fin >> token; // drives
fin >> token; // comma or semicolon
} while (token != ";");
}
}
}
fin.close();
std::cout
<< "machine ready!"
<< std::endl;
}
int main(int argc, char *argv[]) {
std::string part, name, pin;
build(fname);
}
|
Note
This is hardly the best example of a parser for this application, but it works for well-formed data files. If we had more time, I would show you real parsing code here.
To build the machine from this snippet of code, we use a vector
to hold
all of our parts, and another vector to hold all of our wires. Each part and
wire needs to be created dynamically, unless we want to just build an array of
those objects big enough for our simulator. (We do know how many of each we
need - now!)
Tick Tock¶
After we assemble the machine, our only remaining task is to figure out the control mechanism. Since we will have a list (vector) of parts and wires, and each of those provides a method to activate that gadget, running our machine is as simple as this:
for(int p = 0; p < num_parts; p++)
if (???) parts[p].tick();
for (int w=0; w<-num_wires;w++)
if (???) wires[w].tock();
Seems simple enough. Except for filling in those “???” logical expressions.
Cleared for Take Off¶
OK, so I am a pilot. The most fun moment in flying is when you are sitting there in an airplane with everything you need ready, and you are ready to take off. In our case, you have all the parts we need, and a diagram showing how to connect all those parts with some wires, and a plan for controlling everything.
Let’s see if we can make this beast run!