Reviewing the Parts

Read time: 15 minutes (3971 words)

In our work so far, we have focused on building a simple simulation of generic components connected by simple wires. Before we go any further in this effort, let’s step back and consider the parts we really need.

Basic Parts

We called the primary parts in our system “components”, and thought of them like the tiny eight pin integrated circuit that housed the actual attiny85 processor system.

../_images/attiny851.png

In viewing that part, we see one big issue we have not addressed. There are a number of “pins” attached to the device, and wires can be connected to any or all of them. So far, we have been working with simple “wire” objects that just attach to the component in some unspecified way. We need to fix that.

Actual digital components can have as few as the small number (eight or less) you see here, or as many as several hundred pins. Furthermore, each of those “pins” can be either an input or an output point where signals enter of leave the device. To make matters worse, some of those may do both input and output, just not at the same time.

Obviously, a more general component will have a variable number of “pins” associated with it, so we need to redesign our basic component class a bit.

Here is a new specification:

Component.h
 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
#pragma once
#include "Pin.h"
#include <string>
#include <vector>

class Component {
    public:
        // constructor
        Component(std::string n);

        // destructor
        ~Component();

        // input pins
        std::vector<Pin *> in_pins;

        // output pins
        std::vector<Pin *> out_pins;

        // mutators
        void add_in_pin(std::string name);
        void add_out_pin(std::string name);
        void tick(void);

        // accessors
        Pin * get_in_pin(std::string n);
        Pin * get_out_pin(std::string n);
        void dump_pins(void);
    private:
        std::string name;
};

Notice that we have introduced a new C++ construct here, the vector. A vector is a simple data type that acts like a normal array with one big difference. You can adjust the size of this data container at runtime. This avoids needing to “guess” at now many signals we might need for our system,

The add_xx_wire methods will be used to attach a pin to the component. We will use a simple string to name each pin, so we can refer to the pin by name, rather than by some number which does not help us follow the action. We also provide methods to locate pins by name.

Pins

To model the set of attach points for a component, we will introduce a new class called “Pin”. We will add a set of pins to each component. For simplicity, pins will only be allowed to be inputs or outputs, not both. Pins are not active in any way, so all they need to do is provide a place to store a current value, and a name for that signal.

System wires will actually attach to these pins.

The pin object maintains the name of the signal, and provides a place to store the current value of a signal present on that pin.

In the AVR family, it is common to refer to a single pin using a variety of names. Depending on the internal components we decide to activate. The pin signal may be different at different points in time. We may be able to handle this issue by renaming the pins as these internal components get activated. The names will be used when we generate signal traces for watching the machine work.

Here is the specification of the pin objects:

Pin.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#pragma once
// Pin class - for named pins
#include <cstdint>
#include <string>

class Pin {
    public:
        Pin(std::string n);
        void set_val(uint16_t v);
        uint16_t get_val(void);
        std::string get_name(void);
    private:
        std::string name;
        uint16_t val;
};

Here, the pins are set up to hold a 16-bit value, which is the biggest data item we will need for this machine. What this actually means is that the term “pin” is a bit misleading. We are actually modeling a “bus” which is a collection of wires running in parallel between components. Using a bus greatly speeds up the transfer of data between components!

Wires

Since we have redesigned our components to allow multiple attach points, we need to do the same for our wires. A wire can be “driven” by at most one pin, but it can be attached to , and drives, any number of pins on other components.

Note

In real circuits, there is a limit on the number of pins we can drive with one signal. This is called the “fan-out” of the signal, and is usually a number like ten. We will ignore that detail in our simulator.

Here is our modified Wire class:

Wire.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#pragma once
#include "Pin.h"
#include <cstdint>
#include <vector>

class Wire {
    public:
        Wire();
        void tock();
        void attach_drives(Pin * pin);
        void attach_driven(Pin * pin);
    private:
        uint16_t val;
        Pin * driven; 
        std::vector<Pin *> drives;
};

Again, we use a vector to handle the arbitrary output connections for a single wire.

Once again, remember that we are designing each wire to pass up to 16-bits at a time to other components.

Example system

Just to complete this description, let’s look at a simple example program that hooks all of these modified parts together:

Main.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include "Component.h"
#include "Wire.h"

int main(void) {
    std::cout << "attiny85sim (v0.1)" << std::endl;
    Component c1("PC"), c2("IR");
    c2.add_in_pin("MADDR");
    c1.add_out_pin("PCO");
    Wire w1;
    w1.attach_driven(c1.get_out_pin("PCO"));
    w1.attach_drives(c2.get_in_pin("MADDR"));
    c1.tick();
    w1.tock();
    c2.tick();
    c1.dump_pins();
    c2.dump_pins();
}

Our standard project Makefile should build this project.

Here are the implementation files needed for this version

Pin.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "Pin.h"

// constructor
Pin::Pin(std::string n) {
    name = n;
    val = 42;
}

// mutators
void Pin::set_val(uint16_t v) {
    val = v;
}

// accessors
uint16_t Pin::get_val(void) {
    return val;
}

std::string Pin::get_name(void) {
    return name;
}

Wire.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
#include "Wire.h"
#include "Pin.h"
#include <cstddef>
#include <iostream>

Wire::Wire() {
    driven = nullptr;
}

void Wire::attach_driven(Pin * pin) {
    driven = pin;
}

void Wire::attach_drives(Pin * pin) {
    drives.push_back(pin);
}

void Wire::tock(void) {
    val = driven->get_val();
    for(int i=0; i < drives.size(); i++) {
        drives[i]->set_val(val);
        std::cout << drives[i]->get_name()
            << " <- " << driven->get_name()
            << std::endl;;
    }
}
Component.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
// implementation file for Component class
#include "Component.h"
#include <iostream>

// default constructor
Component::Component(std::string n) {
    name = n;
}

Component::~Component() {
    for(int i=0; i< in_pins.size();i++) {
        delete in_pins[i];
    }
    for(int i=0; i< out_pins.size();i++) {
        delete out_pins[i];
    }
}

void Component::dump_pins(void) {
    for(int i=0; i < in_pins.size();i++)
	    std::cout 
            << name << "."
            << "in_pin" << i+1 << ": " 
            << in_pins[i]->get_val()
            << std::endl;
    for(int i=0; i<out_pins.size(); i++) 
        std::cout 
            << name << "."
            << "out_pin" << i+1 << ": " 
            << "->" 
            << int(out_pins[i]->get_val())
            << std::endl;
}

void Component::add_in_pin(std::string n) {
    in_pins.push_back(new Pin(n));
}

void Component::add_out_pin(std::string n) {
    out_pins.push_back(new Pin(n));
}

Pin * Component::get_in_pin(std::string n) {
    for(int i=0;i<in_pins.size(); i++) {
        if (in_pins[i]->get_name() == n) return in_pins[i];
    }
    return nullptr;
}

Pin * Component::get_out_pin(std::string n) {
    for(int i=0;i<out_pins.size(); i++) {
        if (out_pins[i]->get_name() == n) return out_pins[i];
    }
    return nullptr;
}

void Component::tick(void) {
    // dummy - zero all out pins
    for(int i=0; i<out_pins.size(); i++)
        out_pins[i]->set_val(0);
}