基於事件驅動的邏輯電路仿真實現

數據結構

首先觀察一個簡單的電路:
image.png
不難發現,根據信號的傳導方向,能夠將其轉換成一個有向圖:
image.png
其中小寫字母的頂點表示導線的結點,擁有電平狀態,大寫字母表示邏輯門,擁有邏輯運算的功能。
注意導線結點不能只做爲邊,由於可能會有多個邏輯門的端口連在一塊兒,好比:
image.png
這個有向圖裏面存在幾個特色,一是導線結點不容許接受多個輸入,二是任意一條路徑裏面,導線結點和邏輯門必定會交替出現。
另外這個圖裏面有可能出現環,好比SR鎖存器:
image.png
對應的圖裏面就存在環路:
image.png
肯定好了是個有向圖就好辦了,能夠用鄰接表來表示。
這裏採用Java實現。
首先對於總的電路來講,須要記錄全部的導線和邏輯門,雖然是在同一個圖上,但爲了方便,將導線結點和邏輯門分別存儲在哈希表中,而且每一個結點都擁有惟一的一個id:java

class Circuit{
    HashMap<Integer, Node> nodeTable = new HashMap<>();
    HashMap<Integer, Gate> gateTable = new HashMap<>();
    ...
}

對於導線結點,須要記錄一下相鄰的全部邏輯門ID,而後用一個變量表示當前電平狀態。node

class Node{
    Set<Integer> gateIDs = new HashSet<>();
    boolean state;
}

對於邏輯門,須要分別記錄輸入和輸出結點的ID,以及一個求值器:算法

class Gate{
    int[] inputNodeIDs;
    int[] outputNodeIDs;
    Evaluator evaluator;
    ...
}

另外就是輸出端和輸入端須要處理,這裏把輸入端當成只有一個輸出口的特殊邏輯門,輸出端當成只有一個輸入口的特殊邏輯門。輸入端具備一個狀態,輸出端具備一個listener,用來在狀態更新的時候回調。數據結構

class InputGate extends Gate {
    boolean state;
    ...
}
interface OutputListener{
    void OnUpdate(boolean state);
}

class OutputGate extends Gate {
    OutputListener listener;
    ...
}

鏈接

而後對Circuit類進行完善,雖然電路內部用node表示了鏈接狀態,可是爲了簡化對外接口,只提供邏輯門的添加、刪除和鏈接函數,導線結點node由Circuit內部維護,而且全部的邏輯門都經過ID進行索引,如:函數

class Circuit {
    ...
    int addGate(OperType type) {...}
    int addInputGate(){...}
    int addOutputGate(){...}
    void setInputState(int id, boolean state){...}
    void setOutputListener(int id, OutputListener listener){...}
    void removeGate(int id) {...}
    void connectGate(int src_gate_id, int src_out_pin, int dst_gate_id, int dst_in_pin) {...}
    ...
}

經過這一系列的函數,能夠任意構造邏輯電路,好比最開始的電路構造代碼以下:測試

Circuit circuit = new Circuit();
        int a = circuit.addInputGate();
        int b = circuit.addInputGate();
        int c = circuit.addInputGate();
        int d = circuit.addInputGate();
        int A = circuit.addGate(OperType.And);
        int B = circuit.addGate(OperType.And);
        int C = circuit.addGate(OperType.Not);
        int D = circuit.addGate(OperType.Or);
        int h = circuit.addOutputGate();
        circuit.connectGate(a, 0, A, 0);
        circuit.connectGate(b, 0, A, 1);
        circuit.connectGate(c, 0, B, 0);
        circuit.connectGate(d, 0, B, 1);
        circuit.connectGate(A, 0, C, 0);
        circuit.connectGate(C, 0, D, 0);
        circuit.connectGate(B, 0, D, 1);
        circuit.connectGate(D, 0, h, 0);

將全部的邏輯門和結點輸出成graphvz源文件,能夠很方便查看鏈接狀態:
o.pngui

求值

電路仿真的核心在於如何求值,最簡單的方法是把輸入結點加入隊列,而後用廣度優先的方法依次求值,但這會涉及到大量的運算,即便是一個輸入的改變也會致使與之相關的全部邏輯門從新求值,好比一個與門,輸入狀態從01變成00,即便這個邏輯門的輸出沒變,也會致使大量電路的從新求值,其次對於有環的電路也很差處理。一種更好的方法是基於事件驅動的方法,只在當結點狀態發生改變的時候對與之相關的邏輯門進行求值,而且只有當這些邏輯門的輸出發生改變的時候,才產生新的事件繼續下去,直到全部的事件都被處理完成。
因爲數據結構的設計上面,每一個結點都保存了狀態,所以事件產生的檢測就較爲容易了,只須要檢測邏輯門的輸出與對應的結點狀態是否是一致便可。注意這裏把輸入端口也看成一個邏輯門,所以能夠統一對待。
具體實現來講,須要維護兩個隊列,一個邏輯門隊列gateQueue,保存待求值的邏輯門,一個結點隊列nodeQueue,用來標記狀態發生改變的結點。這樣一來,算法就很簡單了:lua

1.把全部已改變的輸入端加入gateQueue
2.從gateQueue取出全部gate進行求值,當求值的結果與輸出端的node狀態不一致時,更新node的狀態,並將其加入nodeQueue
3.當nodeQueue爲空時,結束,不然將輸入口與之想連的gate加入gateQueue(已有就不加)
4.重複二、3步驟

添加邏輯門以及設置輸入狀態的時候把邏輯門加入gateQueue,刪除邏輯門的時候從gateQueue中刪除,而後進行求值,清空gateQueue,這樣就能維護好一個電路的狀態了。
具體的代碼實現來講,使用LinkedHashSet來存nodeQueue和gateQueue,如:spa

void process() {
        while (!gateQueue.isEmpty()) {
            LinkedHashSet<Integer> nodeQueue = new LinkedHashSet<>();
            for (int gid : gateQueue) {
                Gate gate = gateTable.get(gid);
                Set<Integer> changed = gate.Evaluate(nodeTable);
                nodeQueue.addAll(changed);
            }
            gateQueue.clear();
            for (int nid : nodeQueue) {
                Node node = nodeTable.get(nid);
                for (int gid : node.gateIDs) {
                    Gate gate = gateTable.get(gid);
                    if (gate.hasInput(nid))
                        gateQueue.add(gid);
                }
            }
            nodeQueue.clear();
        }
    }

測試

把電路中的c輸入口置爲1,而後求值,結果以下:
o.png
能夠看到已經獲得了正確的結果。設計

構造一個sr latch:

int A = circuit.addGate(OperType.AndNot);
        int B = circuit.addGate(OperType.AndNot);
        int s = circuit.addInputGate();
        int r = circuit.addInputGate();
        int q = circuit.addOutputGate();
        int qbar = circuit.addOutputGate();
        final boolean[] result = new boolean[2];
        circuit.setOutputListener(q, (state) -> {
            result[0] = state;
        });
        circuit.setOutputListener(qbar, (state) -> {
            result[1] = state;
        });

        circuit.connectGate(s, 0, A, 0);
        circuit.connectGate(r, 0, B, 1);
        circuit.connectGate(A, 0, q, 0);
        circuit.connectGate(B, 0, qbar, 0);
        circuit.connectGate(B, 0, A, 1);
        circuit.connectGate(A, 0, B, 0);

鏈接圖以下:
o.png
而後分別用01,11,10測試,01應該對qbar置位,11應該保持,10應該對q置位:

circuit.setInputState(s, true);
        circuit.process();
        System.out.println(Arrays.toString(result));

        circuit.setInputState(r, true);
        circuit.process();
        System.out.println(Arrays.toString(result));

        circuit.setInputState(s, false);
        circuit.process();
        System.out.println(Arrays.toString(result));

結果爲:

[false, true]
[false, true]
[true, false]

符合預期。

相關文章
相關標籤/搜索