Control: The last step!¶
Read time: 22 minutes (5564 words)
The machine is wired and ready for action. All we need to do not is fire the beast up and let it run.
Well, that will happen when we find the right way to control the stages.
We know we need to activate the components to get internal processing taken care of, and we need to activate wires to move data around in our machine. A few small details will set up for a simple solution to the control problem (another 3am idea).
Tick Revisited¶
We originally designed every part in our machine using a single base class:
Component. That class set up the structure common to all components. Every part
has a set or inputs, and a set of outputs. The required tick
method tells
the part to perform whatever internal processing is needed to converts inputs to
outputs.
Simple.
Control Signals¶
Our design uses a tick
method with no parameters. Perhaps it makes sense to
send down a single set of bits that can be used to tell the part the missing
control signal data needed to process correctly.
For some parts, there is no need to additional signals, what they do is defined
and does not change (like a basic boolean part, or an incrementer. Other parts
need additional signals to decide what to do. All of our multiplexors work this
way, as does the alu
. Our memory units have additional controls to select
read or write.
Let’s review our inventory, and see what additional signals we need:
MUX2¶
sel: 1 bit to choose which input is to be routed to the output.
MUX4¶
sel: two bits to choose one of tha available four inputs to route to the output.
PC¶
none: this unit needs no control.
IM¶
none: nothing needed here as well, thi sis a read-only device
DEC¶
none: This unit needs no control.
RF¶
rw: selects read or write actions
ALU¶
This unit needs controls, but those come from the decoder.
STK¶
rw: selects push or pop actions
DM¶
rw: selects read or write
IO – * rw: selects read or write
Looking back, it seems that we need at most two additional bits to decide what
to do internally. If we add a single 8-bit parameter to the tick
method, we
can set up a simple control system that will let us get this system moving.
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 | // Copyright 2018 Roie R. Black
#pragma once
#include <string>
#include <vector>
#include "Component.h"
#include "Pin.h"
class Component {
public:
bool debug;
// constructor
explicit Component(std::string n);
// mutators
void add_in_pin(std::string name);
void add_out_pin(std::string name);
void tick(int ctrl);
void set_pin_val(Pin * p, uint16_t val);
void set_debug(void);
// accessors
std::string get_name(void);
Pin * get_in_pin(std::string n);
Pin * get_out_pin(std::string n);
uint16_t get_pin_val(Pin * p);
void dump(void);
private:
std::string name;
std::vector<Pin *> in_pins;
std::vector<Pin *> out_pins;
};
|
The base Component.cpp
file needs a tweak as well, this is an easy fix.
Modifying all part classes also needs to happen. Once that is done, we modify the internal processing so we control what happens based on that new signal (which is really arriving at the part from the control unit). We will not add wires to make thies signals “move”!
Controlling Wires¶
We can set up a big simplification in the wire management by simply noting that wires only need to be activated when something changes on their inputs. Our current design does not pay attention to this, but it would be an easy fix to check the current output pin value and compare that with our new output values. If an output pin does not change, there is no need to activate that wire.
What we can do is activate all wires attached to output pins at the end of a tick on that component. That will simplify our controller logic.
Building The Controller¶
Here is a plan:
Set up another control file, much like the one used to generate our machine.
This file will list the steps to be taken by identifying the part we need to
activate, and the needed new signal value. Since what happens when the machine
works through the four stages is completely defined by the instruction we
are processing at the moment, we will create an array (actually a vector
)
holding the sequence of components we need to activate, in order, to process that
instruction.
Here is a control file, showing the basic idea:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | Fetch
mux2.reset
pc.0
im.0
END
Decode
dec.0
rf.0
END
EOR
mux2.1
mux3.2
alu.0
END
OUT
io.0
END
IN
io.1
mux6.2
rf.1
END
|
In this example, the part to trigger is named, and that part should have been
created in the net list
we used to build our machine.
The dot followed by a number provides the signal value needed to this activation of that part.
Let’s work through the first two stages, since those do not really do anything different for any instruction. In fact, we can simply set up those two stages as extra “instructions” and record the actions needed for each. We will need to set up the controller so it “calls” these extra instructions on each machine cycle.
There is nothing in the fetch
or decode
stages that needs information from the
controller. Perhaps we should just invent two new “instructions” to tell the
machine to process those two actions.
The real issue is how to handle the last two stages.
Microcode¶
We have defined a list of steps we need to take so that data moves through our machine properly for each instruction we process. W need a way to save that list, so we can use it to manage the machine.
C++ supports the idea of an object that has no methods. The concept is an old
one in programming, and we call an object of this kind a struct
. Here are
two struct
data types we can use to set up our control system
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // Copyright 2019 Roie R. Black
#pragma once
#include <string>
#include <vector>
#include "Component.h"
struct uOp {
Component * part;
int ctrl;
};
struct uCode {
std::string name;
std::vector<uOp *>uops;
};
|
We can use these structs
to create a vector of instruction codes, each
containing a name and another vector with the actual micro-operations we need
to perform, in the correct order. Each micro-instruction (uOp
holds a
pointer to the part to be triggered, and the control value needed for that
step. All of this can be set up using our text file above, with the help of yet
another ugly parsing routine,
Note
Rather that paste that code here, it is in the CPUfactory3 repository, in
the COntroller
class implementation.
The final countdown!¶
Once we have this control system in place, all we need to do it start up the machine, load a program, activate the reset method, and start the machine running.
Note
The machine is running on my test system, but the code is not quite ready
for prime time. There was a bit of major surgery needed to add the new
tick
mechanism to the system, and I am still not ready to release all
the code. That should be done by next class!