Close

Different instances of the same unit

A project log for TRCM

Attempt to reinvent the wheel of HDL

shaosSHAOS 08/15/2018 at 07:240 Comments

Ok, it may look a little tricky - in the beginning we should create a generic unit class that we want to clone multiple times:

class HalfAdder : public Entity
{
 protected:

// indecies:
  int iA,iB,iS,iC;

// inputs:
  Signal A,B;

// outputs:
  Signal S,C;

 public:

  HalfAdder(const char* s) : Entity(s)
  {

// empty constructor for generic unit

  }

  void step()
  {
    A = io(iA).read();
    B = io(iB).read();

    if(A==TRUE && B==TRUE)
       S = FALSE;
    else if(A==FALSE && B==TRUE)
       S = TRUE;
    else if(A==TRUE && B==FALSE)
       S = TRUE;
    else // if(A==FALSE && B==FALSE)
       S = FALSE;

    if(A==TRUE && B==TRUE)
       C = TRUE;
    else
       C = FALSE;

//    cout << name() << ":" << A << B << "->" << C << S << endl;

    io(iS) << S;
    io(iC) << C;
  }
};

Then in the program (inside function main) we will create actual implementations (3 instances):

  class HalfAdder0 : public HalfAdder
  {
   public:
    HalfAdder0() : HalfAdder("HalfAdder0")
    {
      iA = at("INC");
      iB = at("I[0]");
      iS = at("O[0]");
      iC = at("C0");
    }
  } ha0;

  class HalfAdder1 : public HalfAdder
  {
   public:
    HalfAdder1() : HalfAdder("HalfAdder1")
    {
      iA = at("C0");
      iB = at("I[1]");
      iS = at("O[1]");
      iC = at("C1");
    }
  } ha1;

  class HalfAdder2 : public HalfAdder
  {
   public:
    HalfAdder2() : HalfAdder("HalfAdder2")
    {
      iA = at("C1");
      iB = at("I[2]");
      iS = at("O[2]");
      iC = at("C2");
    }
  } ha2;

As you can see 3 half-adders connected to perform optional increment (INC=1) of 3-bit number.

Controls will be done by special unit World that simulate outside world (so all inputs are outputs and outputs are inputs from world's point of view):

class World : public Entity
{

// indecies:
  int i_increment,i_input,i_output,i_carry;

// internal counter:
  int counter;

 public:

  World() : Entity("World")
  {
    i_increment = at("INC");
    i_input     = at("I",3);
    i_output    = at("O",3);
    i_carry     = at("C2");

    counter = 0;
  }

  void step() // test cases
  {
    Wire<4> vec; // temporary vector
    vec[0] = io(i_output+0).read();
    vec[1] = io(i_output+1).read();
    vec[2] = io(i_output+2).read();
    vec[3] = io(i_carry).read();
    cout << "Case " << counter << " output=" << vec << endl;

    switch(counter++)
    {
       case 0:
       case 1:
       case 2:
       case 3:
          io(i_increment) << FALSE;
          io(i_input+0)   << TRUE;
          io(i_input+1)   << TRUE;
          io(i_input+2)   << TRUE;
          break;

       case 4:
       case 5:
       case 6:
       case 7:
          io(i_increment) << TRUE;
          io(i_input+0)   << TRUE;
          io(i_input+1)   << TRUE;
          io(i_input+2)   << TRUE;
          break;

       case 8:
       case 9:
       case 10:
       case 11:
          io(i_increment) << TRUE;
          io(i_input+0)   << FALSE;
          io(i_input+1)   << FALSE;
          io(i_input+2)   << FALSE;
          break;


    }
  }

};

Object of class World ( World world; ) should be created before HalfAdders to make sure that input/output vectors are attached to global circuits before units that attach itself to separate bits of those vectors (otherwise indices will not be connected properly). Main simulation loop will look like this:

  for(int i=0;i<=12;i++)
  {
    sys->prepare();
    ha0.step();
    ha1.step();
    ha2.step();
    world.step();
  }

Order of calling step-methods of the objects is not actually matter, because all changes on outputs are done in different "dimension" that becomes available for inputs only after call sys->prepare() that shifts "time axis" 1 step forward to the future.

And this is output:

INC <- World (idx=0)
I[0] <- World (idx=1)
I[1] <- World (idx=2)
I[2] <- World (idx=3)
O[0] <- World (idx=4)
O[1] <- World (idx=5)
O[2] <- World (idx=6)
C2 <- World (idx=7)
INC <- HalfAdder0 (idx=0)
I[0] <- HalfAdder0 (idx=1)
O[0] <- HalfAdder0 (idx=4)
C0 <- HalfAdder0 (idx=8)
C0 <- HalfAdder1 (idx=8)
I[1] <- HalfAdder1 (idx=2)
O[1] <- HalfAdder1 (idx=5)
C1 <- HalfAdder1 (idx=9)
C1 <- HalfAdder2 (idx=9)
I[2] <- HalfAdder2 (idx=3)
O[2] <- HalfAdder2 (idx=6)
C2 <- HalfAdder2 (idx=7)
Case 0 output=ZZZZ
Case 1 output=NNNN
Case 2 output=PPPN
Case 3 output=PPPN
Case 4 output=PPPN
Case 5 output=PPPN
Case 6 output=NPPN
Case 7 output=NNPN
Case 8 output=NNNP
Case 9 output=NNNP
Case 10 output=PPPN
Case 11 output=PNNN
Case 12 output=PNNN

It's interesting that from 0111 to 1000 it took 3 cycles to calculate 0111+1 because half adders are connected sequentially and carry goes through all 3 of them in 3 simulation cycles :)

Source code of this test program: https://gitlab.com/ternary/trcm/blob/master/tests/test2.cpp


P.S. Some idea how to make life easier - we can hide some repeating things behind macros so instead:

  class HalfAdder0 : public HalfAdder
  {
   public:
    HalfAdder0() : HalfAdder("HalfAdder0")
    {
      iA = at("INC");
      iB = at("I[0]");
      iS = at("O[0]");
      iC = at("C0");
    }
  } ha0;

 it might be

INSTANCE(HalfAdder,0);
  iA = at("INC");
  iB = at("I[0]");
  iS = at("O[0]");
  iC = at("C0");
NAMED(ha0);

where used 2 pre-defined macros: 

#define STRING(s) #s
#define INSTANCE(x,y) class x##y : public x { public: x##y() : x(STRING(x##y)) {
#define NAMED(z) }}z

but it may look like C hack above C++ :)

Discussions