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.

../../_images/sim.png

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!)

../../_images/avrsim.png

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.

../../_images/fixture.png

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:

avrsim.net
 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.

/lib/sim_builder.cpp
  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!