Tags

, , , , , ,

I’m returning to the full-adder logic circuit modeling to present the code for a version that tries to capture the timing of the signals.

The goal is to take a closer look at the intermediate states of the adder as signals trickle through it.

This design uses the nine-NOR circuit:

But differs a lot in how it works, compared to the previous version.

§

Let’s JMP directly to the code:

class BasicGate (object):
    def __init__ (self, name):
        self.name = name
        self.state = False
        self.conns = []
        self.ts = float(0)

    def latch_in (self, ts):
        self.ts = ts

    def latch_out (self, ts):
        self.ts = ts
#

There was enough machinery this time that I created a base class.

The bit difference in this architecture is the latch_in and latch_out methods. These represent the “system clock” making a “tick” or a “tock” cycle. The ts variable is a clock tick (time stamp).

As before, a BasicGate has a name, a state, and a set of connections (conns) to downstream gates.

class Terminal (BasicGate):
    def __init__ (self, name):
        super().__init__(name)
        self.a = False

    def latch_in (self, ts):
        super().latch_in(ts)
        f = 0 if self.state == self.a else 1
        self.state = self.a
        return f

    def latch_out (self, ts):
        super().latch_out(ts)
        for conn in self.conns:
            conn(self.state)
        return self.state

    def input (self, new_state):
        self.a = new_state
#

The Terminal class implements the input and output terminals.

It has an input state (a) distinct from the gate state. The input state is clocked into the gate on the latch_in “tick” cycle.

The value the latch_in method returns signals if the gate actually changed: a 1 means the state changed, a 0 means it didn’t. This value builds the changes vector the “system clock” uses to determine if all gate changes have trickled through.

The latch_out method passes the current gate state to the inputs of any connected gate. This is the clock “tock” that moves signals along the “wires” from gate outputs to downstream gate inputs.

The input method is how an upstream connection (or the user) sets the gate state. (Or more properly, sets the input state that is later clocked into the gate state.)

class Nor (BasicGate):
    def __init__ (self, name):
        super().__init__(name)
        self.a = False
        self.b = False

    def latch_in (self, ts):
        super().latch_in(ts)
        state = False if self.a or self.b else True
        f = 0 if self.state == state else 1
        self.state = state
        return f

    def latch_out (self, ts):
        super().latch_out(ts)
        for conn in self.conns:
            conn(self.state)
        return self.state

    def in_a (self, state):
        self.a = state

    def in_b (self, state):
        self.b = state
#

The Nor class implements the NOR gate.

This class also separates the input states (two, in this case) and gate state. Like the Terminal class, there are two input methods (in_a and in_b) for input, and they just set the input state.

The latch_in and latch_out methods work like they did above with the exception that the logic is slightly more involved (implementing the NOR function).

§

The full-adder class looks a bit different, too:

class full_adder_nor (object):
    def __init__ (self):
        self.i1 = Terminal('A')   # Inputs...
        self.i2 = Terminal('B')
        self.i3 = Terminal('Ci')
        self.o1 = Terminal('S')   # Outputs...
        self.o2 = Terminal('Co')
        self.g1 = Nor('G1')       # Gates...
        self.g2 = Nor('G2')
        self.g3 = Nor('G3')
        self.g4 = Nor('G4')
        self.g5 = Nor('G5')
        self.g6 = Nor('G6')
        self.g7 = Nor('G7')
        self.g8 = Nor('G8')
        self.g9 = Nor('G9')
        self.gates = […all gate objects…]
        # Connections...
        self.i1.conns = [self.g1.in_a, self.g2.in_a]
        self.i2.conns = [self.g1.in_b, self.g3.in_b]
        self.g1.conns = [self.g2.in_b, self.g3.in_a, self.g9.in_a]
        self.g2.conns = [self.g4.in_a]
        self.g3.conns = [self.g4.in_b]
        self.g4.conns = [self.g5.in_a, self.g6.in_a]
        self.i3.conns = [self.g5.in_b, self.g7.in_b]
        self.g5.conns = [self.g6.in_b, self.g7.in_a, self.g9.in_b]
        self.g6.conns = [self.g8.in_a]
        self.g7.conns = [self.g8.in_b]
        self.g8.conns = [self.o1.input]
        self.g9.conns = [self.o2.input]
        # Initialize...
        self.i1.input(0)
        self.i2.input(0)
        self.i3.input(0)
        self.cycle_system()

    def cycle_system (self, times=10):
        for ix in range(times):
            fos = []
            fis = []
            for g in self.gates:
                fos.append(g.latch_out(1+ix))
                fis.append(g.latch_in(1+ix))
            if sum(fis) == 0:
                break

    def __call__ (self, a, b, ci):
        self.i1.input(a)
        self.cycle_system()
        self.i2.input(b)
        self.cycle_system()
        self.i3.input(ci)
        self.cycle_system()
        return (self.o1.state, self.o2.state)
#

There’s a fair bit of code for printing what’s going on to a file that I’ve removed for simplicity. In particular, the cycle_system method does a lot of output.

The constructor is essentially the same as the previous example with two exceptions: the gates list, which contains a reference to all objects; and that it calls cycle_system after setting the inputs to their defaults. This cycles the inputs through to the outputs and sets the default state.

The __call__ method works similar to previously, but calls to cycle_system after each input is set let those changes trickle through the circuit on their own.

The cycle_system method does a loop of up to some number times) or quits before then if no work remains. The loop calls, first, latch_out, and then latch_in, while building vectors of their return values.

As mentioned above, the vector built from the return values of latch_in determine if the loop can exit early. (For it not to exit early is an error, since it means not all the changes were compete. The max needs to be set to insure this doesn’t happen.)

§

Which is about all there is to it except for that it looks like:

Tick: 1
    : G1(1) 0 -> 0
    : G2(1) 0 -> 0
 1.0: (A) 0 -> 0
    : G1(2) 0 -> 0
    : G3(2) 0 -> 0
 1.0: (B) 0 -> 0
    : G5(2) 0 -> 0
    : G7(2) 0 -> 0
 1.0: (Ci) 0 -> 0
    : G2(2) 0 -> 0
    : G3(1) 0 -> 0
    : G9(1) 0 -> 0
 1.0: G1: 0 -> 1 [0, 0]
    : G4(1) 0 -> 0
 1.0: G2: 0 -> 1 [0, 0]
    : G4(2) 0 -> 0
 1.0: G3: 0 -> 1 [0, 0]
    : G5(1) 0 -> 0
    : G6(1) 0 -> 0
 1.0: G4: 0 -> 1 [0, 0]
    : G6(2) 0 -> 0
    : G7(1) 0 -> 0
    : G9(2) 0 -> 0
 1.0: G5: 0 -> 1 [0, 0]
    : G8(1) 0 -> 0
 1.0: G6: 0 -> 1 [0, 0]
    : G8(2) 0 -> 0
 1.0: G7: 0 -> 1 [0, 0]
 1.0: G8: 0 -> 1 [0, 0]
 1.0: G9: 0 -> 1 [0, 0]
 1.0: (S) 0 -> 0
 1.0: (Co) 0 -> 0
[0,0,0,0,0,0,0,0,0,0,0,0,0,0]
[0,0,0,1,1,1,1,1,1,1,1,1,0,0]

That’s just the first cycle of the “power on” sequence! (The default initialization done in the constructor.)

At the bottom are the two vectors created by one cycle. The top one shows that, on this first cycle, all the gates were set to 0. The lower one shows that nearly all the gates changed on this cycle (as they realized those 0 inputs meant they should have a 1 output.)

With a bit of massaging, the whole “power on” sequence looks like this:

1: G1: 0 -> 1 [0, 0]
1: G2: 0 -> 1 [0, 0]
1: G3: 0 -> 1 [0, 0]
1: G4: 0 -> 1 [0, 0]
1: G5: 0 -> 1 [0, 0]
1: G6: 0 -> 1 [0, 0]
1: G7: 0 -> 1 [0, 0]
1: G8: 0 -> 1 [0, 0]
1: G9: 0 -> 1 [0, 0]

2: G2: 1 -> 0 [0, 1]
2: G3: 1 -> 0 [1, 0]
2: G4: 1 -> 0 [1, 1]
2: G5: 1 -> 0 [1, 0]
2: G6: 1 -> 0 [1, 1]
2: G7: 1 -> 0 [1, 0]
2: G8: 1 -> 0 [1, 1]
2: G9: 1 -> 0 [1, 1]
2: (S) 0 -> 1
2: (Co) 0 -> 1

3: G4: 0 -> 1 [0, 0]
3: G5: 0 -> 1 [0, 0]
3: G6: 0 -> 1 [0, 0]
3: G7: 0 -> 1 [0, 0]
3: G8: 0 -> 1 [0, 0]
3: (S) 1 -> 0
3: (Co) 1 -> 0

4: G5: 1 -> 0 [1, 0]
4: G6: 1 -> 0 [1, 1]
4: G7: 1 -> 0 [1, 0]
4: G8: 1 -> 0 [1, 1]
4: G9: 0 -> 0 [1, 1]
4: (S) 0 -> 1

5: G7: 0 -> 1 [0, 0]
5: G8: 0 -> 1 [0, 0]
5: (S) 1 -> 0

6: G8: 1 -> 0 [0, 1]
6: (S) 0 -> 1

7: (S) 1 -> 0

INS: 0 0 0
OUT: 0 0
1-4: True False False True
5-9: False False True False False

Which just shows the change states.

§

The whole project shows how the closer to simulating actual physical reality we get, the more complicated things get. And not just complicated, but harder and harder to model numerically.