|
Site Map
|
|
Program Structure
The program consists of device classes derived from base classes Store
and Source along with utility classes MacroAssembler,
MicroAssembler and Computer. Computer contains a collection of
stores and sources
connected together to form a sequential circuit that simulates a
digital computer. MacroAssembler provides a way to initialize
program and data memory while MicroAssembler provides a way to
initialize the micro-control memory. Computer also contains an
iterative algorithm to run the simulation.
There are three categories of device classes: sequential,
function and transformer. A sequential device has state that changes
in reaction to high and low clock transitions. Functions and
transformers represent combinational circuits.
A source is any class derived from Source. The subclass must
provide an implementation of eval(). A source represents any object
in Figure 8-26 that is at the origin of an arrow. Function and transformer classes are derived from Source.
A store is any class derived from Store. The subclass must provide
implementations of clkHigh() and clkLow(). A store represents any
object in Figure 8-26 that has persistence.
Some classes are both stores and sources. These are the classes
that maintain state from one clock cycle to the next. They are the
objects shown with a blue background in Figure 8-26, namely PC, IR,
CAR, Register File, and Memory. Control Memory, being a ROM, is just a Store.
In execution, the inputs to stores receive data from the
output of another store through a series of intermediate sources. For example,
in the diagram, the input to CAR is taken from a
2-to-1 multiplexor, "MUX C", that selects
between the opcode field of the IR and the NA field of the
control word. The first data input is a FieldExtractor Source
that extracts bits 20 through 27 of the control word. The second
data input is an Extractor Source that extracts bits 9
through 15 of the IR. The selector input of "MUX C" is the
output from an 8-to-1 multiplexor, "MUX S". The data
inputs are stored as an array of sources and the selector input
selects one of them to evaluate.
Another example is the DA input of the register file. A FieldExtractor is constructed for the DR field of IR and another
Extractor is constructed for the TD field of ControlMemory. a
Concatenator is constructed from these two Extractors and used as
the DA source for RegFile.
Class Hierarchy
Transformers (Combinational)

Registers (Persistent)

Register File (Persistent)

Functions (Combinational)

Stores
The run() method of Computer calls the clkHigh() method of each
store and then calls the clkLow() method. A micro-branch to 255
halts the computer.
for (;;) {
m_PC->clkHigh();
m_IR->clkHigh();
m_reg->clkHigh();
m_mem->clkHigh();
m_CAR->clkHigh();
m_PC->clkLow();
m_IR->clkLow();
m_reg->clkLow();
m_mem->clkLow();
m_CAR->clkLow();
cout << endl;
if (m_CAR->eval() == 255)
break;
}
Each store has instance variables m_latch_dataIn and m_dataOut. clkHigh() evaluates all input sources and uses them to construct a
new value in m_dataIn. clkLow() copies m_dataIn to m_dataOut. This
technique eliminates race conditions and obviates an
iterate-to-convergence approach. The stores emulate master-slave
flipflops, where the master loads on a rising clock edge and the
slave stores on a falling clock edge.
Some stores have additional instance variables to augment
m_dataIn. Their function will become obvious upon viewing the source
code.
Sources
A source is an object that can be evaluated. A subclass of Source
contains one or more instances of other sources. eval() returns an
expression by composing the results of eval() for the contained instances.
Here is a summary of sources and their evals(). The class and
role of member variables can be deduced from variables with names
that start with m_.
| Source
Class |
eval()
body
|
| Concatenator |
int low = m_low->eval();
int high = m_high->eval();
return (low & ((1 << m_lowsize) - 1))
| (high << m_lowsize);
|
| FieldExtractor |
int t = m_source->eval();
return (t >> m_offset) & ((1 << m_width) - 1);
|
| FunctionUnit |
int t = m_FS->eval();
switch (t) {
case 0:
return m_A->eval();
...
default:
return 0;
}
|
| FunctionUnitN |
return m_functionUnit->eval() < 0;
|
| FunctionUnitZ |
return m_functionUnit->eval() == 0;
|
| MUX2_1 |
int t = m_cSelect->eval();
return m_dIn[t]->eval();
|
| MUX8_1 |
int t = m_cSelect->eval();
return m_dIn[t]->eval();
|
| Not |
return !m_dIn->eval();
|
| One |
return 1;
|
| ROM |
return m_mem[m_aIn->eval()];
|
| RegFileB |
return m_regfile->evalB();
|
| SignExtender |
int t = m_dIn->eval();
return (t << (32 - m_width)) >> (32 - m_width);
|
| Zero |
return 0;
|
Stores and sources can be indirectly mutually referential. Because of this, constructor
arguments cannot be used to initialized member variables. Rather, a
separate Create() method is provided so that construction and
initialization can be programmed as separate events. Here is
a summary of the creation arguments for sources. In the body of each
Create, parameters are copied to
member variables whose names are the same as those of the parameters,
prefixed by m_. Parameters and members are also classified by
another prefix: "d" means data, "c" means
control, "a" means address.
| Source
Class |
Create()
parameters |
| Concatenator |
Create(Source *high, Source *low, int lowsize)
|
| Counter |
(char * name, Source * cLoad, Source * cIncr, Source * dIn)
|
| FieldExtractor |
Create(Source *source, int offset, int width)
|
| FunctionUnit |
Create(Source *cFS, Source *dA, Source *dB)
|
| FunctionUnitN |
Create(FunctionUnit *functionUnit)
|
| FunctionUnitZ |
Create(FunctionUnit *functionUnit)
|
| MUX2_1 |
Create(char *name, Source *dIn0, Source *dIn1, Source *cSelect)
|
| MUX8_1 |
Create(char *name, Source *dIn0, Source *dIn1, Source *dIn2,
Source *dIn3, Source *dIn4, Source *dIn5, Source *dIn6,
Source *dIn7, Source *cSelect)
|
| Not |
Create(Source *dIn)
|
| One |
Create()
|
| RAM |
Create(Source *dIn, Source *aIn, Source *cMW)
|
| RegFile |
Create(char *name, Source * cLoad, Source * dIn)
|
| RegFileB |
Create(RegFile *regfile)
|
| Register |
Create(char *name, Source * cLoad, Source * dIn)
|
| ROM |
Create(Source *aIn)
|
| SignExtender |
Create(Source *dIn, int width)
|
| Zero |
Create()
|
Stores, again
The eval() method for most stores is straightforward. Only the
register file has a more complex method that takes the temporary
register into account.
| Store
Class |
eval()
body |
| CAR |
return m_dataOut;
|
| IR |
return m_dataOut;
|
| RAM |
return m_mem[m_aIn->eval()];
|
| PC |
return m_dataOut;
|
| RegFile |
int t = m_cTASA->eval();
return m_reg[t & 8 ? 8 : t];
also, evalB()
int t = m_cTBSB->eval();
return m_reg[t & 8 ? 8 : t];
|
However, updates are less straightforward.
| Store
Class |
clkHigh() |
clkLow() |
| Counter |
if (m_cLoad->eval()) {
m_latch_dataIn = m_dIn->eval();
} else if (m_cIncr->eval()) {
m_latch_dataIn = m_dataOut + 1;
}
|
m_dataOut = m_latch_dataIn;
|
| Register |
if (m_cIL->eval())
m_latch_dataIn = m_dIn->eval();
|
m_dataOut = m_latch_dataIn;
|
| RAM |
m_latch_MW = m_cMW->eval();
if (m_latch_MW) {
m_latch_dataIn = m_dIn->eval();
m_latch_addressIn = m_aIn->eval();
}
|
if (m_latch_MW)
m_mem[m_latch_aIn] = m_latch_dataIn;
|
| RegFile |
m_latch_RW = m_cRW->eval();
if (m_latch_RW) {
m_latch_TDDR = m_cTDDR->eval();
m_latch_dataIn = m_dIn->eval();
}
|
if (m_latch_RW) {
if (m_latch_TDDR & 8)
m_reg[8] = m_latch_dataIn;
else
m_reg[m_latch_TDDR] = m_latch_dataIn;
}
|
Here is
a summary of the creation arguments for stores. Recall that parameters are assigned to
member variables whose names are the same as those of the parameters,
prefixed by m_. Parameters and members are also classified by
another prefix: "d" means data, "c" means
control, "a" means address.
| Store Class |
Create()
parameters |
| Counter |
Create(char * name, Source * cLoad, Source * cIncr, Source * dIn)
|
| Register |
Create(char *name, Source * cLoad, Source * dIn)
|
| RAM |
Create(Source *dIn, Source *aIn, Source *cMW)
|
| RegFile |
Create(Source *dIn, Source *cRW, Source *cTDDR,
Source *cTASA, Source *cTBSB)
|
|
|