字頻率計(jì)設(shè)計(jì):從Quartus工程到板級(jí)驗(yàn)證的完整實(shí)戰(zhàn))
簡介這份資源是本科EDA課程期末大作業(yè)的完整提交包圍繞基于FPGA的數(shù)字頻率計(jì)設(shè)計(jì)展開適合電子信息、集成電路及相關(guān)專業(yè)學(xué)生參考課程設(shè)計(jì)、實(shí)驗(yàn)報(bào)告撰寫與Quartus II開發(fā)入門。設(shè)計(jì)采用自上而下的EDA方法用Verilog將測(cè)頻模塊拆分為多個(gè)子模塊再通過頂層元件例化完成整體連接配套完整報(bào)告、代碼、PCB與原理圖便于理解從功能劃分到綜合實(shí)現(xiàn)的完整流程。壓縮包共744個(gè)文件約28.3MB涵蓋Verilog源碼與VHD文件、Quartus工程配置qpf、qsf、sof、綜合與仿真報(bào)告rpt、summary、vho、sdo、原理圖與PCB文檔schdoc、pcbdoc以及大量中間數(shù)據(jù)庫文件完整保留了工程編譯與調(diào)試痕跡。目前已有4695人學(xué)習(xí)下載可幫助讀者對(duì)照?qǐng)?bào)告復(fù)現(xiàn)測(cè)頻邏輯、梳理模塊劃分思路并借鑒工程目錄組織與排錯(cuò)經(jīng)驗(yàn)。1. 從一份 EDA 課設(shè)壓縮包說起FPGA 數(shù)字頻率計(jì)到底能測(cè)什么如果你正在做 EDA 課程設(shè)計(jì)或者剛接觸 FPGA 想找一個(gè)能跑通完整鏈路的練手項(xiàng)目這份「基于 FPGA 的數(shù)字頻率計(jì)設(shè)計(jì)」壓縮包大概率能省掉你不少翻論壇的時(shí)間。它解決的是一個(gè)很具體的問題用 FPGA 測(cè)量外部輸入信號(hào)的頻率并把結(jié)果穩(wěn)定地顯示出來。聽起來簡單但真正動(dòng)手時(shí)你會(huì)發(fā)現(xiàn)從分頻、計(jì)數(shù)、鎖存到數(shù)碼管動(dòng)態(tài)掃描每一步都有坑。這個(gè)項(xiàng)目適合兩類人一類是課程設(shè)計(jì)需要交作業(yè)、想快速理解完整工程結(jié)構(gòu)的同學(xué)另一類是想通過一個(gè)「麻雀雖小五臟俱全」的案例把 Verilog 時(shí)序邏輯、Quartus 工程管理、Testbench 仿真串起來的自學(xué)者。頻率計(jì)本身不復(fù)雜但它把 FPGA 開發(fā)中最核心的幾個(gè)概念——時(shí)鐘域、邊沿檢測(cè)、計(jì)數(shù)器位寬、顯示驅(qū)動(dòng)——全揉在了一起拿來當(dāng)入門模板剛剛好。2. 項(xiàng)目架構(gòu)拆解測(cè)頻原理與模塊劃分2.1 數(shù)字頻率計(jì)的核心邏輯閘門時(shí)間與計(jì)數(shù)數(shù)字頻率計(jì)的本質(zhì)是在一個(gè)已知寬度的閘門時(shí)間內(nèi)統(tǒng)計(jì)被測(cè)信號(hào)的脈沖個(gè)數(shù)。假設(shè)閘門時(shí)間為 T計(jì)數(shù)值為 N則頻率 f N / T。FPGA 實(shí)現(xiàn)時(shí)通常用一個(gè) 1Hz 或 0.5Hz 的使能信號(hào)作為閘門控制一個(gè)計(jì)數(shù)器對(duì)被測(cè)信號(hào)邊沿進(jìn)行累加。這里的關(guān)鍵參數(shù)是閘門時(shí)間的精度和計(jì)數(shù)器的位寬。閘門時(shí)間由系統(tǒng)時(shí)鐘分頻得到比如 50MHz 時(shí)鐘分頻到 1Hz需要計(jì)數(shù) 25,000,000 次這要求分頻計(jì)數(shù)器至少 25 位寬。被測(cè)信號(hào)頻率越高單位閘門時(shí)間內(nèi)計(jì)數(shù)值越大計(jì)數(shù)器位寬也要相應(yīng)增加。常見做法是閘門時(shí)間取 1 秒這樣計(jì)數(shù)值直接就是頻率值省去后續(xù)除法運(yùn)算對(duì)初學(xué)者最友好。2.2 模塊劃分從頂層到子模塊的職責(zé)邊界一個(gè)可綜合的頻率計(jì)工程通常包含以下幾個(gè)模塊頂層模塊例化所有子模塊連接時(shí)鐘、復(fù)位、被測(cè)信號(hào)輸入和數(shù)碼管輸出。分頻模塊將板載高頻時(shí)鐘分頻為 1Hz 閘門信號(hào)和數(shù)碼管掃描所需的低頻時(shí)鐘。測(cè)頻控制模塊產(chǎn)生閘門信號(hào)、鎖存信號(hào)和計(jì)數(shù)器清零信號(hào)協(xié)調(diào)整個(gè)測(cè)量時(shí)序。計(jì)數(shù)器模塊在閘門有效期間對(duì)被測(cè)信號(hào)邊沿計(jì)數(shù)。鎖存器模塊在閘門結(jié)束時(shí)鎖存計(jì)數(shù)值保證顯示穩(wěn)定。數(shù)碼管驅(qū)動(dòng)模塊將鎖存后的二進(jìn)制頻率值轉(zhuǎn)換為段選和位選信號(hào)動(dòng)態(tài)掃描顯示。這種劃分方式的好處是每個(gè)模塊職責(zé)單一方便單獨(dú)仿真和調(diào)試。我一般會(huì)先寫測(cè)頻控制模塊因?yàn)樗钦麄€(gè)系統(tǒng)時(shí)序的「大腦」閘門、鎖存、清零三個(gè)信號(hào)的先后順序一旦搞錯(cuò)顯示就會(huì)跳變或閃爍。2.3 目錄結(jié)構(gòu)壓縮包里的工程長什么樣解壓后典型的 Quartus 工程目錄大致如下freq_meter/ ├── db/ # Quartus 編譯生成的數(shù)據(jù)文件 ├── incremental_db/ # 增量編譯數(shù)據(jù)庫 ├── output_files/ # 編譯輸出含 .sof 和 .pof 下載文件 ├── freq_meter.qpf # Quartus 工程文件 ├── freq_meter.qsf # 工程設(shè)置文件引腳分配在這里 ├── top.v # 頂層模塊 ├── divider.v # 分頻模塊 ├── measure_ctrl.v # 測(cè)頻控制模塊 ├── counter.v # 計(jì)數(shù)器模塊 ├── latch.v # 鎖存器模塊 ├── seg_driver.v # 數(shù)碼管驅(qū)動(dòng)模塊 └── tb_freq_meter.v # Testbench 仿真文件其中.qsf文件里的引腳分配是新手最容易翻車的地方。不同開發(fā)板的引腳定義不同直接拿別人的工程下載很可能數(shù)碼管不亮或者被測(cè)信號(hào)輸入引腳接錯(cuò)。常見做法是打開 Assignment - Pin Planner對(duì)照自己板子的原理圖逐個(gè)核對(duì)時(shí)鐘、復(fù)位、輸入和數(shù)碼管引腳。3. 跑通工程從 Quartus 編譯到板級(jí)驗(yàn)證3.1 環(huán)境準(zhǔn)備與工程打開這個(gè)項(xiàng)目基于 Quartus 開發(fā)具體版本取決于壓縮包內(nèi)的工程文件格式。如果.qpf文件能直接打開說明版本兼容如果提示版本不匹配可以用文本編輯器打開.qpf把版本號(hào)改成你本地安裝的版本再重新打開。常見做法是安裝 Quartus Prime Lite 版它免費(fèi)且支持大多數(shù) Cyclone 系列 FPGA。打開工程后先別急著編譯檢查兩件事一是頂層模塊是否設(shè)置為top二是.qsf里的器件型號(hào)是否和你板子上的 FPGA 型號(hào)一致。器件選錯(cuò)編譯能過但下載會(huì)報(bào)錯(cuò)。3.2 引腳分配與編譯下載引腳分配是硬件驗(yàn)證的第一道坎。以常見的開發(fā)板為例系統(tǒng)時(shí)鐘通常接在專用的 CLK 引腳上復(fù)位按鍵接在某個(gè) GPIO被測(cè)信號(hào)從擴(kuò)展口輸入數(shù)碼管位選和段選分別接到對(duì)應(yīng)的 IO。下面是一個(gè)引腳分配的示例片段# freq_meter.qsf 引腳分配示例 set_location_assignment PIN_23 -to clk # 50MHz 系統(tǒng)時(shí)鐘 set_location_assignment PIN_25 -to rst_n # 復(fù)位按鍵低有效 set_location_assignment PIN_28 -to sig_in # 被測(cè)信號(hào)輸入 set_location_assignment PIN_100 -to seg[0] # 數(shù)碼管段選 a set_location_assignment PIN_101 -to seg[1] # 數(shù)碼管段選 b set_location_assignment PIN_102 -to seg[2] # 數(shù)碼管段選 c set_location_assignment PIN_103 -to seg[3] # 數(shù)碼管段選 d set_location_assignment PIN_104 -to seg[4] # 數(shù)碼管段選 e set_location_assignment PIN_105 -to seg[5] # 數(shù)碼管段選 f set_location_assignment PIN_106 -to seg[6] # 數(shù)碼管段選 g set_location_assignment PIN_107 -to seg[7] # 數(shù)碼管段選 dp set_location_assignment PIN_110 -to sel[0] # 數(shù)碼管位選 1 set_location_assignment PIN_111 -to sel[1] # 數(shù)碼管位選 2 set_location_assignment PIN_112 -to sel[2] # 數(shù)碼管位選 3 set_location_assignment PIN_113 -to sel[3] # 數(shù)碼管位選 4這段分配里clk是 50MHz 有源晶振輸入rst_n是低電平復(fù)位sig_in是被測(cè)信號(hào)輸入引腳。段選和位選的具體引腳號(hào)必須對(duì)照你的板子原理圖修改不能照抄。分配完成后點(diǎn)擊 Processing - Start Compilation等待編譯完成。如果報(bào)錯(cuò)先看 Messages 窗口的 Error 信息常見的是引腳沖突或器件不支持某條約束。編譯通過后用 USB-Blaster 下載線連接板子打開 Programmer選擇output_files目錄下的.sof文件點(diǎn)擊 Start。下載成功后給sig_in接入一個(gè)已知頻率的信號(hào)源比如函數(shù)發(fā)生器輸出的 1kHz 方波觀察數(shù)碼管顯示是否接近 1000。3.3 仿真驗(yàn)證先跑 Testbench 再上板上板之前強(qiáng)烈建議先跑一遍 Testbench 仿真。壓縮包里的tb_freq_meter.v通常會(huì)產(chǎn)生一個(gè)模擬的被測(cè)信號(hào)并觀察計(jì)數(shù)器和鎖存器的輸出。下面是一個(gè)簡化的 Testbench 結(jié)構(gòu)// tb_freq_meter.v 仿真激勵(lì)示例 timescale 1ns/1ps module tb_freq_meter; reg clk 0; reg rst_n 0; reg sig_in 0; wire [31:0] freq_val; wire [7:0] seg; wire [3:0] sel; // 50MHz 時(shí)鐘周期 20ns always #10 clk ~clk; // 模擬被測(cè)信號(hào)周期 1ms即 1kHz always #500000 sig_in ~sig_in; initial begin rst_n 0; #200 rst_n 1; // 釋放復(fù)位 #2000000000 $stop; // 仿真 2ms 后停止 end // 例化頂層模塊 freq_meter_top u_top ( .clk(clk), .rst_n(rst_n), .sig_in(sig_in), .seg(seg), .sel(sel) ); endmodule這段 Testbench 里clk周期 20ns 對(duì)應(yīng) 50MHzsig_in周期 1ms 對(duì)應(yīng) 1kHz。仿真跑 2ms 后停止理論上閘門時(shí)間 1 秒還沒到所以看不到完整的鎖存值。實(shí)際仿真時(shí)為了快速驗(yàn)證可以把分頻模塊的閘門時(shí)間參數(shù)改小比如改成 1ms這樣仿真幾毫秒就能看到計(jì)數(shù)結(jié)果。參數(shù)化設(shè)計(jì)的好處就在這里——改一個(gè)parameter就能加速仿真不用改邏輯。注意仿真時(shí)如果計(jì)數(shù)器一直不變化先檢查閘門信號(hào)是否正常產(chǎn)生再看被測(cè)信號(hào)是否真的在翻轉(zhuǎn)。很多時(shí)候是 Testbench 里的always塊寫錯(cuò)了時(shí)間單位。4. 核心代碼逐段拆解分頻、計(jì)數(shù)與數(shù)碼管驅(qū)動(dòng)4.1 分頻模塊從 50MHz 到 1Hz 閘門分頻模塊的任務(wù)是把系統(tǒng)時(shí)鐘降到閘門信號(hào)所需的頻率。下面是一個(gè)參數(shù)化的分頻器實(shí)現(xiàn)// divider.v 參數(shù)化分頻器 module divider #( parameter CNT_MAX 25_000_000 // 50MHz 分頻到 1Hz )( input wire clk, input wire rst_n, output reg clk_out ); reg [24:0] cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt 25d0; clk_out 1b0; end else if (cnt CNT_MAX - 1) begin cnt 25d0; clk_out ~clk_out; // 翻轉(zhuǎn)輸出得到 1Hz 方波 end else begin cnt cnt 1b1; end end endmoduleCNT_MAX決定了輸出頻率。50MHz 時(shí)鐘下計(jì)數(shù) 25,000,000 次后翻轉(zhuǎn)輸出周期 1 秒即 1Hz。如果板載時(shí)鐘是 100MHz這個(gè)參數(shù)要改成 50,000,000。cnt的位寬必須足夠容納CNT_MAX - 125 位可以表示到 33,554,431夠用。如果位寬不夠計(jì)數(shù)器會(huì)提前溢出輸出頻率就錯(cuò)了。我一般會(huì)在仿真時(shí)把CNT_MAX改小到 100這樣幾微秒就能看到翻轉(zhuǎn)省時(shí)間。4.2 測(cè)頻控制模塊閘門、鎖存、清零的時(shí)序配合測(cè)頻控制模塊是整個(gè)設(shè)計(jì)里時(shí)序最講究的部分。它需要產(chǎn)生三個(gè)信號(hào)閘門使能gate_en、鎖存使能latch_en和計(jì)數(shù)器清零cnt_clr。三者的順序是先清零計(jì)數(shù)器再打開閘門閘門結(jié)束后鎖存數(shù)據(jù)然后再次清零循環(huán)往復(fù)。// measure_ctrl.v 測(cè)頻時(shí)序控制 module measure_ctrl ( input wire clk, // 1Hz 閘門時(shí)鐘 input wire rst_n, output reg gate_en, // 閘門使能高有效 output reg latch_en, // 鎖存使能上升沿鎖存 output reg cnt_clr // 計(jì)數(shù)器清零高有效 ); reg [1:0] state; localparam IDLE 2d0, GATE 2d1, LATCH 2d2, CLEAR 2d3; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; gate_en 1b0; latch_en 1b0; cnt_clr 1b1; end else begin case (state) IDLE: begin cnt_clr 1b1; gate_en 1b0; latch_en 1b0; state GATE; end GATE: begin cnt_clr 1b0; gate_en 1b1; latch_en 1b0; state LATCH; end LATCH: begin gate_en 1b0; latch_en 1b1; state CLEAR; end CLEAR: begin latch_en 1b0; cnt_clr 1b1; state GATE; end default: state IDLE; endcase end end endmodule這個(gè)狀態(tài)機(jī)用 1Hz 時(shí)鐘驅(qū)動(dòng)每個(gè)狀態(tài)持續(xù) 1 秒。GATE狀態(tài)下gate_en拉高計(jì)數(shù)器開始對(duì)被測(cè)信號(hào)計(jì)數(shù)LATCH狀態(tài)下latch_en拉高把計(jì)數(shù)值鎖存到顯示寄存器CLEAR狀態(tài)下cnt_clr拉高計(jì)數(shù)器歸零為下一輪測(cè)量做準(zhǔn)備。這里有個(gè)細(xì)節(jié)latch_en是電平信號(hào)鎖存器模塊應(yīng)該在latch_en的上升沿或高電平期間更新輸出。如果鎖存器對(duì)latch_en的邊沿不敏感顯示值可能會(huì)在閘門期間跟著計(jì)數(shù)器變化導(dǎo)致數(shù)碼管跳數(shù)。4.3 計(jì)數(shù)器與鎖存器位寬選擇和邊沿檢測(cè)計(jì)數(shù)器模塊對(duì)被測(cè)信號(hào)的上升沿進(jìn)行計(jì)數(shù)。這里需要一個(gè)邊沿檢測(cè)邏輯把被測(cè)信號(hào)的上升沿轉(zhuǎn)換成單周期脈沖// counter.v 帶邊沿檢測(cè)的計(jì)數(shù)器 module counter ( input wire clk, // 系統(tǒng)時(shí)鐘用于同步 input wire rst_n, input wire gate_en, // 閘門使能 input wire cnt_clr, // 清零 input wire sig_in, // 被測(cè)信號(hào) output reg [31:0] cnt // 計(jì)數(shù)值 ); reg sig_d0, sig_d1; wire sig_posedge; // 兩級(jí)寄存器同步被測(cè)信號(hào)檢測(cè)上升沿 always (posedge clk or negedge rst_n) begin if (!rst_n) begin sig_d0 1b0; sig_d1 1b0; end else begin sig_d0 sig_in; sig_d1 sig_d0; end end assign sig_posedge sig_d0 ~sig_d1; // 上升沿脈沖 always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt 32d0; end else if (cnt_clr) begin cnt 32d0; end else if (gate_en sig_posedge) begin cnt cnt 1b1; end end endmodulesig_d0和sig_d1構(gòu)成兩級(jí)同步寄存器把異步的被測(cè)信號(hào)同步到系統(tǒng)時(shí)鐘域同時(shí)sig_d0 ~sig_d1產(chǎn)生一個(gè)單周期脈沖代表上升沿。計(jì)數(shù)器位寬取 32 位足夠測(cè)量到幾十 MHz 的頻率。如果被測(cè)信號(hào)頻率接近系統(tǒng)時(shí)鐘的一半邊沿檢測(cè)可能會(huì)漏掉脈沖這是奈奎斯特采樣定理的限制不是代碼問題。鎖存器模塊在latch_en有效時(shí)把cnt的值復(fù)制到輸出寄存器// latch.v 數(shù)據(jù)鎖存器 module latch ( input wire clk, input wire rst_n, input wire latch_en, input wire [31:0] cnt_in, output reg [31:0] freq_out ); always (posedge clk or negedge rst_n) begin if (!rst_n) freq_out 32d0; else if (latch_en) freq_out cnt_in; end endmodule這里用latch_en作為條件在系統(tǒng)時(shí)鐘上升沿采樣。如果latch_en持續(xù)多個(gè)時(shí)鐘周期freq_out會(huì)一直跟隨cnt_in所以latch_en最好只保持一個(gè)時(shí)鐘周期。但在 1Hz 時(shí)鐘域下一個(gè)周期就是 1 秒所以實(shí)際工程中常用邊沿檢測(cè)來縮短鎖存窗口。4.4 數(shù)碼管動(dòng)態(tài)掃描段選與位選的配合數(shù)碼管驅(qū)動(dòng)模塊負(fù)責(zé)把 32 位頻率值拆成十進(jìn)制數(shù)字并動(dòng)態(tài)掃描顯示。下面是一個(gè)簡化的實(shí)現(xiàn)// seg_driver.v 數(shù)碼管動(dòng)態(tài)掃描 module seg_driver ( input wire clk, // 掃描時(shí)鐘約 1kHz input wire rst_n, input wire [31:0] freq_val, output reg [7:0] seg, // 段選低有效 output reg [3:0] sel // 位選低有效 ); reg [16:0] scan_cnt; reg [1:0] digit_sel; reg [3:0] bcd_digit; reg [3:0] bcd [3:0]; // 4 位十進(jìn)制數(shù)字 // 掃描計(jì)數(shù)器產(chǎn)生位選切換信號(hào) always (posedge clk or negedge rst_n) begin if (!rst_n) scan_cnt 17d0; else scan_cnt scan_cnt 1b1; end always (posedge clk or negedge rst_n) begin if (!rst_n) digit_sel 2d0; else if (scan_cnt 17d99_999) digit_sel digit_sel 1b1; end // 將頻率值拆分為 BCD 碼簡化版僅取低四位 always (*) begin bcd[0] freq_val % 10; bcd[1] (freq_val / 10) % 10; bcd[2] (freq_val / 100) % 10; bcd[3] (freq_val / 1000) % 10; end // 位選和段選輸出 always (*) begin case (digit_sel) 2d0: begin sel 4b1110; bcd_digit bcd[0]; end 2d1: begin sel 4b1101; bcd_digit bcd[1]; end 2d2: begin sel 4b1011; bcd_digit bcd[2]; end 2d3: begin sel 4b0111; bcd_digit bcd[3]; end default: begin sel 4b1111; bcd_digit 4d0; end endcase end // 段選譯碼共陽極數(shù)碼管低有效 always (*) begin case (bcd_digit) 4d0: seg 8b1100_0000; 4d1: seg 8b1111_1001; 4d2: seg 8b1010_0100; 4d3: seg 8b1011_0000; 4d4: seg 8b1001_1001; 4d5: seg 8b1001_0010; 4d6: seg 8b1000_0010; 4d7: seg 8b1111_1000; 4d8: seg 8b1000_0000; 4d9: seg 8b1001_0000; default: seg 8b1111_1111; endcase end endmodulescan_cnt每計(jì)數(shù)到 99,999 切換一次位選在 50MHz 時(shí)鐘下大約 2ms 切換一次人眼看起來是同時(shí)亮的。bcd數(shù)組存儲(chǔ)各位十進(jìn)制數(shù)字digit_sel循環(huán)選擇。段選譯碼表對(duì)應(yīng)共陽極數(shù)碼管低電平點(diǎn)亮。如果你的板子是共陰極段選值要取反。這個(gè)模塊的坑在于除法器和取模運(yùn)算會(huì)消耗較多邏輯資源如果頻率值位寬很大綜合器可能會(huì)報(bào)資源不足。常見做法是用雙 dabble 算法把二進(jìn)制轉(zhuǎn) BCD或者限制顯示位數(shù)只顯示低四位。5. 避坑與排查上板不亮、跳數(shù)、編譯報(bào)錯(cuò)怎么辦5.1 數(shù)碼管不亮或只亮一位現(xiàn)象下載后數(shù)碼管全滅或者只有一位亮且不切換。原因位選信號(hào)沒有正確掃描或者段選譯碼表與數(shù)碼管類型不匹配。共陽極和共陰極的段選值正好相反接錯(cuò)就全滅。解決先用萬用表測(cè)數(shù)碼管公共端是接高還是接低確認(rèn)類型再檢查scan_cnt的比較值是否太小導(dǎo)致掃描頻率過高人眼看不到。把掃描時(shí)鐘降到 1kHz 左右位選切換間隔約 1ms顯示最穩(wěn)定。5.2 頻率顯示跳數(shù)或歸零現(xiàn)象數(shù)碼管顯示的數(shù)字一直在跳或者每隔一秒歸零一次。原因鎖存器的鎖存時(shí)機(jī)不對(duì)或者計(jì)數(shù)器清零和鎖存同時(shí)發(fā)生。解決檢查測(cè)頻控制模塊的狀態(tài)機(jī)確保latch_en在cnt_clr之前有效且兩者不同時(shí)拉高。如果鎖存器對(duì)latch_en的電平敏感改成邊沿敏感只在上升沿鎖存一次。另外閘門信號(hào)和鎖存信號(hào)如果跨時(shí)鐘域要加同步器。5.3 編譯報(bào)錯(cuò)引腳沖突或資源不足現(xiàn)象Quartus 編譯報(bào) Error提示引腳重復(fù)分配或邏輯單元不夠。原因.qsf文件里同一個(gè)引腳被分配給多個(gè)信號(hào)或者除法器、乘法器消耗了太多 LE。解決打開 Pin Planner 查看沖突引腳刪除重復(fù)分配把除法運(yùn)算改成移位和查表或者用專用 DSP 塊。如果資源實(shí)在不夠降低顯示位數(shù)只顯示 4 位頻率值。5.4 被測(cè)信號(hào)無輸入時(shí)顯示亂碼現(xiàn)象不接信號(hào)源時(shí)數(shù)碼管顯示隨機(jī)數(shù)字。原因輸入引腳懸空FPGA 輸入緩沖器拾取噪聲計(jì)數(shù)器亂計(jì)數(shù)。解決在sig_in引腳上使能內(nèi)部上拉或下拉電阻或者在代碼里加一個(gè)輸入濾波連續(xù)采樣多次才認(rèn)為有效邊沿。常見做法是在.qsf里加set_instance_assignment -name WEAK_PULL_UP_RESISTOR ON -to sig_in。5.5 仿真通過但上板結(jié)果不對(duì)現(xiàn)象Testbench 波形完全正確下載到板子后頻率值偏差很大。原因板載時(shí)鐘頻率和代碼里假設(shè)的不一致比如代碼按 50MHz 分頻實(shí)際板子是 100MHz閘門時(shí)間變成 0.5 秒計(jì)數(shù)值翻倍。解決確認(rèn)板子晶振頻率修改分頻模塊的CNT_MAX參數(shù)。如果不確定可以用一個(gè)已知頻率的信號(hào)源校準(zhǔn)比如輸入 1kHz看顯示是 1000 還是 2000反推實(shí)際時(shí)鐘頻率。6. 二次開發(fā)把頻率計(jì)改成周期測(cè)量與占空比顯示這個(gè)工程跑通之后最自然的改造方向是擴(kuò)展測(cè)量功能。頻率計(jì)只測(cè)頻率但同樣的硬件可以測(cè)周期和占空比。周期是頻率的倒數(shù)但直接做除法會(huì)消耗資源更常見的做法是反過來用被測(cè)信號(hào)作為閘門統(tǒng)計(jì)系統(tǒng)時(shí)鐘個(gè)數(shù)。具體來說把sig_in當(dāng)作閘門信號(hào)在它的高電平期間對(duì) 50MHz 時(shí)鐘計(jì)數(shù)計(jì)數(shù)值乘以 20ns 就是周期。這樣測(cè)低頻信號(hào)時(shí)精度更高因?yàn)橄到y(tǒng)時(shí)鐘頻率遠(yuǎn)高于被測(cè)信號(hào)。占空比測(cè)量則需要分別統(tǒng)計(jì)高電平時(shí)間和總周期時(shí)間??梢栽诒粶y(cè)信號(hào)的上升沿清零一個(gè)高電平計(jì)數(shù)器下降沿鎖存高電平計(jì)數(shù)值同時(shí)另一個(gè)計(jì)數(shù)器統(tǒng)計(jì)完整周期。兩者相除得到占空比。除法可以用移位近似或者把結(jié)果送到數(shù)碼管顯示百分比。改造時(shí)我一般會(huì)保留原來的測(cè)頻通道增加一個(gè)模式選擇開關(guān)。模式 0 顯示頻率模式 1 顯示周期模式 2 顯示占空比。這樣一份工程能覆蓋更多課設(shè)題目要求。下面是一個(gè)模式選擇的代碼框架// mode_sel.v 測(cè)量模式選擇 module mode_sel ( input wire [1:0] mode, // 模式選擇 input wire [31:0] freq_val, // 頻率值 input wire [31:0] period_val, // 周期值 input wire [31:0] duty_val, // 占空比 output reg [31:0] display_val ); always (*) begin case (mode) 2d0: display_val freq_val; 2d1: display_val period_val; 2d2: display_val duty_val; default: display_val freq_val; endcase end endmodule這個(gè)模塊把三個(gè)測(cè)量值映射到一個(gè)顯示通道數(shù)碼管驅(qū)動(dòng)模塊不用改只需要在頂層根據(jù)模式選擇輸出。驗(yàn)證時(shí)先用信號(hào)發(fā)生器輸出一個(gè)已知頻率和占空比的方波比如 1kHz、50% 占空比看三個(gè)模式下的顯示是否分別為 1000、1000000單位 ns和 50。如果周期顯示不對(duì)檢查系統(tǒng)時(shí)鐘頻率是否準(zhǔn)確如果占空比偏差大檢查高電平計(jì)數(shù)器的清零和鎖存時(shí)機(jī)。從那以后我每次做 FPGA 課設(shè)都會(huì)先把仿真跑通再上板而且一定會(huì)用已知信號(hào)源校準(zhǔn)一遍。這個(gè)頻率計(jì)工程雖然簡單但它把時(shí)序控制、跨時(shí)鐘域、顯示驅(qū)動(dòng)這些核心問題都串了一遍改完周期和占空比測(cè)量基本就能應(yīng)付大多數(shù) EDA 期末大作業(yè)的要求了。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取