動(dòng)設(shè)計(jì)全解析:從時(shí)序圖到Verilog狀態(tài)機(jī)的實(shí)戰(zhàn)指南)
簡(jiǎn)介FPGA驅(qū)動(dòng)設(shè)計(jì)與原代碼解析資料包面向嵌入式工程師、FPGA學(xué)習(xí)者及高校電子類學(xué)生幫助掌握從硬件識(shí)別、配置加載到中斷處理、數(shù)據(jù)讀寫的完整驅(qū)動(dòng)開發(fā)流程。包內(nèi)共2個(gè)文件一份臺(tái)灣某大學(xué)實(shí)驗(yàn)室的C語(yǔ)言驅(qū)動(dòng)源碼和一份PDF安裝使用說(shuō)明壓縮包僅165KB體積小且內(nèi)容聚焦。源碼分別實(shí)現(xiàn)設(shè)備識(shí)別與初始化、中斷服務(wù)例程、I/O操作函數(shù)、配置加載、錯(cuò)誤處理等關(guān)鍵部分展示如何使用Linux驅(qū)動(dòng)模型編寫FPGA字符設(shè)備驅(qū)動(dòng)PDF則提供驅(qū)動(dòng)安裝及驗(yàn)證指引便于對(duì)照代碼理解總線尋址、寄存器操作和設(shè)備樹描述。已有244人學(xué)習(xí)/下載。讀者可通過(guò)該案例快速上手驅(qū)動(dòng)的框架搭建與調(diào)試優(yōu)化包括中斷處理、數(shù)據(jù)交互、錯(cuò)誤恢復(fù)等實(shí)用技巧也可將這套代碼移植到個(gè)人項(xiàng)目中代碼中的注釋與模塊劃分清晰通過(guò)分析設(shè)備樹、初始化函數(shù)、中斷處理函數(shù)和I/O操作函數(shù)之間的協(xié)作關(guān)系可以深入理解硬件驅(qū)動(dòng)的整體運(yùn)行機(jī)制是FPGA驅(qū)動(dòng)入門和Linux底層開發(fā)不可多得的參考。 FPGA的驅(qū)動(dòng)設(shè)計(jì)說(shuō)到底是把芯片數(shù)據(jù)手冊(cè)里那一張時(shí)序圖翻譯成Verilog代碼再配上一整套能讓代碼在真實(shí)板卡上穩(wěn)定跑起來(lái)的約束和調(diào)試方法。很多朋友剛開始接觸FPGA第一反應(yīng)是“我要寫圖像處理”“我要做神經(jīng)網(wǎng)絡(luò)加速”結(jié)果連一顆溫度傳感器都讀不回來(lái)板子上的信號(hào)亂成一鍋粥。真正做過(guò)三五個(gè)驅(qū)動(dòng)模塊之后你才會(huì)明白FPGA項(xiàng)目能不能成往往不是算法多漂亮而是底層驅(qū)動(dòng)夠不夠扎實(shí)。這篇文章就圍繞FPGA驅(qū)動(dòng)設(shè)計(jì)和原代碼展開適合剛?cè)腴T想搞懂接口邏輯的也適合手頭有項(xiàng)目但不知道從哪下手的朋友參考。我會(huì)從最核心的設(shè)計(jì)思路講起拆解常見接口驅(qū)動(dòng)怎么做再給一份可以直接動(dòng)手改的代碼骨架最后聊聊調(diào)試中容易踩的坑。1. FPGA驅(qū)動(dòng)設(shè)計(jì)的核心思路拆解1.1 先搞清楚“驅(qū)動(dòng)”到底在驅(qū)動(dòng)什么FPGA里的“驅(qū)動(dòng)設(shè)計(jì)”和嵌入式軟件里寫一個(gè)Linux驅(qū)動(dòng)、Windows驅(qū)動(dòng)是完全兩碼事。你在STM32上寫I2C驅(qū)動(dòng)做的是配置寄存器、輪詢狀態(tài)位、處理中斷在FPGA里寫I2C驅(qū)動(dòng)做的是用狀態(tài)機(jī)產(chǎn)生SCL時(shí)鐘、在正確的時(shí)刻抬起SDA、按協(xié)議發(fā)送起始位和停止位。也就是說(shuō)FPGA驅(qū)動(dòng)是用硬件邏輯“重新實(shí)現(xiàn)”一套接口協(xié)議底層沒有任何現(xiàn)成的外設(shè)控制器可以調(diào)用除非你用廠商IP核。這帶來(lái)一個(gè)很關(guān)鍵的理解FPGA驅(qū)動(dòng)設(shè)計(jì)本質(zhì)上是“時(shí)序設(shè)計(jì)”。你要做的就是把數(shù)據(jù)手冊(cè)里的一張波形圖變成一組可綜合的RTL代碼讓FPGA的引腳上按要求出現(xiàn)對(duì)應(yīng)的電平變化。它解決的核心問題是FPGA與外部器件傳感器、ADC/DAC、存儲(chǔ)器、另一顆處理器之間如何可靠地交換數(shù)據(jù)。適合誰(shuí)看搞嵌入式想轉(zhuǎn)FPGA的、做接口擴(kuò)展遇到瓶頸的還有那些想從流水燈進(jìn)階到協(xié)議級(jí)開發(fā)的初學(xué)者。1.2 驅(qū)動(dòng)設(shè)計(jì)的三個(gè)層級(jí)管腳、時(shí)序、協(xié)議驅(qū)動(dòng)設(shè)計(jì)在實(shí)際項(xiàng)目中并不是一個(gè)單一動(dòng)作它至少分成三個(gè)層級(jí)。第一層是管腳約束包括引腳位置分配、IO電平標(biāo)準(zhǔn)選擇、上下拉配置、輸出速率設(shè)置。Vivado里的一行set_property或者Quartus里的Assignment Editor很多人不當(dāng)回事但恰恰是“仿真全對(duì)、上板全錯(cuò)”的首要原因。第二層是時(shí)序設(shè)計(jì)包括時(shí)鐘頻率規(guī)劃、建立保持時(shí)間、跨時(shí)鐘域處理、時(shí)序約束。第三層才是協(xié)議實(shí)現(xiàn)也就是狀態(tài)機(jī)、計(jì)數(shù)器、移位寄存器這些邏輯怎么搭。這三個(gè)層級(jí)對(duì)應(yīng)到具體項(xiàng)目里就是各種各樣的接口驅(qū)動(dòng)。比如STM32H743和FPGA實(shí)現(xiàn)FMC通信核心就是一個(gè)異步并行總線接口驅(qū)動(dòng)FOC驅(qū)動(dòng)速度環(huán)設(shè)計(jì)雖然算法在DSP或MCU里跑但FPGA側(cè)要負(fù)責(zé)編碼器采集和PWM輸出那就要搞定Biss-C、SPI、增量編碼器這些驅(qū)動(dòng)再往上一層FPGA做圖像處理可能要接MIPI攝像頭、HDMI顯示、DDR存儲(chǔ)哪一個(gè)不是驅(qū)動(dòng)為王??梢哉f(shuō)驅(qū)動(dòng)設(shè)計(jì)就是FPGA工程的地基地基不穩(wěn)上面蓋什么都白搭。1.3 為什么說(shuō)驅(qū)動(dòng)設(shè)計(jì)是FPGA入門的分水嶺驅(qū)動(dòng)設(shè)計(jì)在FPGA學(xué)習(xí)路徑里的位置很微妙。它比流水燈難因?yàn)樯婕皡f(xié)議和時(shí)序分析它又比圖像處理、神經(jīng)網(wǎng)絡(luò)加速簡(jiǎn)單因?yàn)檫壿嬕?guī)模通常不大。我見過(guò)不少人Verilog語(yǔ)法看了兩遍立刻想做PCIe RC或者M(jìn)IPI結(jié)果被復(fù)雜的IP配置和鏈路訓(xùn)練折磨得懷疑人生。反過(guò)來(lái)講如果老老實(shí)實(shí)從UART、SPI、I2C做起把數(shù)據(jù)手冊(cè)讀明白把波形調(diào)出來(lái)再轉(zhuǎn)頭去碰DDR、PCIe你會(huì)發(fā)現(xiàn)那些所謂“高大上”的東西底層套路其實(shí)是一樣的——先懂時(shí)序再寫協(xié)議最后做約束和驗(yàn)證。2. 驅(qū)動(dòng)設(shè)計(jì)的核心技術(shù)點(diǎn)解析2.1 讀懂時(shí)序圖是驅(qū)動(dòng)設(shè)計(jì)的第一步拿到一份陌生的芯片數(shù)據(jù)手冊(cè)很多人習(xí)慣先翻寄存器描述直接跳到代碼實(shí)現(xiàn)。這是個(gè)很常見的誤區(qū)。驅(qū)動(dòng)設(shè)計(jì)的第一步永遠(yuǎn)是看接口時(shí)序圖。比如SPI接口你至少要搞清楚時(shí)鐘極性和相位CPOL、CPHA數(shù)據(jù)在哪個(gè)沿采樣、哪個(gè)沿變化I2C接口你要知道起始條件、停止條件是什么電平關(guān)系、數(shù)據(jù)建立保持時(shí)間分別是多少。我自己的習(xí)慣是先在時(shí)序圖上標(biāo)出“FPGA應(yīng)該做什么”什么時(shí)候拉高、拉低、輸出數(shù)據(jù)、采樣輸入。然后用文字把時(shí)序描述翻譯成一張表格比如“在SCLK上升沿之前SDA必須在至少10ns內(nèi)保持穩(wěn)定”。這么做的好處是把模糊的波形圖變成了清晰的設(shè)計(jì)輸入后續(xù)寫狀態(tài)機(jī)的時(shí)候思路完全不會(huì)亂。有些芯片數(shù)據(jù)手冊(cè)里時(shí)序參數(shù)特別復(fù)雜什么tSU、tHD、tREC看起來(lái)嚇人其實(shí)你只需要關(guān)心數(shù)據(jù)手冊(cè)里“定時(shí)特性表”的幾個(gè)關(guān)鍵值建立時(shí)間、保持時(shí)間、最小脈沖寬度其他的都可以先不管。2.2 狀態(tài)機(jī)把時(shí)序圖表翻譯成硬件動(dòng)作理解了時(shí)序圖之后代碼實(shí)現(xiàn)的核心工具就是狀態(tài)機(jī)。FPGA驅(qū)動(dòng)設(shè)計(jì)里最經(jīng)典的做法是把一次完整的通信過(guò)程拆成幾個(gè)狀態(tài)空閑、啟動(dòng)、發(fā)送/接收數(shù)據(jù)、結(jié)束。比如SPI傳輸IDLE狀態(tài)下等待觸發(fā)信號(hào)到START狀態(tài)拉起片選然后進(jìn)入移位循環(huán)一邊發(fā)送數(shù)據(jù)、一邊采樣接收全部bit發(fā)完到STOP狀態(tài)釋放片選。這里我要特別強(qiáng)調(diào)一下三段式狀態(tài)機(jī)的寫法這也是FPGA面試?yán)飵缀醣乜嫉臇|西。三段式把狀態(tài)跳轉(zhuǎn)邏輯、次態(tài)組合邏輯、輸出時(shí)序邏輯分開寫好處是輸出不會(huì)出現(xiàn)毛刺代碼可讀性和可維護(hù)性也高很多。很多初學(xué)者習(xí)慣把所有邏輯塞進(jìn)一個(gè)always塊單段式看著省事但項(xiàng)目復(fù)雜起來(lái)以后改一個(gè)輸出都可能引發(fā)一連串問題。驅(qū)動(dòng)設(shè)計(jì)這種對(duì)時(shí)序敏感的場(chǎng)景老老實(shí)實(shí)用三段式是最穩(wěn)的。2.3 跨時(shí)鐘域與數(shù)據(jù)緩沖驅(qū)動(dòng)穩(wěn)定的關(guān)鍵驅(qū)動(dòng)設(shè)計(jì)里最容易出問題、也最考驗(yàn)功力的不是協(xié)議本身而是跨時(shí)鐘域處理。比如FPGA的系統(tǒng)時(shí)鐘是100MHz外部ADC的采樣時(shí)鐘是10MHz給進(jìn)來(lái)的又比如SPI從機(jī)的SCLK是由外部主機(jī)產(chǎn)生的和FPGA內(nèi)部時(shí)鐘完全異步。如果直接拿異步信號(hào)做寄存器就會(huì)出現(xiàn)亞穩(wěn)態(tài)輕則數(shù)據(jù)偶爾錯(cuò)一位重則整個(gè)狀態(tài)機(jī)跑飛。業(yè)界通用的做法是單比特信號(hào)用兩級(jí)同步器多比特?cái)?shù)據(jù)用異步FIFO。兩級(jí)同步器很簡(jiǎn)單就是把跨時(shí)鐘信號(hào)連續(xù)打兩拍但這只能解決單比特信號(hào)的亞穩(wěn)態(tài)問題而且默認(rèn)信號(hào)變化頻率遠(yuǎn)低于采樣頻率。真正的數(shù)據(jù)搬運(yùn)必須用異步FIFOFIFO的讀寫時(shí)鐘可以不同內(nèi)部通過(guò)格雷碼指針同步配合空滿標(biāo)志就能可靠地實(shí)現(xiàn)數(shù)據(jù)跨時(shí)鐘域傳輸。DDR控制器、PCIe、以太網(wǎng)MAC這些高速接口內(nèi)部全是這套邏輯。2.4 硬件IP核和自己的驅(qū)動(dòng)代碼怎么選驅(qū)動(dòng)設(shè)計(jì)還有一個(gè)繞不開的選型問題用廠商IP核還是自己寫RTLSPI、I2C、UART這種相對(duì)低速的接口自己寫完全可行代碼量不大而且能完全掌控接口行為出了問題也容易排查。但DDR控制器、PCIe硬核、高速串行收發(fā)器這些幾乎不可能靠純邏輯手寫因?yàn)樯婕皬?fù)雜的訓(xùn)練序列、自動(dòng)校準(zhǔn)、均衡算法必須用Xilinx、Intel或者國(guó)產(chǎn)FPGA廠商提供的IP核。拿PCIe RC來(lái)說(shuō)你在Xilinx環(huán)境下生成一個(gè)XDMA IP核做起來(lái)其實(shí)像配置一個(gè)復(fù)雜的軟件驅(qū)動(dòng)要關(guān)注鏈路速率、DMA描述符、中斷機(jī)制底層物理層的事情IP核全包了。再比如Biss-C這種專用的絕對(duì)編碼器接口它本質(zhì)上是一種帶雙向數(shù)據(jù)通信的串行協(xié)議沒有現(xiàn)成IP核可用需要自己寫狀態(tài)機(jī)去實(shí)現(xiàn)。我的建議是能用IP核的場(chǎng)合別重復(fù)造輪子但用IP核之前最好先把對(duì)應(yīng)的協(xié)議原理搞清楚否則IP核配置界面里那一堆選項(xiàng)你根本不知道該怎么填。3. 原代碼結(jié)構(gòu)解析一個(gè)驅(qū)動(dòng)模塊的組織方式3.1 頂層模塊管腳和信號(hào)的總調(diào)度一份合格的FPGA驅(qū)動(dòng)原代碼絕不是只寫一個(gè)邏輯模塊就完事了。它至少包含頂層模塊、功能子模塊、約束文件和仿真測(cè)試文件。頂層模塊的作用是把FPGA內(nèi)部邏輯和外部引腳掛接起來(lái)相當(dāng)于整個(gè)工程的“總調(diào)度”。在頂層里你會(huì)實(shí)例化各個(gè)子模塊然后用wire或reg把模塊之間的信號(hào)連起來(lái)同時(shí)把這些內(nèi)部信號(hào)映射到FPGA的物理引腳上。頂層模塊寫起來(lái)并不復(fù)雜但有一個(gè)地方特別容易出問題信號(hào)命名。我見過(guò)不少人子模塊內(nèi)部信號(hào)名和外層信號(hào)名亂成一團(tuán)到了仿真階段波形里找信號(hào)找得想砸電腦。我的習(xí)慣是所有信號(hào)名遵循統(tǒng)一規(guī)范比如頂層用s_開頭表示輸入m_開頭表示輸出內(nèi)部信號(hào)用w_前綴表示wire。這樣可以少刪很多頭發(fā)。3.2 一個(gè)最簡(jiǎn)SPI Master驅(qū)動(dòng)的完整實(shí)現(xiàn)以SPI Master驅(qū)動(dòng)為例我拆一個(gè)可以直接抄作業(yè)的簡(jiǎn)化版本。假設(shè)要做一個(gè)8bit SPI主機(jī)工作模式選CPOL0、CPHA0也就是SCLK空閑為低、數(shù)據(jù)在上升沿采樣。狀態(tài)機(jī)分成IDLE、SHIFT、DONE三個(gè)狀態(tài)內(nèi)部用計(jì)數(shù)器產(chǎn)生SCLK時(shí)鐘分頻同時(shí)把8bit數(shù)據(jù)通過(guò)移位寄存器逐位送出、逐位采樣接收。module spi_master #( parameter CLK_DIV 100 // 系統(tǒng)時(shí)鐘/SCLK 100即500kHz50MHz )( input wire clk, input wire rst_n, input wire start, input wire [7:0] data_in, output reg [7:0] data_out, output reg spi_cs_n, output reg spi_sclk, output reg spi_mosi, input wire spi_miso ); localparam IDLE 2d0, SHIFT 2d1, DONE 2d2; reg [1:0] state, next_state; reg [6:0] cnt; // 分頻計(jì)數(shù)器 reg [2:0] bit_cnt; // 發(fā)送/接收位計(jì)數(shù) reg [7:0] shift_reg; // 移位寄存器 // 時(shí)鐘分頻與SCLK產(chǎn)生 always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt 7d0; spi_sclk 1b0; end else if (state SHIFT) begin if (cnt CLK_DIV/2 - 1) begin cnt 7d0; spi_sclk ~spi_sclk; end else begin cnt cnt 1b1; end end else begin cnt 7d0; spi_sclk 1b0; end end // 三段式狀態(tài)機(jī)第一段狀態(tài)跳轉(zhuǎn) always (posedge clk or negedge rst_n) begin if (!rst_n) state IDLE; else state next_state; end // 第二段次態(tài)組合邏輯 always (*) begin case (state) IDLE : next_state start ? SHIFT : IDLE; SHIFT : next_state (bit_cnt 3d7 spi_sclk 1b1) ? DONE : SHIFT; DONE : next_state IDLE; default: next_state IDLE; endcase end // 第三段輸出與移位寄存器 always (posedge clk or negedge rst_n) begin if (!rst_n) begin spi_cs_n 1b1; spi_mosi 1b0; shift_reg 8d0; bit_cnt 3d0; data_out 8d0; end else begin case (state) IDLE: begin spi_cs_n 1b1; if (start) begin shift_reg data_in; spi_cs_n 1b0; bit_cnt 3d0; end end SHIFT: begin // SCLK上升沿前送數(shù)據(jù)下降沿采樣MISO if (spi_sclk 1b0 cnt 7d0) begin spi_mosi shift_reg[7]; shift_reg {shift_reg[6:0], 1b0}; end if (spi_sclk 1b1 cnt CLK_DIV/2 - 1) begin shift_reg[0] spi_miso; bit_cnt bit_cnt 1b1; end end DONE: begin data_out shift_reg; spi_cs_n 1b1; end default: ; endcase end end endmodule這段代碼是簡(jiǎn)化版實(shí)際項(xiàng)目里還要加上異常處理、FIFO緩存和更復(fù)雜的狀態(tài)跳轉(zhuǎn)條件。我給的建議是拿到一份代碼不要直接復(fù)制到工程里就完事先對(duì)著時(shí)序圖捋一遍流程把每個(gè)信號(hào)的變化時(shí)刻弄明白這句話是“SPI驅(qū)動(dòng)設(shè)計(jì)”最核心的練習(xí)方法。3.3 跨時(shí)鐘域處理兩級(jí)同步器與異步FIFO就算協(xié)議邏輯寫得再對(duì)跨時(shí)鐘域處理少一步系統(tǒng)穩(wěn)定性和可靠性就會(huì)大打折扣。最簡(jiǎn)單也最常用的單比特跨時(shí)鐘域處理就是把異步信號(hào)連續(xù)打兩拍代碼量幾乎可以忽略reg sig_sync1, sig_sync2; always (posedge clk or negedge rst_n) begin if (!rst_n) begin sig_sync1 1b0; sig_sync2 1b0; end else begin sig_sync1 async_sig; sig_sync2 sig_sync1; end end但要注意兩級(jí)同步器只適用于單比特信號(hào)或者由單比特構(gòu)成的握手信號(hào)不能直接用來(lái)同步多比特總線。多比特?cái)?shù)據(jù)的跨時(shí)鐘域可靠方案是異步FIFOXilinx的XPM_FIFO、Intel的ALTSYNC_FIFO、或者國(guó)產(chǎn)廠商的FIFO IP都可以用盡量不要自己手寫格雷碼同步邏輯除非你純粹為了學(xué)習(xí)。DDR控制器內(nèi)部、PCIe的DMA路徑、以太網(wǎng)MAC和PHY之間的接口本質(zhì)上全是在做“異步FIFO 指針同步”這件事。3.4 約束和仿真驅(qū)動(dòng)代碼能不能上板的決定因素很多項(xiàng)目的驅(qū)動(dòng)代碼仿真波形明明是對(duì)的上板就是不工作。這時(shí)候90%的問題出在約束文件。以Xilinx為例你需要至少完成三件事一是物理引腳約束把邏輯網(wǎng)表里的端口映射到FPGA的物理引腳二是IO電平標(biāo)準(zhǔn)約束LVCMOS33、LVDS、SSTL這些不能亂配三是時(shí)鐘約束告訴工具你的時(shí)鐘頻率、時(shí)鐘來(lái)源、時(shí)鐘間是否需要同步。仿真文件也一樣重要。寫testbench的時(shí)候不要只給一個(gè)激勵(lì)信號(hào)、然后看波形“大概對(duì)了”就完事。至少要把整個(gè)通信過(guò)程做成一個(gè)完整的時(shí)序閉環(huán)模擬外部器件的行為在SPI總線上按從機(jī)時(shí)序回?cái)?shù)據(jù)在I2C總線上自動(dòng)響應(yīng)ACK在UART接收端把字節(jié)收回來(lái)之后和發(fā)送端比對(duì)。這樣仿真測(cè)過(guò)上板成功率高得多。我已經(jīng)習(xí)慣把testbench寫得比設(shè)計(jì)代碼還長(zhǎng)因?yàn)樵綇?fù)雜的驅(qū)動(dòng)模塊驗(yàn)證環(huán)境的投入越值得。4. 常見問題與排查技巧實(shí)錄4.1 仿真正確但上板完全沒反應(yīng)這個(gè)問題排在所有FPGA開發(fā)者的吐槽榜第一名。排查順序我建議按下面這張表來(lái)不要第一次就直接寫邏輯分析儀。排查步驟檢查內(nèi)容常見原因1引腳分配頂層信號(hào)沒有正確映射到FPGA引腳或者映射到了錯(cuò)誤的bank2電平標(biāo)準(zhǔn)與外部器件不匹配3.3V電平配成了1.8V或者LVDS沒加終端電阻3時(shí)鐘復(fù)位PLL沒有鎖定導(dǎo)致內(nèi)部時(shí)鐘沒起來(lái)復(fù)位極性搞反導(dǎo)致模塊一直處于復(fù)位態(tài)4外部硬件芯片沒上電、焊接虛焊、信號(hào)線接反這些往往最容易忽略我印象特別深的是有一次調(diào)一顆ADC驅(qū)動(dòng)仿真做了好幾天上板怎么讀數(shù)據(jù)都是0xFFFF。查到最后居然是一根片選信號(hào)在PCB上被下拉電阻拉死了。所以驅(qū)動(dòng)調(diào)試第一準(zhǔn)則先別懷疑代碼把板子上的硬件用示波器量一遍。4.2 數(shù)據(jù)偶爾錯(cuò)一位波形看著又沒什么問題這種“偶發(fā)錯(cuò)誤”是驅(qū)動(dòng)調(diào)試?yán)镒钅ト说膯栴}。通常不是因?yàn)閱未螘r(shí)序錯(cuò)了而是信號(hào)完整性或跨時(shí)鐘域問題——輸入信號(hào)建立保持時(shí)間不夠?qū)е虏蓸狱c(diǎn)落在數(shù)據(jù)跳變的邊沿上或者異步信號(hào)沒有同步引發(fā)了亞穩(wěn)態(tài)。排查手段無(wú)非三種加長(zhǎng)建立時(shí)間、降低時(shí)鐘頻率、加同步處理。先用降低頻率的方法驗(yàn)證是不是時(shí)序裕量不夠如果降頻之后錯(cuò)誤消失說(shuō)明就是時(shí)序問題再回頭優(yōu)化代碼和約束。4.3 時(shí)序收斂失敗怎么辦驅(qū)動(dòng)模塊邏輯本身不算復(fù)雜但當(dāng)它和DDR、PCIe、圖像處理這些大模塊組合到一起時(shí)時(shí)序收斂就會(huì)成為問題。最常見的瓶頸是組合邏輯鏈太長(zhǎng)或者扇出太大。方案也直接在關(guān)鍵路徑中間插一拍寄存器用流水線斷開長(zhǎng)邏輯鏈復(fù)制寄存器降低高扇出信號(hào)優(yōu)化狀態(tài)機(jī)把多路選擇邏輯盡量前移。總之時(shí)序報(bào)告里哪條路徑紅了就盯住那條路徑改。4.4 調(diào)試工具怎么用才能效率高FPGA內(nèi)部節(jié)點(diǎn)是不可見的所以必須依靠調(diào)試工具觀測(cè)內(nèi)部信號(hào)。Vivado里是ILA核Quartus里是SignalTap還有不少國(guó)產(chǎn)工具也有類似功能。但很多人的使用方式很原始把一堆信號(hào)都拉出來(lái)看波形里密密麻麻反而什么信息都抓不到。我建議先設(shè)置一個(gè)全局觸發(fā)條件比如片選信號(hào)的下降沿或者某個(gè)狀態(tài)機(jī)標(biāo)志位拉高觸發(fā)之后再逐級(jí)往下看數(shù)據(jù)通道。這樣定位問題的速度能快出好幾倍。5. 驅(qū)動(dòng)設(shè)計(jì)的學(xué)習(xí)路徑和實(shí)戰(zhàn)建議5.1 新手少踩彎路的路線圖做過(guò)分析和教學(xué)之后我總結(jié)了一條比較實(shí)用的學(xué)習(xí)順序先從簡(jiǎn)單的GPIO和按鍵消抖開始理解寄存器和always塊的基本用法然后寫UART收發(fā)這能讓你理解包頭、包尾、波特率對(duì)齊這些概念接著做SPI或I2C體會(huì)狀態(tài)機(jī)和時(shí)序參數(shù)約束這一步算是正式進(jìn)入驅(qū)動(dòng)設(shè)計(jì)的地界了然后再接觸DDR讀寫和高速接口比如LVDS接收、MIPI、PCIe你會(huì)發(fā)現(xiàn)前面練的基礎(chǔ)全都用得上。很多人一上來(lái)就買開發(fā)板做HDMI圖像輸出底層I2C配置EDID都還沒搞明白出了問題根本無(wú)從下手。5.2 選型比寫代碼更重要FPGA開發(fā)板和學(xué)習(xí)工具的選擇也很關(guān)鍵。Xilinx生態(tài)成熟、資料多但Vivado下載安裝比較折騰Intel的Quartus對(duì)新手稍微友好一些國(guó)產(chǎn)的安路、高云、紫光同創(chuàng)這些年進(jìn)步很快文檔和工具鏈也逐步跟上來(lái)了而且對(duì)國(guó)內(nèi)工程師來(lái)說(shuō)采購(gòu)便利、性價(jià)比高。接口外設(shè)的選型建議優(yōu)先選和實(shí)際項(xiàng)目相關(guān)的比如做電機(jī)控制的就找?guī)iss-C或編碼器接口的板子做視覺的就找?guī)IPI和HDMI的。用不上比用不上更浪費(fèi)。5.3 排錯(cuò)心態(tài)驅(qū)動(dòng)設(shè)計(jì)是耐心活做驅(qū)動(dòng)設(shè)計(jì)一半時(shí)間是看波形另一半時(shí)間是讀數(shù)據(jù)手冊(cè)。遇到問題不用慌驅(qū)動(dòng)調(diào)試本質(zhì)上是一個(gè)“二分定位”的過(guò)程先確認(rèn)時(shí)鐘復(fù)位沒問題再確認(rèn)管腳電平狀態(tài)然后拉ILA看內(nèi)部狀態(tài)機(jī)跳得對(duì)不對(duì)最后回頭查協(xié)議時(shí)序。只要每一步都驗(yàn)證清楚了問題一定能定位出來(lái)。我自己最喜歡的一句口頭禪是別急著改代碼先把數(shù)據(jù)手冊(cè)翻到對(duì)應(yīng)頁(yè)。最后再分享一個(gè)我自己的小習(xí)慣每一份驅(qū)動(dòng)模塊寫好之后我都會(huì)在testbench里把外部器件的時(shí)序模型建出來(lái)哪怕花上一個(gè)下午也絕不省掉這一步。剛開始看起來(lái)“慢得像蝸?!钡?xiàng)目越往后這個(gè)驅(qū)動(dòng)越穩(wěn)定需要回來(lái)返工的時(shí)間越少。驅(qū)動(dòng)設(shè)計(jì)這個(gè)東西前期多一分仔細(xì)上板調(diào)試就少掉一排頭發(fā)。本文還有配套的精品資源點(diǎn)擊獲取