Step 5: Adding the Wire Class¶
Read time: 18 minutes (4631 words)
This class is going to be a bit harder to set up than the Pin class. We need help from part of the C++ Standard Template Library to set up a wire.
As we discussed earlier, a wire can be driven by only one signal source, but it
can deliver that signal to any number of other parts. The input to a wire is an
output signal from some part. The signal will be sitting on an output pin
attached to that part. The signal travels over the wire, then reaches another
part where it will connect to an input pin on some other part. In real digital
hardware, one wire can usually drive no more than about 10 other parts. This
is called the fan-out
limit and it is the result of needing enough power in
the signal to adequately trigger a bunch of inputs on other parts. We will not
worry about that here, but in real hardware, you need to pay attention to that
limit.
We will set up wires so they “point” to a pin. The attachment will be a pointer to a pin. We need one pointer to a Pin object on one side of the wire. This one pin will “drive” the wire. That one is simple to set up. We just add an attribute that is a pointer to a pin. On the other end of the wire we need a way to attach a bunch of pins.
A simple array of pin pointers will not do, because we do not know how many attachments we might need. What we need is an array that can grow at run-time.
C++ provides exactly that kind of data structure in the C++ STL Vector class.
Internally, a vector is something like a linked list of objects, except that
this list is accessed more like an array. A vector is a template
class,
something you may not have used before. Basically a template is like a normal
class, but there is a data type parameter that you specify when you create
objects using the class.
C++ Vectors¶
Objects of the vector class can be accessed using simple array notation, and we
can ask how many items are currently in the “array”. The new feature is the
ability to add items to the array at runtime. The method we use for this is
named push_back
.
Here is some example code that demonstrates how we will use the vector class to create out output attachment points.
#include <vector>
#include "Pin.h"
Pin * driven;
std::vector<Pin *> drives;
Pin pin1("P1");
Pin pin2("P2");
Pin pin3("P3");
driven = &pin1;
drives.push_back(&pin2);
drives.push_back(&pin3);
The key piece of code here is that data type definition inside of the angle brackets above. Those create a specific vector class that will be an array of pointers to pins.
Note
This is just an example, the actual code will be shown later.
Here, we are recording the address of a pin object in the drives
object,
and adding the addresses of the needed pins in the drives
vector object.
The Wire Specification¶
Here is the specification of our Wire class:
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 | // Copyright 2018 Roie R. Black
#pragma once
#include <string>
#include <cstdint>
#include <vector>
#include "Pin.h"
class Wire {
public:
// constructors
explicit Wire(std::string n);
// accessors
std::string get_name(void);
std::string get_driven_name(void);
std::string get_drives_names(void);
// mutators
void tock();
void attach_drives(Pin * pin);
void attach_driven(Pin * pin);
private:
std::string name;
uint16_t val;
Pin *driven;
std::vector<Pin *> drives;
};
|
It is not common to name wires in digital circuits, you tend to focus on the names of the pins used to hook things up. I am adding a name for later use. Again, it gives us a simple test of a constructor.
Notice that we provide the tock
method we will use to move data along the
wire.
There are also methods we can use to manufacture a specific wire, adding the attachments on both ends as needed.
Finally, there are methods to retrieve the names of the pins we end up attaching to a wire. This will be used in checking that our machines work properly later when we start building real circuits using this code.
Testing the Wire¶
With the header defined, we can write some simple tests. We already have a basic constructor, but we need to test the value on the wire. This logic is identical to what we set up for testing the pin class.
The new methods will take some setup to exercise. Here is the code I came up with:
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 | // Copyright 2018 ROie R. Black
#include <catch.hpp>
#include "Pin.h"
#include "Wire.h"
TEST_CASE( "test wire constructor", "wire" ){
const std::string name = "basic";
Wire wire(name);
REQUIRE(wire.get_name() == name);
}
TEST_CASE( "test wire operation", "wire" ) {
Wire wire("W1");
Pin pin1("P1");
Pin pin2("P2");
Pin pin3("P3");
wire.attach_driven(&pin1);
wire.attach_drives(&pin2);
wire.attach_drives(&pin3);
pin1.set_val(1234);
wire.tock();
REQUIRE(pin2.get_val() == 1234);
REQUIRE(pin3.get_val() == 1234);
}
|
Note
This test code is certainly not complete. I should add checks that verify that when we attach a wire to pin objects, e can retrieve the names of the attached pins. Adding such tests enhances the “coverate” of our tests. We will add a tool to check your test coverage later.
Wire Class Implementation¶
With the test in place, it is time to add the Wire class implementation code.
This looks worse than it is. All we need to remember is how a wire works in our
system. We have provided a tock
method that will make the wire perform its
work. When that method is called, the wire will read the current signal on the
driven
pin, and copy that value onto each of the drives
pins. It is
pretty simply, but you need to remember that each attachment is a pointer to a
pin somewhere, and we need pointer notation to access the pin methods.
Here is the code we need to implement this class:
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 | // Copyright 2018 Roie R. Black
#include "Wire.h"
// constructors
Wire::Wire(std::string n) {
name = n;
driven = nullptr;
}
// accessors
std::string Wire::get_name(void) {
return name;
}
std::string Wire::get_driven_name(void) {
if (driven)
return driven->get_name();
return "no attachment";
}
std::string Wire::get_drives_names(void) {
std::string names = "";
for (int i = 0; i < drives.size(); i++) {
if ( i>0 ) names += " ";
names += drives[i]->get_name();
}
return names;
}
// mutators
void Wire::tock(void) {
val = driven->get_val();
for (int i = 0; i < drives.size(); i++) {
drives[i]->set_val(val);
}
}
void Wire::attach_driven(Pin * pin) {
driven = pin;
}
void Wire::attach_drives(Pin * pin) {
drives.push_back(pin);
}
|
Run your tests now, and make sure your wire works as expected!
We have two of the three basic classes we need to start our simulation work.
The next class on our list is the generic Component
class. We will not be
creating objects using this class in our simulator. Instead, we will inherit
from this class and create the real components we need.