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:

include/Wire.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
// 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:

tests/test_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
// 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:

lib/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
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.