Modeling Wires

Read time: 36 minutes (9245 words)

Note

Beginning with this lecture, we will start Lab4. Remember to tag your commits as indicated.

So far, we have been moving data the traditional way, by letting the objects be manipulated directly by some controlling code. We need to stop doing that and add the wires between our components. The next few steps make that transition.

Step05: Introducing Wires

We have a significant mental switch to make here. So far, the terms read and write have been viewed from the perspective of the controller making things happen. As we said earlier, that has to change. From this point on, those verbs (actions) will be viewed as happening inside of the component. When the component writes, data is leaving the component. When the component reads, data is arriving from the outside world, to the inside of the component.

In both cases, that new wire gadget will be involved.

When Objects Collide!

Scene: Coffee shop

Nick: I just watched an ancient science fiction movie: “When Worlds Collide”. If that happens, we are not going to be happy campers!

Ada: Yeah, normally planets should stay out each other’s way!

Alan: I wonder if when they get close enough, people would float between them, The two gravities would cancel each other out, and we could FLY!

Nick: Not for long, but enjoy it while you can!

We have two different kinds of objects to work with now. Neither of these objects will “contain” each other. They are independent. So how do we tie them together to make our simulator work.

The answer is those evil pointer gadgets. We will place a pointer to a component in each “end” of the wire. The wire will be able to record the address of the component that can write to the wire on one end, and the component on the other end will be able to read from the wire.

Note

We could place pointers to wires in the components just as well. In that case, we would need to connect the components to the wires, and provide methods to do that. When I first created this simulator, I chose not to modify the component class, and let wires be in charge of connections. That seemed reasonable at the time, but we may revisit that later.

We need a way to get the addresses of our two components, and attach the wire to both of them.

Here is what out new main function will look like:

main.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
#include <iostream>
#include "Component.h"

// create parts

Component c1("X",5), c2("Y",0);
Wire c1_c2;

void build_circuit( void ) {
    c1_c2.attach_in(&c1);
    c1_c2.attach_out(&c2);
}

int main( int argc, char *argv[] ) {
    std::cout << "attinysim v(0.5.0)" << std::endl;
    std::cout << "running ..." << std::endl;

    // build the circuit
    build_circuit();

    // make the data move
    c1_c2.tick();

    std::cout << "done!" << std::endl;
}

Notice that we still build two component objects, but we also build a new wire, with a name to remind us which components we are attaching to the two ends. In this case, c1 will be writing to the wire, and c2 will be reading from the wire. Notice, also, that we hook everything together in the build_circuit function, using methods in the Wire class. This function connects the two ends of the wire to the correct components, using their addresses.

Component Modifications

Here is the new Component definition:

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

class Component {
    public:
        Component(std::string n, int val);
        friend class Wire;
    private:
        std::string name;
        int data;

        std::string get_name();
        int read(int val);
        int write( void );
};


The friend specification in this file may be new to you. Basically, a friend class is allowed to access attributes and methods in another class that are marked as private. This is yet another neat way to restrict access to only that part of the application code that really needs that access. In our machine we are making as much of the Component class as we can private. But, we will allow the Wire class to reach in and work with things inside that protective wrapper.

Here is the component implementation:

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

Component::Component( std::string n, int val ) {
    data = val;
    name = n;
}

int Component::write(int val) {
    data = val;
    return data;
}

int Component::read(void) {
    return data;
}

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

Wire Implementation

And, finally, here is the Wire class definition:

Wire.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#pragma once
#include <string>

class Component;

class Wire {
    public:
        Wire();
        void attach_in(Component *c_in);
        void attach_out(Component *c_out);
        void tick( void );
    private:
        int data;
        Component *in;
        Component *out;
        std::string source, dest;
        void read(void);
        void write( void );
};


There is definitely something new here. This wire object supports a new method called tick. We have been describing the controller as a component that sends signals to other components in the form of a tick method. Here, we are asking the wire component to respond to a tick call and do something.

Does that make any sense.

Recall what we are modeling here. It is the simple transfer of data from one place to another. The components have no real work to do yet. But the wire definitely has work to do. It must transfer the data between the components.

What we are doing here is giving the wire the responsibility for reading and writing data from the individual components`. Real wires have no “intelligence”, so it really is not a wise design decision to make them do anything. It would definitely be better to move those actions into the component. However, there is something interesting that will happen if we leave this design as is, at least for now. We may revisit that decision later.

Here is the wire implementation:

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
#include <iostream>
#include <string>
#include "Wire.h"
#include "Component.h"

// constructor
Wire::Wire() {
    data = -1;

    //attachments
    in = NULL;
    out = NULL;
}

// Accessors do not modify object state
void Wire::write( void ) {
    // send wire data to output
    dest = out->get_name();
    out->write();
    std::cout << source << "-(" << 
        data << ")->" << dest << std::endl;
}

// mutators modify object state
void Wire::read(void) {
    // load wire data from input
    data = in->write();
    source = in->get_name();
}

void Wire::attach_in(Component *c_in) {
    // connect component to inut side
    in = c_in;
}

void Wire::attach_out(Component *c_out) {
    // connect component to output side
    out = c_out;
}

void Wire::tick( void ) {
    // on demand get new data and deliver it
    read();
    write();
}

Does it still work? Let’s see:

$ make run
./cosc2325
attinysim v(0.5.0)
running ...
X-(5)->Y
done!

It looks like we are still running fine. This time with a completely new way to accomplish our data transfer.

Reviewing Step05

We are definitely making progress. The transfer of data is now happening by a simple, direct, movement of that data over a simulated “wire”. We have instrumented our code so we can observe that transfer to make sure things are happening in a logical order. Our machine is still pretty primitive, but it is headed in the right direction!

The wire is definitely in control of the action in this version. Wires are not in control of anything in a real system. They are just conducting pathways over which electrons flow from place to place. They are hugely important, but they have no intelligence. So, we really should get rid of that part of our design and transfer the intelligence into the components, where that makes more sense. However, we will be able to watch things work in a very simple way using this design, so I am leaving that code as is.

In our next few steps, we will start making the machine “run” and set up a simulated oscilloscope so we can visually watch things go!

This should be fun!

Note

Commit this version. Tag it as v0.5.0

Step06: Control Logic

Now that we have a class that will model connections between our components, we need to look at how we are building the simulator.

Reviewing the Simulation

Our Wire class has no real purpose other than to facilitate the movement of data from register to register in our simulator. At this point, we are using the term “register” to maen a component that can hold a piece of information internally. The “register” does not perform any work on that data, it simply hangs on to it.

Wires, in general, can have only one input source at a time. They may be read by many components. In real circuits, there is a limit on the number of components you can attach to a wire, but we will ignore that limitation.

For now, we will focus on a single input and a single output for the wire objects.

We will let the wire objects respond to the clock ticks as well, which is not something normally done in simulators. My idea here is that we will treat our clock as a two stage device, sending “tick” signals to all system components, then sending “tock” signals to all wires to have them move data between components. In a real circuit, the wire just sits there, transferring signals over their length to any attached components, so this model seems reasonable,

I am setting things up this way so we can watch our data moving over the wires from place to place. We will eventually build two kinds of output displays: a simple console log of the actions taken, and a graphical system we can use to visualize what is going on, using a display similar to those found on oscilloscopes.

Adding a Machine Class

We need to pull building the machine out of our main` function, and set up the control logic for the simulator. We will not call this component the controller, since it is going to be responsible for the construction and operation of the entire machine. We will simply call it the Machine!

Here is the new Machine class definition:

Machine.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#pragma once
#include "Component.h"
#include "Wire.h"

class Machine {
    public:
        Machine( void );
        void build( void );
        void run( void );
        friend class wire;
    private:
        // parts needed
        Component *c1;
        Component *c2;
        Wire *c1_c2;
};

There is something new in here. Instead of creating our component and wire objects statically, by simply declaring them in the code, I am building everything dynamically, at run time. We store the address of these new objects in a pointer variable. That change means I no longer can call methods using a simple dot notation. Instead, we need to use the -> notation, which simply means “follow this pointer to an object, and call a method you find there”.

Here is the implementation of the Machine class:

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

Machine::Machine( void ) {
}

void Machine::build( void ) {
    c1 = new Component("C1",5);
    c2 = new Component("C2", 0);
    c1_c2 = new Wire();
    c1_c2->attach_in(c1);
    c1_c2->attach_out(c2);
}

void Machine::run( void ) {
    c1_c2->tick();
}

Make sure you understand this new notation!

Component Class Modifications

The new way of creating our components and wires means we need to alter the component files slightly:

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

class Component {
    public:
        Component(std::string n, int val);
        friend class Wire;
    private:
        std::string name;
        int data;

        std::string get_name();
        int read(int val);
        int write( void );
};


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

Component::Component( std::string n, int val ) {
    data = val;
    name = n;
}

int Component::write( void ) {
    return data;
}

int Component::read( int val ) {
    return data = val;
}

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

Modifications to main

With our new Machine class in place, here is the modified main.cpp file

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

Machine m;

int main( int argc, char *argv[] ) {
    std::cout << "cycsi v(0.6.0)" << std::endl;
    std::cout << "running ..." << std::endl;

    // construct the machine
    m.build();

    // make data move
    m.run();

    std::cout << "done!" << std::endl;
}

This is much cleaner. All of the setup logic is now gone from main. All main needs to do is create a Machine object, initialize that object (by building the system), and then direct that object to run!

Obviously, we need to test this version:

$ make
make[2]: Nothing to be done for `all'.

And run it:

$ make run
./cosc2325
cycsi v(0.6.0)
running ...
C1-(5)->C2
done!

Note

You should commit this version of the code now. Tag it as version v.0.6.0

Step07: Modeling Time

Our simulation is making progress, but it is only a one-shot “tick” system. We need a way to make this machine run for some period of time. The change to do that just needs to add a simple loop in the run method in our Machine class:

Machine Class

The machine class has one new function in addition to the clock loop. It will start the log output. iAt this point, we only display console messages. The run method has the needed code.

The class specification file is unchanged

The implementation adds output in the run method. This code starts a line of output by displaying a clock “tick” number. The run method is now set up to run for several cycles. Since our simulator is not doing any real work for now, the output will be a set of repeated lines.

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

Machine::Machine( void ) {
}

void Machine::build( void ) {
    c1 = new Component("C1",5);
    c2 = new Component("C2", 0);
    c1_c2 = new Wire();
    c1_c2->attach_in(c1);
    c1_c2->attach_out(c2);
}

void Machine::run( void ) {
    int max_ticks = 10;
    for( int time = 0; time < max_ticks; time++ ) {
        std::cout << "t" << time << ": ";
        c1_c2->tick();
    }
}

Main Function

The main function remains unchanged, except for the version number:

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

Machine m;

int main( int argc, char *argv[] ) {
    std::cout << "cycsi v(0.7.0)" << std::endl;
    std::cout << "running ..." << std::endl;

    // build the machine
    m.build();

    // make the data move
    m.run();

    std::cout << "done!" << std::endl;
}

Let’s check out this version:

$ make
make[2]: Nothing to be done for `all'.

And run it:

$ make run
./cosc2325
cycsi v(0.7.0)
running ...
t0: C1-(5)->C2
t1: C1-(5)->C2
t2: C1-(5)->C2
t3: C1-(5)->C2
t4: C1-(5)->C2
t5: C1-(5)->C2
t6: C1-(5)->C2
t7: C1-(5)->C2
t8: C1-(5)->C2
t9: C1-(5)->C2
done!

Note

You should commit this version of the code now. Tag it as version v.0.7.0