2
\$\begingroup\$

I've seen a lot of C++ simulators implementing logic gates, but absolutely all of them use the wrapped bitwise operations of the programming language itself. I have tried to omit the use of a bitwise operator and mimic what a transistor really does at its basic level until it reaches a really functional logical gate (just using branching states). I would appreciate any kind of feedback on how this can be improved because I have an OR gate for which I need to use the C++ OR bitwise (|) operator, for which also I have an approach using 3 NAND logic gates, but I think maybe the starting idea is wrong as how I modeled the transistor and the overall composition of the electronic schema. Maybe I'll need to implement electric flow output probes to be able to virtualize the transistor itself correctly. The overall scope of this is to create a full working 8bit CPU from scratch in C++.

Electronic schema profile:

class ElectricSchema
{
  public:
    NotGate*   m_NotGate;
    AndGate*   m_AndGate;
    NandGate*  m_NandGate;
    OrGate*    m_OrGate;
    XorGate*   m_XorGate;
    HalfAdder* m_HalfAdder;
    FullAdder* m_FullAdder;
    // 512 bit adder
    FullAdder* m_BusAdder512[512];

  private:
    bool m_voltageRail;

  public:
    ElectricSchema() : m_voltageRail(false) {}
    ElectricSchema(bool voltage) : m_voltageRail(voltage) {};
    ~ElectricSchema() {}

    const bool& getVoltageRail() const { return m_voltageRail; }
};

Transistor profile:

class Transistor
{
  private:
    bool m_collecterPinIn;
    bool m_basePinIn;
    bool m_emitterPinOut;

  public:
    Transistor() : m_collecterPinIn(schema.getVoltageRail()), m_basePinIn(false), m_emitterPinOut(false){}

    Transistor(bool collecter, bool base, bool emitter) : m_collecterPinIn(schema.getVoltageRail()), m_basePinIn(base), m_emitterPinOut(emitter)
    {
      if(collecter == false) { m_emitterPinOut = false; }
      else if(base == true) { m_emitterPinOut = true; }
      else { m_emitterPinOut = false; }
    }

    ~Transistor(){}

    const bool& getCollecterPinIn() const { return m_collecterPinIn; }
    const bool& getBasePinIn() const { return m_basePinIn; }
    const bool& getEmitterPinOut() { updateInternalStates(); return m_emitterPinOut; }

    bool& setCollecterPinIn() { return m_collecterPinIn; }
    bool& setBasePinIn() { return m_basePinIn; }

  private:
    void updateInternalStates()
    {
      if(m_collecterPinIn == false) { m_emitterPinOut = false; }
      else if(m_basePinIn == true) { m_emitterPinOut = true; }
      else { m_emitterPinOut = false; }
    }
};

Full code is around 1600 lines of code: https://godbolt.org/z/McnjY5T1d

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Why simulate transistors?

I do not think simulating "what a transistor really does at its basic level" is productive. Apart from just slowing down your code and not adding any value to the end user, your simulation of a transistor is quite arbitrary. A real transistor does not have boolean inputs and a boolean output, and the output doesn't instanteneously change when the inputs change. Furthermore, virtually all logic gates nowadays are implemented using a CMOS design, meaning that it is not using transistors, but rather pairs of complementary p- and n-type MOSFETs. Finally, your NandGate is based on an AndGate and NotGate, but in real life the basic gates are often NAND and NOT, and an AND-gate is implemented by combining a NAND-gate and a NOT-gate.

Class design

The class ElectricSchema has a fixed set of pointers to logic gates. What if you want to make a more complex schema? Ideally, an ElectricSchema would be able to hold an arbitrary amount of logic gates. You could create a base class LogicGate, and derive the concrete gate classes from that:

class LogicGate {
public:
    virtual void update() = 0;
    ...
};

class NotGate: public LogicGate {
public:
    void update() override {
        pinOut = !pinIn;
    }
    ...
};

Then in ElectricSchema, you could do:

class ElectricSchema {
    std::vector<LogicGate *> gates;
    ...
public:
    void update() {
        for (auto& gate: gates) {
            gate->update();
        }
    }
    ...
};

And then add gates like so:

schema.gates.push_back(new NotGate());

Ideally each concrete LogicGate knows how to update itself based on its inputs and outputs, and you have to find some way to connect outputs from one gate to the inputs of other gates. But once you have that, you should just be able to call schema.update() in a loop to simulate the whole schema.

Avoid manual new and delete

It's easy to make mistakes when doing manual memory management, and in modern C++ it is almost never necessary to do this anymore. First, don't allocate memory if it is not necessary to begin with. In your class ElectricSchema, why not just write:

class ElectricSchema
{
public:
    NotGate m_NotGate;
    AndGate m_AndGate;
    ...
    FullAddres m_BusAdder512[512];
    ...
};

This avoids having to write any new and delete statements. However, to make that work, your gate classes must be defined before ElectricSchema.

If you do need to allocate memory, then the C++ standard library comes with several types to manage memory for you, like std::unique_ptr and the STL containers.

\$\endgroup\$
3
  • \$\begingroup\$ Hi, first of all thank you for your feedback. It is a very good idea mentioning inheritance with pure interface for update method it seems logic. I can not use direct instances I choose pointers as I forward declare gate classes defined after ElectricSchema class. ElectricSchema is first class and implementation of gate classes are unknown at that point in time (see: godbolt.org/z/xqo7jMfn3). The second problem is that some of the gates the first ones what are transistor composites and the transistor itself need to know about ElectricSchema voltage rails (+5V true, 0 false). \$\endgroup\$
    – LXSoft
    May 31, 2022 at 13:14
  • \$\begingroup\$ So Transistor has an ElectricSchema* as a aggregation to initialize based on schema conditions, but Transistor itself must be only know that not to deal with other components in the circuit, I will need to create protected fields in the ElectronicSchema class too and just provide access to only what is required for a specific gate implementation so that the transistor itself will not able to modify other transistor that are not linked directly to it. Seems that my class design is somehow not started correctly I am drawing on paper on and on and I still see a diamond pattern which should not be \$\endgroup\$
    – LXSoft
    May 31, 2022 at 13:18
  • 1
    \$\begingroup\$ I would still say that you can solve the issues by not forward declaring anything, and to avoid the dependency of logic gates on ElectricSchema, remove m_voltageRail from it. Why does it have only one rail anyway that is either +5V or GND? You could create a VoltageRail class that derives from LogicGate, and have it only have an output pin with the desired voltage. \$\endgroup\$
    – G. Sliepen
    May 31, 2022 at 18:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.