首先觀察一個簡單的電路:
不難發現,根據信號的傳導方向,能夠將其轉換成一個有向圖:
其中小寫字母的頂點表示導線的結點,擁有電平狀態,大寫字母表示邏輯門,擁有邏輯運算的功能。
注意導線結點不能只做爲邊,由於可能會有多個邏輯門的端口連在一塊兒,好比:
這個有向圖裏面存在幾個特色,一是導線結點不容許接受多個輸入,二是任意一條路徑裏面,導線結點和邏輯門必定會交替出現。
另外這個圖裏面有可能出現環,好比SR鎖存器:
對應的圖裏面就存在環路:
肯定好了是個有向圖就好辦了,能夠用鄰接表來表示。
這裏採用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源文件,能夠很方便查看鏈接狀態:
ui
電路仿真的核心在於如何求值,最簡單的方法是把輸入結點加入隊列,而後用廣度優先的方法依次求值,但這會涉及到大量的運算,即便是一個輸入的改變也會致使與之相關的全部邏輯門從新求值,好比一個與門,輸入狀態從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,而後求值,結果以下:
能夠看到已經獲得了正確的結果。設計
構造一個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);
鏈接圖以下:
而後分別用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]
符合預期。