# Electronic circuit logic gates simulator

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;

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

# 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;
...

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.
• 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. May 31, 2022 at 18:51