Logikgatter Simulation - Probleme bei der Verschaltung einfacher Logikgatter zu komplexen Gattern

Ilosin

Neues Mitglied
Hallo zusammen,

in Rahmen einer Projektarbeit meines Studiums soll ich eine Anwendung schreiben, die die Simulation von Logikgattern ermöglicht. Dabei gibt es zwei Herausforderungen.
  1. Die Interaktion erfolgt rein über die JShell und System.out.println() ist verboten. Stattdessen wird ein von meinem Dozenten selbst geschriebenes Programm verwendet um die Ausgaben eines Logikgatters darzustellen. Das soll hier aber erstmal keine Rolle spielen. Ich stehe aktuell vor einer anderen Herausforderung, ich gebe dieses Information nur weiter, damit jedem klar ist, was genau gefordert ist und wie der Rahmen der Aufgabe ist.
  2. Ich habe bereits einen, meiner Meinung nach, guten Code entwickelt, der die fünf Grundlegenden Gatter, "And", "Or ", "Not", "Nand" und "Nor" umsetzt. Diese können zu komplexeren Schaltungen, wie einem "XOR" verschaltet werden und diese Bausteine können auch gespeichert und wiederverwendet werden. Dies wird bei späteren Aufgaben relevant. Ziel ist es nämlich aus mehreren solcher Bausteine einzelne Bestandteile einer CPU zu schalten. Dabei entsteht folgendes Problem: Schaltet man einen "Halbaddierer" aus einem XOR Baustein und einem And-Gatter und speichert diesen ab und schaltet dann zwei dieser Halbaddierer mit einem Or zusammen sollte ein Volladdierer entstehen, das scheitert aber an folgenden Punkt:
    Bei einem komplexen Gatter (CompositeGate-Klasse) können zwar die Eingänge gesetzt werden, werden aber nicht der eigentlichen Logik als Eingang zur Verfügung gestellt. Diese bleiben weiterhin bei den bei der initial geschalteten Eingängen. Und für dieses Problem bräuchte ich eine Methode, die diese Verknüpfung herstellt. Sprich, die Eingänge, die sich eine komplexe Schaltung speichert, auch den Schaltungen, die ihr zu Grunde liegen, als Eingang sitzt.
    An bei der Code mit einigen Ausführungen.
Das interface LogicGate dient dazu festzulegen, was ein Logikgatter ist und was nicht, damit die Listen, die in der CompositeGate Klasse verwendet werden, alle Arten von Logikgattern aufnehmen kann. Die BinaryGate Klasse dient dazu zwischen And, Or, Nand, Nor und Not Gatter zu unterschieden.
Java:
// Interface für allgemeines Logikgatter
interface LogicGate {
    boolean output(); 
    LogicGate copy();
}

// Abstrakte Klasse für Gatter mit zwei Eingängen
abstract class BinaryGate implements LogicGate {
    LogicGate input1, input2;

    public void setInput1(LogicGate input1) {
        this.input1 = input1;
    }

    public void setInput2(LogicGate input2) {
        this.input2 = input2;
    }

    public LogicGate getInput1() {
        return input1;
    }

    public LogicGate getInput2() {
        return input2;
    }
}

Die nächsten Klassen implementieren alle das interface LogicGate und stellen die fünf grundlegend Gatter zur Verfügung. Beachtet hierbei die copy-Methoden, die dazu dienen, beim späteren Laden einer komplexen Schaltung eine Kopie dieses Gatters zu laden, da ansonsten, die selben Gatter, wie bei der initialen Verschaltung für jede geladen komplexe Schaltung genutzt werden.

Java:
// Klasse für Logisches UND
class AndGate extends BinaryGate {
    AndGate(LogicGate input1, LogicGate input2) {
        this.input1 = input1;
        this.input2 = input2;
    }

    AndGate(boolean input1, boolean input2) {
        this.input1 = new InputGate(input1, "input 1");
        this.input2 = new InputGate(input2, "input 2");
    }

    @Override
    public boolean output() {
        return input1.output() && input2.output();
    }

    public AndGate copy() {
        return new AndGate(input1.copy(), input2.copy());
    }

}

// Klasse für Logisches ODER
class OrGate extends BinaryGate {
    OrGate(LogicGate input1, LogicGate input2) {
        this.input1 = input1;
        this.input2 = input2;
    }

    OrGate(boolean input1, boolean input2) {
        this.input1 = new InputGate(input1, "input 1");
        this.input2 = new InputGate(input2, "input 2");
    }

    @Override
    public boolean output() {
        return input1.output() || input2.output();
    }

    public OrGate copy() {
        return new OrGate(input1.copy(), input2.copy());
    }
}

// Klasse für Logisches NICHT
class NotGate implements LogicGate {
    LogicGate input;

    NotGate(LogicGate input) {
        this.input = input;
    }

    void setInput(LogicGate input) {
        this.input = input;
    }

    LogicGate getInput() {
        return input;
    }

    @Override
    public boolean output() {
        return !input.output();
    }

    public NotGate copy() {
        return new NotGate(input.copy());
    }
}

// Klasse für NAND
class NandGate extends BinaryGate {
    NandGate(LogicGate input1, LogicGate input2) {
        this.input1 = input1;
        this.input2 = input2;
    }

    @Override
    public boolean output() {
        return !(input1.output() && input2.output());
    }

    public NandGate copy() {
        return new NandGate(input1.copy(), input2.copy());
    }
}

// Klasse für NOR
class NorGate extends BinaryGate {
    NorGate(LogicGate input1, LogicGate input2) {
        this.input1 = input1;
        this.input2 = input2;
    }

    @Override
    public boolean output() {
        return !(input1.output() || input2.output());
    }

    public NorGate copy() {
        return new NorGate(input1.copy(), input2.copy());
    }
}

Die Klasse Inputgate dient dafür einen Eingang für logische Schaltungen zur Verfügung zu stellen.

Java:
// Klasse für statische Eingaben (Konstanten)
class InputGate implements LogicGate {
    boolean value;
    String name;

    InputGate(boolean value, String name) {
        this.value = value;
        this.name = name;
    }

    void setValue(boolean value) {
        this.value = value;
    }

    @Override
    public boolean output() {
        return value;
    }

    @Override
    public String toString() {
         return name;
    }

    public InputGate copy() {
        return new InputGate(value, name);
    }
}

Jetzt kommen wir zur problematischen Klasse... die CompsiteGate Klasse. Hier können Eingängen in einer ArrayList gesetzt werden und Outputs in einer ArrayList gesetzt werden. In der Outputs ArrayList wird an jedem Index, die Schaltung gespeichert, die dem entsprechenden Ausgang einer komplexen Schaltung zugrunde liegt. (Bei einem Halbaddierer beim ersten Ausgang (Index 0) für die Summe also die Schaltung für ein XOR und am Index 1 also ein einzelnes And Gate). Das Problem ist, dass ich keine Methode habe, die Eingänge in der Inputs Liste den Schaltungen in der Outputs Liste zur Verfügung zu stellen. Ich lasse bewusst alle meine Ansätze weg, die ich hatte, da mich sowas persönlich nur selbst verwirrt, wenn ich neue Ansätze suche.


Java:
class CompositeGate implements LogicGate {
    List<LogicGate> inputs;
    List<LogicGate> outputs;

    CompositeGate(List<LogicGate> inputs, LogicGate... outputs) {
        this.inputs = inputs;
        this.outputs = new ArrayList<>();
        for (LogicGate logicGate : outputs) {
            this.outputs.add(logicGate);
        }
    }

    CompositeGate(List<LogicGate> inputs, List<LogicGate> outputs) {
        this.inputs = inputs;
        this.outputs = outputs;
    }

    @Override
    public boolean output() {
        // assert outputs.size() == 1 : "Diese Methode funktioniert für komplexe Schaltungen mit mehr als einem Ausgang nicht";
        return getOutput(0);
    }

    public boolean getOutput(int index) {
        assert index >= 0 && index < outputs.size() : "Output nicht vorhanden";
        return outputs.get(index).output();
    }

    public void setInputs(List<LogicGate> inputs) {
        this.inputs = inputs;
    }

    void setOutputs(List<LogicGate> outputs) {
        this.outputs = outputs;
    }

    public CompositeGate copy() {
        List<LogicGate> copyedInputs = new ArrayList<LogicGate>();
        for (LogicGate input : inputs) {
            copyedInputs.add(input.copy());
        }
        List<LogicGate> copyeOutputs = new ArrayList<LogicGate>();
        for (LogicGate output : outputs) {
            copyeOutputs.add(output.copy());
        }
        return new CompositeGate(copyedInputs, copyeOutputs);
    }
}

Es folgt noch die CircuteManager-Klasse, die es ermöglicht eine Schaltung zu speichern und wieder zu laden. Dafür wird eine HashMap genutzt.

Java:
class CircuitManager {
    Map<String, CompositeGate> circuitMap;

    CircuitManager() {
        circuitMap = new HashMap<>();
    }

    void saveCircuit(String name, CompositeGate circuit) {
        circuitMap.put(name, circuit);
    }

    CompositeGate loadCircuit(String name) {
        return circuitMap.get(name).copy();
    }

    boolean circuitExists(String name) {
        return circuitMap.containsKey(name);
    }
}

Zu guter letzt noch einige Anwendungsbeispiele. Beachte hierbei, dass zum Testen verbotenerweise System.out.println() genutzt wurde.

Java:
CircuitManager manager = new CircuitManager();

// Beispiel: Halbaddierer
InputGate x1 = new InputGate(true, "x1");
InputGate x2 = new InputGate(false, "x2");

CompositeGate xor = new CompositeGate(Arrays.asList(x1, x2), new NandGate(x1, x2), new OrGate(x1, x2));
manager.saveCircuit("Xor", xor);

CompositeGate laodedXor = manager.loadCircuit("Xor");

CompositeGate halfAdder = new CompositeGate(Arrays.asList(x1, x2), laodedXor, new AndGate(x1, x2));
System.out.println("HalfAdder Sum = " + halfAdder.getOutput(0));
System.out.println("HalfAdder Carry = " + halfAdder.getOutput(1));

manager.saveCircuit("HalfAdder", halfAdder);

// Testen der gespeicherten Schaltung
CompositeGate loadedHalfAdder = manager.loadCircuit("HalfAdder");
assert loadedHalfAdder.getOutput(0) == true;
assert loadedHalfAdder.getOutput(1) == false;

// Beispiel: Volladdierer
InputGate a1 = new InputGate(false, "a1");
InputGate b1 = new InputGate(false, "b1");
InputGate cin = new InputGate(false, "cin");

CompositeGate halfAdder1 = manager.loadCircuit("HalfAdder");
halfAdder1.setInputs(Arrays.asList(a1, b1));
System.out.println("Halfadder 1 Inputs: " + halfAdder1.inputs);
for (LogicGate gate : halfAdder1.inputs) {
    System.out.println(gate.output());
}
CompositeGate halfAdder2 = manager.loadCircuit("HalfAdder");
InputGate temp = new InputGate(halfAdder1.getOutput(0), "Sum HA 1");
System.out.println("Halfadder 2 Inputs: " + halfAdder2.inputs);
for (LogicGate gate : halfAdder2.inputs) {
    System.out.println(gate.output());
}
System.out.println("Halfadder 1 Inputs: " + halfAdder1.inputs);
assert halfAdder1.inputs.equals(Arrays.asList(a1, b1));



System.out.println("\nHalfAdder 1:");
for (LogicGate gate : halfAdder1.inputs) {
    System.out.println(gate.output());
}
System.out.println("halfAdder1 sum = " + halfAdder1.getOutput(0) + "\nhalfAdder1 carry = " + halfAdder1.getOutput(1));

LogicGate sum = new OrGate(halfAdder1.getOutput(0), halfAdder2.getOutput(0));
LogicGate carry = new OrGate(halfAdder1.getOutput(1), halfAdder2.getOutput(1));

CompositeGate fullAdder = new CompositeGate(Arrays.asList(a1, b1, cin), sum, carry);
assert fullAdder.getOutput(0) == false;
assert fullAdder.getOutput(1) == false;
Dieser Code wird beim Laden der Quellcodedatei mit in die JShell geladen. Bei einem korrekt verschalteten fullAdder müsste die asserts Stimmen. Tue sie aber nicht. Der tatsächliche Output des Fulladders am Index 1 ist true, da dem ersten Halbdaddierer immer noch die InputGates x1 und x2 zugrunde liegen.
Falls jemand eine Idee für dieses Problem hat, gerne her damit. Ich persönlich bin mittlerweile komplett Überfragt, da alle meine ursprünglichen Ideen, einfach gar nichts getan haben.
 

Robert Zenz

Top Contributor
Ich bin mir nicht ganz sicher ob ich dein Problem richtig verstanden haben. aber waere es nicht einfach korrekt wenn output ebenfalls eine Liste liefert standardmaeaszig? Und muesste CompositeGate nicht einfach setInputX ueberschreiben und dort die getInputX() Methoden aufrufen der "inneren" Gatter? Dann macht jemand compositeGateInstance.setInput1(blabla) und greift transparent direkt auf das erste Gatter zu welches hinter diesem Eingang liegt. Natuerlich musste du dafuer auch entsprechende Klassen akzeptieren.
 

KonradN

Super-Moderator
Mitarbeiter
Also ich sehe nicht, wie das überhaupt funktionieren kann.

Du erzeugst ja mit copy() immer eine neue Kopie.
Bei den Eingängen hast Du also ganz neue Instanzen.
Und bei einem gate hast Du eine Kopie mit den gleichen Inputs.

Also CompositeGate aus EIngängen E1 und E2 und dann einem Output AND(E1, E2):Nach dem Copy hast Du
E1Copy, E2Copy als Eingänge und ein ANDCopy(E1, E2)

Das kann also gar nicht funktionieren.

Und wenn man sich eine Lösung überlegt, dann sind die Namen eine sinnvolle Sache.
Du musst dann aber in der Lage sein, einem InputGate ein anderes Gate zuzuordnen. Derzeit hast Du da nur ein value.

Und dann kannst Du das XOR erstellen und es hat 2 Eingänge: "X1" und "X2".
Und dann kannst Du dem Eingang "X1" ein anderes Gate zuordnen. Das muss aber über den Namen gehen. Natürlich kannst Du da nicht einfach eine neue List setzen und da die vorhandene List überschreiben....

Spiel es mit Stift und Zettel durch! Alleine schon, weil die Namen auch nur für das CompositeGate eindeutlig sind. Klar: Du kannst zwei XOR haben und beide haben "X1" und "X2" als Eingänge. Und wenn Du die in einem XOR zusammen fügst, dann hast Du das äußere XOR mit "X1" und "X2" und jedes der beiden XOR hat auch so benannte Eingänge.

Und der Aufbau ist zu überdenken. Eine CompositeGate sollte als Eingänge nur wirkliche Eingänge haben. Denen wird dann später etwas zugewiesen. (Ist auch klar - ich habe bisher an einem Bauteil auch nur andere Bauteile angeschlossen - ich habe nie einen IC aufgefräst um den Eingang auszutauschen :) )
 

Barista

Top Contributor
CompositeGate halfAdder = new CompositeGate(Arrays.asList(x1, x2), laodedXor, new AndGate(x1, x2));

Der tatsächliche Output des Fulladders am Index 1 ist true, da dem ersten Halbdaddierer immer noch die InputGates x1 und x2 zugrunde liegen.

Beim Aufbau (Konstruktoraufruf) verweist Du 2mal auf die Inputs x1 und x2.

Nach meiner Meinung verletzt Du dadurch die Kapselung (nicht im exakten Sinne der OOP, sondern für diesen konkreten Fall):

1720824696184.png

Die inneren Gatter dürfen sich nur auf innere Eingänge und Ausgänge beziehen.

Die Frage ist nun, wie kann man dass programmieren?

Meine Schnellschuss-Idee ist, eine komplette eigene Klassen-Hierarchie für innere Gatter zu machen, also InnerAnd, InnerNand usw..

Diese Inner* verwenden dann innere Inputs und Outputs.

Eine (nicht so gute) Möglichkeit wäre, dass sich die Inputs über einen Index (Nummer mit 0 beginnend) auf Listen oder Arrays (also über Index ansprechbar) beziehen.

Also:

Java:
CompositeGate halfAdder = new CompositeGate(Arrays.asList(InnerInput(0), InnerInput(1)), new XorGate(InnerInput(0), InnerInput(1)), new AndGate(InnerInput(0), InnerInput(1)));

Dann kommt danach ein Aufruf, der jeweils die inneren Inputs mit den äußeren Inputs und die inneren Outputs mit den äußeren Outputs verbindet.

Java:
halfAdder.setInputs(x1, x2);

Besser für die Übersichtlichkeit wären dann Namen (Strings), in diesem Fall wird dann die Zuordnung innen-außen dann über Maps gemacht.

Java:
CompositeGate halfAdder = new CompositeGate(Arrays.asList(InnerInput("I0"), InnerInput("I1")), new XorGate(InnerInput(0), InnerInput(1)), new AndGate(InnerInput("I0"), InnerInput("I1")));

OK, für Anfänger ist das schon ziemlich anspruchsvoll.
 
Zuletzt bearbeitet:

KonradN

Super-Moderator
Mitarbeiter
Wenn man es sich überlegt: Das Kernproblem fängt schon an, dass CompositeGate ein LogicGate sein soll, was ja nicht funktioniert. Ein CompositeGate ist nur eine Sammlung von LogicGates und dabei werden Eingänge separat verfügbar gemacht. Also auch keine Vererbung dahin. Und Du verwendest CompositeGate selbst nicht in der Schaltung. Du nutzt nur die LogicGates aus dem CompositeGate (Also die Eingänge und die LogicGates (Ausgänge)

Aber schauen wir uns das Copy einmal an - bei dem einfachsten Fall: Wir haben einen sauberen Baum von LogicGates:
Den kann ich nun kopieren. Alles wird verdoppelt. Ok, funktioniert. Alles super.

Nun habe ich aber die Eingänge auch noch separat. Ich brauche also die neuen Eingänge.
Also merke ich mir die neuen Eingänge in einer Map alt -> neu. Dann kann ich Eingänge durchgehen und durch neue ersetzen. Problem mit Eingängen gelöst.

Aber ich habe ja keinen Baum. Es kann ja auch Kreise und so geben. Einfachster Fall: Ich habe einen Eingang mehrfach verwendet. Aber Flip Flop: Ein Ausgang geht zu einem Eingang zurück. Also muss ich schauen: Ist ein Element bereits kopiert worden? Wenn ja, dann nehme ich die vorhandene Kopie.
Ich merke mir also jedes Element, das ich kopiere und kann so immer prüfen: Wurde es bereits kopiert?

Und dann kommen wir zu dem Punkt: Output berechnen: Sobald wir Schleifen haben, dann können wir das auch nicht mehr so handhaben wie Du es gemacht hast. Denn sobald ein Kreis vorhanden ist, hast Du eine Endlosschleife. Hier braucht man dann auch Überlegungen, wie man das machen will. Zumal auch die Frage ist: haben wir einen stabilen Zustand? Oder habe ich etwas, das ständig wechselt? Also eine Schaltung, die ständig umschaltet:

Ich habe eine NOT Schaltung, dessen Ausgang ich auch zum Eingang schalte. Eingang false: Ausgang ist true und setzt daher Eingang auf true. Das setzt den Ausgang auf false. Das wiederum geht ja auch zum Eingang und das sorgt dann für ein erneutes umschalten. Die Schaltung wäre also nicht stabil. (Da ist jetzt nur einmal angedeutet, was ich meine.)

Und dann sehe ich noch ein Problem: Ich kann jetzt bei einem Eingang nur eine Schaltung anschließen. Abe das ist doch nicht korrekt. Beispiel Flip Flop: Ein Ausgang geht zu einem Eingang zurück. Bei dem Inneren Gatter habe ich also zwei Dinge: Das InputGate und auch ein Ausgang eines LogicGates. Das muss also auch abgebildet werden können. (Oder macht man das intern einfach als eine ODER Verbindung, da eine angelegte Spannung (oder die höhere Spannung) sozusagen "siegt", d.h. ein true setzt sich durch?)
 

Barista

Top Contributor
Mir war dazu auch noch folgendes eingefallen:

In der Welt der Schaltungen (Addierer usw) sind die Grundgatter (AND, OR, NOT usw.) mit nur einem Ausgang ein Sonderfall.

In Java können Methoden nur einen Rückgabewert haben, in Python kann man mehrere Rückgabewerte kommagetrennt aufschreiben.

In Java packt man mehrere Ergebnisse eben in eine Objekt (ok, das darf dann nicht null sein, ignorieren wir das erst mal).

Man könnte also die Inputs und die Outputs in jeweils in einem Objekt verpacken und die Inputs gemeinsam als Parameter übergeben und entsprechend die Outputs erhalten.
 

Barista

Top Contributor
Zumal auch die Frage ist: haben wir einen stabilen Zustand? Oder habe ich etwas, das ständig wechselt? Also eine Schaltung, die ständig umschaltet:
Es handelt sich um eine Übung, wahrscheinlich wird es nicht so weit gehen.

Nach meinem Wissen lösen Digitalschaltungen dies über einen externen Taktgeber.

Der externe Taktgeber sorgt dann für definiertes Verhalten in der Zeit.
 

Barista

Top Contributor
Und dann sehe ich noch ein Problem: Ich kann jetzt bei einem Eingang nur eine Schaltung anschließen. Aber das ist doch nicht korrekt. Beispiel Flip Flop: Ein Ausgang geht zu einem Eingang zurück.

Bei einem Flip-Flop aus zwei NAND-Gattern werden die Rückkopplungen an anderen Gatter-Eingängen als die Schaltungs-Eingänge angeschlossen.

Man kann an einen Ausgang mehrere Eingänge anschließen, begrenzt durch die Ausgangsleistung gegenüber dem Energie-Bedarf der Eingänge.

Man kann aber nie an einen Eingang mehrere Ausgänge anschließen, das würde zum Kurzschluss führen, also Smoke-Test.
 

KonradN

Super-Moderator
Mitarbeiter
Ok, dann hatte ich das falsch in Erinnerung … E-Technik war ein Semester und das ist jetzt fast 30 Jahre her … aber macht Sinn - denn Ausgang kann direkt Masse oder die Versorgungsspannung sein - wenn man also etwas nachdenkt, dann ist es logisch :)
 

KonradN

Super-Moderator
Mitarbeiter
Hmm … Haut das auch hin bei Bus-Leitungen? Man hat ja dann nicht die dedizierten Ein-/Ausgänge … wenn also eine einfache CPU simuliert wird, dann hat man Datenleitungen auf die ja CPU als auch Speicher Daten geben können … Ach jee … technische Informatik … auch viel zu lange her :)

Ist aber Off Topic … also evtl. nicht gut, das zu sehr zu thematisieren …
 

Jw456

Top Contributor
Deshalb sind ja alle Bus Chips Tri-State und haben einen cs Chipselect Eingang.
Der Buss ist trotzdem nur binär Irgend ein Chip ist immer aktiv.
 

Neue Themen


Oben