器設(shè)計詳解:從JK觸發(fā)器狀態(tài)表到Verilog仿真全流程)
簡介一份面向數(shù)字電子線路課程的實驗指導文檔圍繞計數(shù)器邏輯功能與設(shè)計展開適合高校電類專業(yè)學生及數(shù)字電路學習者閱讀。內(nèi)容覆蓋同步/異步、二進制/十進制/任意進制、加法/減法/可逆計數(shù)器等基礎(chǔ)分類重點講解反饋歸零法和反饋置數(shù)法設(shè)計任意進制計數(shù)器并結(jié)合74HC161、74HC390等中規(guī)模集成芯片以簡易數(shù)字鐘為例呈現(xiàn)六十進制、二十四進制計數(shù)器的級聯(lián)構(gòu)建方法。資源共1個docx文件大小207KB文檔包含實驗?zāi)康?、儀器設(shè)備、預(yù)習要求、原理說明、芯片結(jié)構(gòu)圖與引腳圖、實驗步驟和思考題等完整環(huán)節(jié)既可作為實驗預(yù)習資料也可在設(shè)計接線時提供方法與思路參考。已有288人學習該資源適合需要完成同類數(shù)字電路實驗并希望深入理解計數(shù)功能與設(shè)計方法的讀者。1. 2-5 計數(shù)器是什么先把這個邏輯功能翻譯成電路術(shù)語拿到“數(shù)字電子線路基礎(chǔ)2-5 計數(shù)器邏輯功能和設(shè)計”這個題目時最容易踩的坑是把它當成一個簡單的“從 2 數(shù)到 5 再停下來”的計數(shù)器。實際上數(shù)字電路里說 2-5 計數(shù)器通常指一個同步計數(shù)器讓狀態(tài)按 2→3→4→5→2 循環(huán)每來一個時鐘上升沿前進一次本質(zhì)是一個模 4 計數(shù)器只是有效狀態(tài)從 2 開始、到 5 結(jié)束。這種序列控制邏輯在電子鐘分頻、步進電機換相、交通燈時序和簡單狀態(tài)機里非常常見。適合的人群是正在上數(shù)字電子線路課的初學者以及想做課程設(shè)計但不想只抄仿真圖的硬件新人。這篇文章的目標很直接幫你把“邏輯功能”拆成狀態(tài)表再用 JK 觸發(fā)器設(shè)計出可復(fù)現(xiàn)的電路最后用仿真驗證它真的在循環(huán)而不是停在某個非法狀態(tài)里出不來。2. 狀態(tài)設(shè)計2→3→4→5→2 的循環(huán)怎么變成狀態(tài)表2.1 三比特編碼2、3、4、5 到底對應(yīng)哪一組 Q2Q1Q0數(shù)字電路的計數(shù)器最終要落到觸發(fā)器的 Q 端上。2-5 這個序列里一共有四個有效狀態(tài)理論上兩個觸發(fā)器就夠了但兩個觸發(fā)器只能表示 0 到 3如果直接把 2、3、4、5 當作數(shù)值兩個觸發(fā)器根本裝不下 4 和 5。因此常見做法是用三個觸發(fā)器 Q2、Q1、Q0把狀態(tài)編碼成三位二進制計數(shù)序號計數(shù)值Q2 Q1 Q0020 1 0130 1 1241 0 0351 0 14回到 20 1 0注意這里的狀態(tài)編碼不是連續(xù)的 000→001→010→011而是 010→011→100→101 循環(huán)。為什么要這樣編碼因為這樣最直觀地對應(yīng)輸出數(shù)值Q2Q1Q0 本身就是二進制數(shù)010 等于十進制 2011 等于 3100 等于 4101 等于 5。后續(xù)接數(shù)碼管顯示或者接比較器判斷數(shù)值時不需要再額外做復(fù)雜的算術(shù)轉(zhuǎn)換。剩下的 000、001、110、111 四個狀態(tài)是非法狀態(tài)。設(shè)計時我一般不會直接忽略它們否則仿真跑得通、上電后可能卡在 000 或者 111 里永遠進不了循環(huán)。真正可靠的計數(shù)器設(shè)計必須把非法狀態(tài)也拉回到合法狀態(tài)這就是后面要說的自啟動設(shè)計。2.2 把狀態(tài)轉(zhuǎn)換寫成激勵表JK 觸發(fā)器對每個翻轉(zhuǎn)需要什么輸入有了狀態(tài)編碼下一步是列出狀態(tài)轉(zhuǎn)換關(guān)系。每來一個時鐘上升沿電路從當前狀態(tài)跳到下一狀態(tài)。四個合法轉(zhuǎn)換是010→011011→100100→101101→010。再用 JK 觸發(fā)器的特性逐位分析。JK 觸發(fā)器的行為是J1、K0 時置位為 1J0、K1 時清零為 0J1、K1 時翻轉(zhuǎn)J0、K0 時保持。所以對于某一位 Q如果它要從 0 變 1那 J 必須為 1如果要從 1 變 0那 K 必須為 1如果保持原值則 J 和 K 中至少有一個為 0具體是 00 還是 10 還是 01 都可以通常填 X 表示任意。下面是三個觸發(fā)器的完整性激勵表當前狀態(tài)下一狀態(tài)J2 K2J1 K1J0 K00100110 XX 01 X0111001 XX 1X 1100101X 00 X1 X101010X 11 XX 1以 011→100 這行為例Q2 從 0 變 1所以 J21Q1 從 1 變 0所以 K11Q0 從 1 變 0所以 K01。其他位填 X這不代表引腳可以懸空而是說化簡時可以靈活選擇 0 或 1用來湊最簡邏輯式。2.3 先把可運行的計數(shù)器模塊搭出來一個 Verilog 最小框架狀態(tài)表寫完我先不急著化簡而是用 Verilog 把 JK 觸發(fā)器和計數(shù)器骨架搭出來。這樣做的好處是后面每一步化簡出的方程都可以直接替換進代碼里跑仿真不會等到接線完才發(fā)現(xiàn)邏輯矛盾。先寫一個行為級 JK 觸發(fā)器module jk_ff ( input wire clk, input wire rst_n, input wire j, input wire k, output reg q ); always (posedge clk or negedge rst_n) begin if (!rst_n) begin q 1b0; end else begin case ({j, k}) 2b10: q 1b1; // J1,K0 置位 2b01: q 1b0; // J0,K1 清零 2b11: q ~q; // J1,K1 翻轉(zhuǎn) default: q q; // 保持 endcase end end endmodule這段代碼里的 case 語句直接對應(yīng) JK 觸發(fā)器的四種行為。注意 default 分支不能省略否則 j/k 為 00 時 q 的行為在仿真里可能沒有定義綜合時也容易多出鎖存器或不定態(tài)。再寫計數(shù)器頂層module counter_2_5 ( input wire clk, input wire rst_n, output wire [2:0] q ); wire j2, k2, j1, k1, j0, k0; wire [2:0] q; // 先臨時用最保守的激勵保持為低后面用卡諾圖化簡結(jié)果替換 assign j2 1b0; assign k2 1b0; assign j1 1b0; assign k1 1b0; assign j0 1b0; assign k0 1b0; jk_ff u2 (.clk(clk), .rst_n(rst_n), .j(j2), .k(k2), .q(q[2])); jk_ff u1 (.clk(clk), .rst_n(rst_n), .j(j1), .k(k1), .q(q[1])); jk_ff u0 (.clk(clk), .rst_n(rst_n), .j(j0), .k(k0), .q(q[0])); endmodule復(fù)位后三個觸發(fā)器輸出都是 000所以計數(shù)器會先從 000 跳到 010再進入 2→3→4→5 循環(huán)。這里把復(fù)位統(tǒng)一做成低電平異步復(fù)位所有觸發(fā)器在一個復(fù)位信號控制下回到 000避免不同觸發(fā)器復(fù)位時間不一致導致狀態(tài)錯位。下一步就是使用激勵表求出這六根線 j2、k2、j1、k1、j0、k0 的表達式。3. 從狀態(tài)表到邏輯圖卡諾圖化簡和接線3.1 用卡諾圖圈 JK 激勵J2、K2、J1 怎么圈最少卡諾圖是手算組合邏輯最快的方法。我以 J2 為例演示化簡過程。J2 只在當前狀態(tài) 011 時為 1其他合法狀態(tài)為 0 或 X非法狀態(tài)里 110、111 是 X000、001 我們暫時希望它是 0。按 Q2Q1 作行、Q0 作列可得到下面的三變量卡諾圖J2 的卡諾圖 Q00 Q01 Q2Q100 0 0 Q2Q101 0 1 Q2Q111 X X Q2Q110 X X卡諾圖圈“1”和“X”時盡量圈 2、4、8 個相鄰格。這張圖里 011 這個 1 與 111 這個 X 相鄰可以圈成一行化簡結(jié)果是 Q1·Q0。所以 J2 Q1·Q0。K2 的卡諾圖同樣處理K2 需要在 Q2Q1Q0101 時為 1為了讓非法狀態(tài) 110、111 也能被清回合法狀態(tài)K2 要在這兩個非法狀態(tài)也為 1圈出來就是 Q2·(Q1Q0)。3.2 六條激勵方程和自啟動校驗按同樣的卡諾圖方法把所有激勵項圈完得到一組可以直接接線使用的方程信號化簡表達式作用位說明J2Q1·Q0只有 Q1、Q0 同時為 1 時把 Q2 置 1K2Q2·(Q1Q0)Q2 為 1 且 Q1 或 Q0 為 1 時把 Q2 清 0J1~Q1·(~Q2Q0)讓 Q1 在狀態(tài) 000、001、101 時置 1K1~Q2·Q1·Q0從 011 轉(zhuǎn)到 100 時把 Q1 清 0J0Q2 XOR Q1兩個高位不同時讓 Q0 置 1K0Q0Q0 為 1 時下一個周期必翻轉(zhuǎn)這組方程和上一章激勵表的合法狀態(tài)完全一致同時還處理了非法狀態(tài)。比如 000 狀態(tài)下J1 為 1、K1 為 0一個時鐘脈沖后 Q1 變 1狀態(tài)變成 010也就是回到計數(shù)值 2。111 狀態(tài)下 J2、K2 同時為 1Q2 翻轉(zhuǎn)成 0J0、K0 同時為 1Q0 翻轉(zhuǎn)成 0Q1 保持 1于是也落到 010。四個非法狀態(tài)都會在 1 到 2 個時鐘周期內(nèi)回到合法循環(huán)里這就是自啟動。寫一小段 Python 腳本可以快速驗證狀態(tài)轉(zhuǎn)移def jk_next(q, j, k): if j and not k: return 1 if k and not j: return 0 if j and k: return 1 - q return q def next_state(q2, q1, q0): j2 q1 and q0 k2 q2 and (q1 or q0) j1 (not q1) and ((not q2) or q0) k1 (not q2) and q1 and q0 j0 q2 ^ q1 k0 q0 return ( jk_next(q2, j2, k2), jk_next(q1, j1, k1), jk_next(q0, j0, k0), ) seen {} for s in range(8): q2 (s 2) 1 q1 (s 1) 1 q0 s 1 path [] while (q2, q1, q0) not in path: path.append((q2, q1, q0)) q2, q1, q0 next_state(q2, q1, q0) print(f初始 {path[0]} - 第一步 {path[1] if len(path)1 else } 循環(huán)長度 {len(path)})這段腳本把八個狀態(tài)全部跑一遍如果每個狀態(tài)的路徑最終都能落進 010→011→100→101 這個環(huán)就說明自啟動沒問題。參數(shù)上要注意Python 里的 and/or 返回的是布爾值JK 觸發(fā)器的 q 是 0 或 1所以在 jk_next 里直接返回 0/1不會出現(xiàn) None 或字符串問題。3.3 邏輯圖接線從觸發(fā)器輸出到 2-5 輸出端得到激勵方程后實際接線就是把門電路連接到三個 JK 觸發(fā)器的 J/K 端。J2 用一只二輸入與門輸入接 Q1 和 Q0K2 用一只二輸入或門接 Q1、Q0再和 Q2 相與J1 需要兩只門組合先讓 Q2 取反后與 Q0 相或再與 Q1 的反相相與K1 是三輸入與門J0 用異或門K0 直接接 Q0。整體是簡單的 SSI 門電路不需要鎖存器或額外寄存器。輸出端不需要二次譯碼因為 Q2Q1Q0 本身就是二進制編碼的 2、3、4、5。若你想接數(shù)碼管需要再送 BCD 轉(zhuǎn)七段譯碼器因為數(shù)碼管驅(qū)動要求的是 8421 BCD 碼而不是原始三位二進制碼。進位信號則可取 K2 的輸出因為每當 Q2 被清零時就意味著狀態(tài)要從 5 回到 2這個時刻就是一輪循環(huán)結(jié)束。4. 仿真驗證讓計數(shù)器按預(yù)期跑完一輪循環(huán)4.1 最小測試平臺時鐘、復(fù)位和一輪 2→3→4→5→2邏輯圖畫得對不對最快的方法是跑行為仿真。測試平臺最小化到只要三樣東西時鐘發(fā)生器、復(fù)位信號、計數(shù)器實例。下面是一個完整的 testbenchmodule tb_counter_2_5; reg clk 0; reg rst_n 0; wire [2:0] q; counter_2_5 uut ( .clk(clk), .rst_n(rst_n), .q(q) ); always #5 clk ~clk; // 時鐘周期 10ns always (posedge clk) begin if (rst_n) $display(t%0t q%b %d, $time, q, q); end initial begin $dumpfile(counter_2_5.vcd); $dumpvars(0, tb_counter_2_5); #10 rst_n 1; // 復(fù)位釋放等待第一個上升沿 repeat (8) (posedge clk); $finish; end endmodule這里時鐘用 always #5 clk ~clk生成 10ns 周期的方波。復(fù)位保持 10ns即一個完整時鐘周期后才釋放確保電源和復(fù)位建立穩(wěn)定。repeat(8) 表示觀察八個時鐘沿足夠覆蓋 000→010→011→100→101→010 兩輪循環(huán)。$display 會在每個時鐘上升沿后打印當前 Q 值方便對照狀態(tài)表。4.2 跑仿真并讀結(jié)果四個邊沿對應(yīng)四個狀態(tài)如果使用免費的 Verilog 仿真工具命令行方式如下iverilog -s tb_counter_2_5 -o counter_tb.out counter_2_5.v tb_counter_2_5.v vvp counter_tb.out也可以換成自己熟悉的圖形化仿真器把 counter_2_5.v 和 testbench 一起加進工程即可。運行后打印結(jié)果應(yīng)該是時鐘邊沿序號Q2 Q1 Q0十進制值第 1 個上升沿0 1 02第 2 個上升沿0 1 13第 3 個上升沿1 0 04第 4 個上升沿1 0 15第 5 個上升沿0 1 02注意如果在第 1 個上升沿看到的是 011先別急著改代碼檢查復(fù)位釋放時間。由于復(fù)位釋放后電路首先把 000 拉到 010這個跳變發(fā)生在復(fù)位釋放后的第一個上升沿正好對應(yīng)“2”。如果復(fù)位釋放與時鐘同時發(fā)生可能會看到復(fù)位競爭把第一個狀態(tài)吃掉這個問題后面單獨講。4.3 波形檢查時重點看哪幾個信號時鐘沿、復(fù)位釋放、毛刺打開波形文件后我習慣先看三個東西。第一是復(fù)位釋放位置是否在時鐘低電平區(qū)間不要在時鐘上升沿附近釋放復(fù)位第二是 Q 端變化是否都在上升沿后出現(xiàn)若某個 Q 在時鐘低電平期間變了通常說明組合邏輯存在競爭冒險第三是 J/K 信號本身有沒有毛刺尤其要觀察 J1 和 K1因為這兩個信號用了取反和或門門延遲不匹配時容易在時鐘沿附近出現(xiàn)極窄的尖峰。波形里還有一個容易被忽略的細節(jié)Q2 從 1 變 0 的時刻應(yīng)該和 Q1 從 0 變 1 基本同步因為 J0 依賴 Q2 XOR Q1兩個高位翻轉(zhuǎn)時間相差太大會讓 Q0 多翻一次。如果發(fā)現(xiàn) Q0 出現(xiàn)雙脈沖就要回到卡諾圖檢查是否把 J0 化簡得太大或者給 K0 增加一個小的濾波電容。5. 計數(shù)器設(shè)計避坑五個常見翻車點5.1 上電停在非法狀態(tài)計數(shù)器不進循環(huán)現(xiàn)象仿真時把復(fù)位釋放后狀態(tài)一直是 000 或 111看不到 2→3→4→5 的序列。原因電路沒有做自啟動設(shè)計非法狀態(tài)各自形成死循環(huán)比如 111→111。解決把非法狀態(tài)在激勵表里顯式處理至少要保證 000 和 111 能進入合法環(huán)。本文先前的方程已經(jīng)在 000、001、110、111 上都做了跳轉(zhuǎn)處理如果你自己化簡時把非法狀態(tài)全部當作 X 圈掉就可能得到非自啟動電路。5.2 觸發(fā)器的復(fù)位方式不統(tǒng)一狀態(tài)錯位一個周期現(xiàn)象板子實測時有時從 2 開始有時從 3 開始每上電結(jié)果都不一樣。原因某些觸發(fā)器用了異步復(fù)位另一些用了同步置數(shù)復(fù)位釋放的瞬間三位的恢復(fù)時間不一致。解決所有觸發(fā)器統(tǒng)一接同一個低電平復(fù)位并在復(fù)位釋放后預(yù)留至少一個完整時鐘周期再開始計數(shù)。不要在復(fù)位信號上串聯(lián)電阻或電容那只會讓沿更鈍。5.3 JK 同時為 1 造成的翻轉(zhuǎn)毛刺和競爭冒險現(xiàn)象波形圖上 Q 端在同一個時鐘沿出現(xiàn)快速 0→1→0 的跳動。原因某個位從 0 變 1 時J 和 K 同時為 1觸發(fā)器實際發(fā)生翻轉(zhuǎn)而相鄰位的組合邏輯還在變化造成二次翻轉(zhuǎn)。解決設(shè)計激勵方程時盡量讓“置位”和“清零”兩支路徑互斥避免 JK1 出現(xiàn)在關(guān)鍵轉(zhuǎn)換上。如果電路已經(jīng)焊好不方便改可以在 J/K 輸入端加同步寄存器讓激勵信號和時鐘嚴格同源同沿。5.4 把 Q2Q1Q0 直接當十進制輸出數(shù)碼管亂碼現(xiàn)象LED 或數(shù)碼管顯示的不是 2、3、4、5而是跳出一堆亂碼。原因大多數(shù)七段數(shù)碼管需要 BCD 碼而 Q2Q1Q0 是以 010、011、100、101 編碼的原始狀態(tài)直接接上去當然對不上。解決接一個 BCD 七段譯碼器或者用單片機讀 Q 后再查表輸出。若只想驗證邏輯功能用三只 LED 分別顯示 Q2、Q1、Q0 就夠了010 燈亮狀態(tài)就是計數(shù)值 2。5.5 復(fù)位時間太短上電瞬間就把 2 漏掉現(xiàn)象示波器看到計數(shù)器啟動后第一個狀態(tài)是 3而不是 2。原因復(fù)位信號寬度小于一個時鐘周期電路還沒從 000 跳到 010第一次有效上升沿就把它當成了 010 本身直接推進到 011。解決復(fù)位低電平至少要維持兩個時鐘周期以上并在時鐘低電平中段釋放。上電復(fù)位電路可以用施密特觸發(fā)器反相器加 RC 延遲RC 時間常數(shù)取時鐘周期的 10 倍以上。6. 進階用法把一個固定序列計數(shù)器變成可預(yù)置分頻器6.1 加一個置數(shù)端計數(shù)器就能從指定位置重新開始固定 2→3→4→5 的計數(shù)器再往上走一步就是加置數(shù)端。常見做法是把撥碼開關(guān)預(yù)置到 010當 LD 信號有效時在下一個時鐘沿把 Q2、Q1、Q0 分別裝入預(yù)設(shè)值。這樣不但能隨時讓狀態(tài)回到 2還能通過改變預(yù)置數(shù)改變起始位置把一個固定序列計數(shù)器變成可編程的分頻器。比如預(yù)置 100 時計數(shù)序列變成 4→5→2→3輸出脈沖密度不變但相位整體移動了。實際使用時LD 信號要和時鐘同步否則仍然會出現(xiàn)上一章說的競爭問題。6.2 用級聯(lián)和進位輸出擴展計數(shù)范圍單個計數(shù)器只有 4 拍。要做更長定時把低位計數(shù)器的進位輸出接到高位計數(shù)器的時鐘端就得到級聯(lián)結(jié)構(gòu)。低位從 5 跳回 2 時K2 產(chǎn)生一個低脈沖經(jīng)過簡單整形后送給高位時鐘高位計數(shù)器加 1。這樣兩級級聯(lián)后周期為 16 個低位周期輸出頻率是輸入時鐘的十六分之一。級聯(lián)時要注意高位時鐘觸發(fā)沿低位進位信號翻轉(zhuǎn)沿不能太緩建議用上升沿觸發(fā)的邊沿檢測電路整形后再接高位。6.3 一次系統(tǒng)性自檢腳本模擬八個周期驗證自啟動最后分享一個我常用的收尾動作把設(shè)計好的電路在軟件里做一次全狀態(tài)遍歷。做法是把上一章的 Python 腳本擴展一下從 8 個狀態(tài)分別運行 8 個時鐘周期斷言最終都進入 010→011→100→101 環(huán)。只要這個斷言過了綜合和接線再出問題基本可以排除邏輯功能錯誤。固定序列計數(shù)器最怕的不是門電路復(fù)雜而是某些“想當然能忽略”的非法狀態(tài)在斷電上電時偷偷接管電路。我習慣把這套自檢腳本留在工程目錄里每次改完激勵方程都跑一遍給自己留一張后悔藥。希望這篇從狀態(tài)表到仿真、再到實物排錯的筆記能幫你少走一圈彎路也希望你能把這套自啟動的思路用在你手頭的下一個序列控制電路上。本文還有配套的精品資源點擊獲取