Modeling Wires

Read time: 27 minutes (6840 words)

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.

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;
}

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: Adding Control Logic

At this point in our development, we see that the main function is building the system we are simulating, then making it run. It would be much better to move that logic out of main, which really ought to be more focused on the user of this code. Since we still need a place to set up our system, let’s introduce a new class, this one called Machine.

To see this new idea, 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!

Of course, we need to look at 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, atq 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!

The new way of creating our components and wires means we need to alter the other 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;
}
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 );
};


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

// constructor
Wire::Wire( void ) {
    data = -1;
    in = NULL;
    out = NULL;
}

// Accessor
void Wire::read(void) {
    // fetch wire value from input side
    data = in->write();
    source = in->get_name();
}

void Wire::write( void ) {
    // deliver wire value to output side
    dest = out->get_name();
    out->write();
    std::cout << source << "-(" << 
        data << ")->" << dest << std::endl;
}

void Wire::attach_in(Component *c_in) {
    in = c_in;
}

void Wire::attach_out(Component *c_out) {
    out = c_out;
}

void Wire::tick( void ) {
    read();
    write();
}

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:

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

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

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

// receive data from somewhere
int Component::read( int val ) {
    return data = val;
}

std::string Component::get_name( void ) {
    return name;
}
Component.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;
};

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();
    }
}
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 );
};


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

// constructor
Wire::Wire( void ) {
    data = -1;
    in = NULL;
    out = NULL;
}

// Accessor
void Wire::read(void) {
    // fetch wire value from input side
    data = in->write();
    source = in->get_name();
}

void Wire::write( void ) {
    // deliver wire value to output side
    dest = out->get_name();
    out->write();
    std::cout << source << "-(" << 
        data << ")->" << dest << std::endl;
}

void Wire::attach_in(Component *c_in) {
    in = c_in;
}

void Wire::attach_out(Component *c_out) {
    out = c_out;
}

void Wire::tick( void ) {
    read();
    write();
}
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(1.7)" << std::endl;
    std::cout << "running ..." << std::endl;

    // build the machine
    m.build();

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

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

Note

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