:AXI Quad SPI與N25Q128 Flash方案詳解)
我在這塊摸爬滾打了五六年遠程升級這個需求幾乎每個FPGA項目跑到后期都會冒出來——不管是現場設備固件迭代、算法參數在線調整還是產品出貨后悄悄修幾個不那么光彩的Bug。但說實話FPGA遠程升級比MCU那套要別扭得多尤其是當你以為“不就是往Flash里寫幾個數嘛”的時候設計本身已經在埋雷了。這篇就拿Xilinx 7系列Vivado環(huán)境配合AXI Quad SPI IP核操作N25Q128這塊再常見不過的128Mb SPI NOR Flash把整個流程從架構、寄存器、時序踩坑到回滾策略完整捋一遍。這篇文章適合誰正在做FPGA在線升級方案選型的、被ICAP和MultiBoot文檔繞暈的、寫完Flash讀寫卻發(fā)現上電起不來的還有那些準備把遠程升級當“最后一公里”交付給現場的工程師。我們直接開工。1. 遠程升級的整體架構先把“能回滾”想明白再動手遠程升級最怕的不是升級失敗而是失敗之后設備變磚人還得跑一趟現場。所以整個架構設計的核心不是“怎么把新固件寫進去”而是“寫壞了怎么回來”。N25Q128有16MB空間FPGA的bit文件通常在幾MB到十幾MB不等合理劃分這16MB讓系統(tǒng)同時具備正常啟動區(qū)和備份區(qū)是第一步。1.1 存儲布局Golden區(qū)、Update區(qū)與臨時緩存區(qū)我常用的分區(qū)方式是這樣的最前面的1MB放Golden鏡像出廠固化、永不擦除的“保命”版本接下來8MB放Update區(qū)日常遠程升級的落地區(qū)再往后的4MB作為Download Buffer接收新固件的臨時暫存區(qū)域校驗通過后才搬移到Update區(qū)最后留一些空間存版本信息和升級日志。這樣即使升級過程中途斷電最多丟一個未完成的臨時文件Golden鏡像永遠在。地址分配并不是固定的要根據實際bit文件大小調整但核心準則是Golden區(qū)必須獨立、寫保護、所有用戶邏輯都無法修改它。有些設計會把Golden區(qū)放在Flash末尾用跳線控制啟動地址但用AXI Quad SPI和MultiBoot機制的話Goldern放低地址段是默認且最簡單可靠的。1.2 為什么用AXI Quad SPI而不是普通SPI或者BPIN25Q128支持標準SPI、Dual SPI和Quad SPI模式。遠程升級最看重的就是寫入速度和總線打通效率。標準SPI一次只有一根數據線16MB寫滿得跑很久而Quad SPI模式下四根IO并行同樣的時鐘頻率下理論傳輸速度提升四倍。AXI Quad SPI IP把AXI4協(xié)議轉成Flash時序讓PS嵌入式ARM或者用戶邏輯可以通過內存映射的方式直接讀寫Flash。在7系列純FPGA場景下用戶邏輯通過AXI4-Lite或AXI4接口掛Master在PS場景下Zynq的MIO/EMIO可以連接到IP。雙場景統(tǒng)一用AXI Quad SPI的好處是驅動邏輯可以復用不需要為PS和PL寫兩套Flash驅動。1.3 啟動流程與遠程升級的關系7系列的上電加載流程是芯片先按配置模式引腳M[2:0]找到SPI Flash讀取起始地址0x000000的鏡像如果該鏡像頭部的CRC校驗失敗或者你通過ICAP主動發(fā)起了IPROG命令則跳到WBSTAR寄存器指定的地址繼續(xù)加載。這就給了我們很大的設計空間正常啟動時讀Golden區(qū)需要升級時先把新固件寫到Update區(qū)然后用WBSTAR把啟動地址指向Update區(qū)最后觸發(fā)ICAP重配置——啟動流程就變成立即用新固件運行。這個機制拆解下來會發(fā)現所謂“遠程升級”的本質就是改一個啟動地址 觸發(fā)一次重配置。這比以往聽到的各種云里霧里的說法樸素得多。2. AXI Quad SPI IP配置詳解最容易翻車的幾個寄存器Vivado里添加AXI Quad SPI IP并不難雙擊打開配置界面才是噩夢的開始。那些選項組合起來有幾十種可能選錯一個后面寫Flash寫到你懷疑人生。2.1 模式選擇Standard vs Dual vs QuadIP提供了三種模式Standard單線、Dual雙線、Quad四線。我強烈建議直接用Quad模式因為后續(xù)所有讀寫指令都統(tǒng)一走四線協(xié)議速度和兼容性都有了。Dual是過渡方案如果是第一次接這個IP為了穩(wěn)妥想先用單線打通也不是不行但后面再改IP配置、改約束、改驅動工作量翻倍。一個容易忽視的點是Quad模式下讀Flash用的是0x6B指令Fast Read Quad Output而寫Flash用的是0x32指令Quad Page Program。也就是說讀和寫都會走四線但指令碼完全不同驅動里要把這兩個指令分開定義清楚。2.2 時鐘比例與FIFO深度IP內部的SCK時鐘來自AXI時鐘分頻配置界面有幾個檔位2、4、8。N25Q128最高支持108MHz的讀時鐘但實際布線、PCB信號完整性以及溫度漂移都會限制這個值不建議直接頂到上限。我一般ACLK跑100MHzSCK分頻到50MHz左右寫Flash時頁面編程Page Program本身就受限于Flash內部寫時間SCK再高也無法突破讀的時候50MHz也是比較穩(wěn)的區(qū)間。FIFO深度則取決于你的AXI總線位寬和突發(fā)長度。選32或者64就夠用除非PS側DMA搬運數據才會考慮更深的FIFO。過大的FIFO只會浪費Block RAM資源。2.3 EZ模式與DIY模式的取舍這是IP最隱蔽、也最坑人的一個選項。INCR模式叫DTR其中有個“FIFO mode Legacy mode”的選擇EZ模式IP內部把Flash的狀態(tài)機封裝好了你只需要做AXI讀寫它會自動處理寫使能、輪詢忙標志、頁寫入邊界等底層細節(jié)??雌饋砻篮玫俣銓Φ刂房臻g的操作是連續(xù)的、自然對齊的不夠靈活。DIY模式IP只負責最基本的指令收發(fā)寫使能、狀態(tài)寄存器輪詢、擦除、頁編程這些要你自己通過SPI指令序列完成。遠程升級這種場景需要精細控制配置寄存器比如狀態(tài)寄存器的保護位而且跳轉啟動地址前要讀Flash狀態(tài)確認EZ模式的封裝反而礙事DIY模式才是能自由操控的方案。記住這個結論遠程升級用DIY模式純XAxi接口的流水數據搬運場景才考慮EZ模式。3. N25Q128關鍵指令與時序從擦除到編程每個步驟的Wait都省不得N25Q128是一款非常標準的SPI NOR Flash指令集在Micron/Numonyx的datasheet里都有但時序的嚴謹程度直接決定了你的Flash是健康工作十年還是三個月就壞塊。3.1 寫使能WREN 0x06的前置角色NOR Flash寫入前必須拉高狀態(tài)寄存器的WEL位Write Enable Latch就像你改系統(tǒng)關鍵配置前要輸入sudo密碼一樣。WREN指令只有一條0x06沒有地址階段。每執(zhí)行一次Page Program或Sector Erase都會自動把WEL位清0所以每次編程/擦除操作前必須先發(fā)WREN。很多新手的第一個坑就在這里寫了WREN之后立刻發(fā)Page Program然后發(fā)現數據根本沒寫進去大概率是WREN之后沒有輪詢狀態(tài)寄存器確認WEL已經拉高。正確的順序是發(fā)WREN → 讀狀態(tài)寄存器 → 確認WEL1 → 再發(fā)Page Program。3.2 狀態(tài)寄存器輪詢Busy Bit的等待邏輯N25Q128的Sector Erase典型時間是3秒Page Program典型時間是0.65ms。這些時間里Flash內部在忙外部任何指令除了RDSR0x05都被忽略。驅動必須循環(huán)讀狀態(tài)寄存器直到Bit0WIP位Write In Progress變成0才算完。這里有個時間預算的問題。Sector Erase一次要3秒如果你的升級固件有4MB按64KB一個Sector算就是64個扇區(qū)光擦除時間就192秒。這個時間用戶從APP層看就是“升級中”的等待體驗上必須給足提示。更關鍵的是如果你的驅動在上次擦除還沒結束時就直接發(fā)下一個指令Flash會忽略當前異常指令并導致狀態(tài)錯亂后續(xù)操作全部撲街。所以輪詢一定不能省而且要保證超時判斷足夠寬裕。我用的輪詢函數大致長這樣int flash_wait_ready(uint32_t timeout_ms) { uint32_t elapsed 0; uint8_t status 0; uint32_t command 0x05000000; // RDSR via AXI Quad SPI while (elapsed timeout_ms) { // 通過IP發(fā)送0x05指令并讀取狀態(tài)寄存器 status axi_quad_spi_transfer(command); if ((status 0x01) 0) { // WIP 0不忙了 return 0; } delay_ms(1); elapsed; } return -1; // 超時 }3.3 扇區(qū)擦除與數據保護位N25Q128的Sector Erase指令是0xD8按64KB一個扇區(qū)操作。官方還支持4KB的子扇區(qū)擦除0x20但升級固件動輒幾MB用4KB擦除會極其耗時只有更新配置參數時才劃算。所以升級場景默認用0xD8做整扇區(qū)擦除。N25Q128有BP0~BP3四個保護位位于狀態(tài)寄存器的高位。默認情況下保護區(qū)域是空的你可以自由擦寫。但一旦你開啟保護擦除和編程指令在保護地址范圍內會被硬生生拒絕。我見過產線上一批板子Flash寫不進去查半天發(fā)現是之前某次升級代碼里多了一句寫狀態(tài)寄存器保護位把整個Flash鎖死了。解鎖的方法是先發(fā)WREN然后往狀態(tài)寄存器寫入0x00清除所有保護位。這條邏輯要放在每一次擦除前做到“不管上次狀態(tài)如何這次都要強制解鎖”。3.4 Page Program與跨頁寫入N25Q128的Page Program指令Quad模式0x32一次最多寫256字節(jié)且這256字節(jié)必須落在同一個256字節(jié)對齊的Page內。如果你要寫的緩沖區(qū)跨越了Page邊界必須拆分成兩次甚至多次Page Program。這塊邏輯我通常在驅動層封裝好提供一個緩沖區(qū)任意長度寫入的接口內部自動拆頁int flash_write_buffer(uint32_t addr, const uint8_t *buf, uint32_t len) { uint32_t offset 0; uint32_t page_offset addr 0xFF; // 當前頁內偏移 uint32_t remaining_in_page 256 - page_offset; uint32_t chunk; while (len 0) { chunk (len remaining_in_page) ? len : remaining_in_page; flash_write_enable(); // 發(fā)送0x32指令寫入當前塊 flash_quad_page_program(addr offset, buf offset, chunk); flash_wait_ready(1000); // 等待頁編程完成 offset chunk; len - chunk; remaining_in_page 256; // 后續(xù)都從頁首開始 } return 0; }這個封裝的要點是首先生效了頁內偏移之后每次寫入都以256為基準不用擔心跨頁數據被拆亂。4. 雙鏡像啟動與MultiBoot細節(jié)WBSTAR和IPROG的正確用法4.1 Golden與Update的啟動地址設定回到分區(qū)設計。假設Golden鏡像在0x000000Update鏡像在0x1000001MB處。7系列的上電默認讀0x000000沒有問題。但要切換到Update啟動需要做兩件事把WBSTAR寄存器設為0x100000然后觸發(fā)IPROG命令。IPROG是ICAP接口提供的一條虛擬指令值通常是0x0000000F和0x0000AABB的組合具體取決于芯片族。通過ICAPE2原語發(fā)送IPROG后FPGA會立刻重新加載配置讀取WBSTAR指向的地址。這個“立即”很關鍵——它不經過PLD復位、不經過DDR復位相當于整個芯片熱重啟。4.2 ICAPE2原語的驅動實現ICAPE2在Vivado里是一個原語直接例化即可。7系列驅動ICAP發(fā)送IPROG的代碼框架如下module icap_reboot ( input wire clk, input wire trigger, input wire [31:0] wbstar_addr, output reg done ); // WBSTAR 寄存器設置 (0x2000??) // 需要先從ICAP寫入WBSTAR再寫入IPROG localparam WBSTAR_CMD 32h2000_0000; // 地址對齊后寫入 localparam IPROG_CMD 32h0000_000F; reg [31:0] icap_data; reg [7:0] icap_count; reg icap_csib; reg icap_rdwrb; always (posedge clk) begin if (trigger) begin // 先寫WBSTAR icap_csib 0; icap_rdwrb 0; // 寫模式 icap_data {8h00, wbstar_addr}; // 按ICAP時序組合 // ... 實際需要按ICAP字對齊累加 end end // 之后再發(fā)送IPROG指令并斷言CS_B等最終觸發(fā)配置加載 endmoduleIPROG的設計有一個很重要的細節(jié)WBSTAR的地址必須按32位字對齊即bit[1:0]00原因在SPI Flash啟動時地址是字地址而不是字節(jié)地址。如果你的Update區(qū)地址設計成0x100000那么寫字到WBSTAR的值是0x040000。這個換算很多人第一次都栽了啟動總是不對往往是多了一個左移兩位。4.3 回滾策略一種主動放棄的方式雙鏡像的好處是Update區(qū)固件如果因為自身Bug起不來Golden區(qū)仍然在Flash起始處上電默認還是從Golden啟動。但這里有個前提你的Update區(qū)Bootloader必須能判斷“自身不能正常工作”的條件。最終極的兜底方案是外置看門狗——FPGA重配置后如果軟件一段時間內沒有喂狗看門狗強制硬件復位復位后的啟動流程又回到Golden。所以在一個完整的遠程升級系統(tǒng)里FPGA內部的狀態(tài)機會這樣運轉處于Golden鏡像時收到升級請求把新固件下載到Download Buffer校驗CRC和鏡像頭寫Update區(qū)回讀校驗設置WBSTAR指向Update區(qū)并觸發(fā)IPROG。系統(tǒng)在Update鏡像中運行若運行期間檢測到致命錯誤、喂狗超時、或收到回滾命令則直接把WBSTAR改為0x000000并IPROG回到Golden。上電或復位時硬件默認從0x000000的Golden啟動不受任何遠程狀態(tài)干擾。這套策略哪怕遠端網絡中斷、固件下載一半斷電都不會導致不可恢復的變磚。設備最差也就是回到出廠版本這是遠程升級系統(tǒng)的保命底線。5. 遠程升級執(zhí)行流程從固件接收到ICAP重啟5.1 固件包的協(xié)議設計遠程升級不是把原始.bit文件裸著扔過去。現場網絡環(huán)境千差萬別丟包重傳、斷點續(xù)傳都是家常便飯。我建議自定義一個升級包格式至少包含以下幾個字段魔數Magic Word用于快速識別有效鏡像版本號主版本次版本構建號鏡像長度CRC32校驗值時間戳鏡像數據接收到固件包后先做完整性校驗再做啟動頭校驗Xilinx bit文件有固定的同步字32位0xFFFFFFFF的同步序列然后是0xAA995566確認這個包確實能當配置鏡像用才允許進入Flash編程階段。5.2 下載到Download Buffer的通道選擇如果你的FPGA系統(tǒng)有以太網口固件數據通常從網口進來通過DMA或者CPU搬運到DDR再從DDR讀到AXI Quad SPI寫進Flash。這一路帶寬瓶頸其實不在FPGA邏輯而在Flash的擦寫速度。實測下來N25Q128在Quad模式下寫完16MB需要大約40-60秒含擦除這個數據可以作為你給上層應用設計升級進度條的參考。如果是純PL設計比如沒有PS可以通過UART、SPI Slave或者PCIe接收固件寫入一個Block RAM或DDR作為緩存。DDRAXI DMA的方式在大固件面前優(yōu)勢很大否則就要用狀態(tài)機慢慢磨對協(xié)議棧超時控制壓力大得多。5.3 編程流程的完整狀態(tài)機整個升級寫Flash的流程我習慣用狀態(tài)機嚴格管理每個狀態(tài)都必須有超時跳轉和處理分支typedef enum { ST_IDLE, ST_UNLOCK, ST_ERASE, ST_PROGRAM, ST_VERIFY, ST_SET_BOOT_ADDR, ST_REBOOT, ST_ERROR } upgrade_state_t;各狀態(tài)的轉移條件和關鍵細節(jié)ST_UNLOCK解除Flash保護位。發(fā)WREN寫狀態(tài)寄存器0x00讀回確認WIP0、保護位清零成功后再往下走。ST_ERASE按扇區(qū)擦除Update區(qū)整片區(qū)域。每個扇區(qū)擦除前都要發(fā)WREN、等待WIP擦除完成后建議巡檢整個區(qū)域讀回來應該是全0xFF如果有殘留說明Flash有壞塊或擦除失敗直接進ST_ERROR并回滾。ST_PROGRAM逐塊寫入數據。這里要注意AXI Quad SPI IP的FIFO溢出問題——你的Master寫入速率不能超過IP到Flash的實際吞吐否則FIFO滿了之后IP會拉低AXI的WREADY此時你必須等待不可超時放棄。ST_VERIFY寫完后回讀整個Update區(qū)逐字節(jié)和源數據比較。這一步很多人偷懶但恰恰是它能在現場升級失敗前攔住99%的壞塊和電壓波動問題。ST_SET_BOOT_ADDR把WBSTAR設置為Update區(qū)地址寫完后讀回確認然后才進入最后的ST_REBOOT狀態(tài)。ST_REBOOT發(fā)送IPROG命令讓FPGA從新地址加載。這個狀態(tài)機里每個超時都要有“回滾到Golden”的出口。比如說擦除超時不要試圖反復重試直接退出升級流程、保持Golden啟動并把錯誤碼上報。5.4 驗證階段的附加保險啟動頭回讀除了逐字節(jié)比較我還會額外驗證Update區(qū)開頭的64字節(jié)確認同步字0xAA995566和頭部寄存器值正確。這是因為SPI Flash偶爾會出現一種極其隱蔽的錯誤——頁面編程時數據線毛刺導致個別位寫錯但讀取時因為電壓和時序的變化又讀出正確的值。這屬于寫后讀不一致的偶發(fā)失效單獨靠讀回校驗可能漏過去。加一個“啟動頭校驗”再配合CRC雙保險才能把現場返修率壓到最低。6. 實測階段最容易踩的坑給我打工資級別的血淚經驗這部分是全文的“付費內容”。以下每個坑我都真金白銀付過代價有的甚至是幾個通宵排查出來的。每一類問題的表象、根因、解決方式我都列在下面供你對照。6.1 坑一擦除期間IP的FIFO卡死與AXI死鎖表象寫Flash到一半CPU或狀態(tài)機跑飛AXI總線的WREADY一直拉低整個系統(tǒng)像被點穴一樣一動不動。根因AXI Quad SPI IP的發(fā)送FIFO深度有限當你連續(xù)寫入大量數據時如果Flash配置了比較慢的等待時間比如頁面編程本身需要時間IP內部的發(fā)送通道會阻塞而你沒有監(jiān)控AXI的WREADY信號傻乎乎等固定延遲結果產生總線上鎖。解決用戶邏輯每一個AXI寫操作都要使用正確的握手時序即等WREADY變高后才認為寫完成開啟IP的寄存器中斷Interrupt Enable Register使能TX FIFO Half Empty和TX FIFO Full中斷用于驅動層感知利用IP的SRRSoftware Reset Register作為最后殺招檢測到異常后軟復位IP讓總線脫離死鎖。6.2 坑二SCK頻率過高導致的數據毛刺表象讀取時每個扇區(qū)偶爾有一兩個字節(jié)是0xFF整個文件CRC怎么都對不上擦除、編程偶爾失敗但重試一次又好了間歇性發(fā)作。根因我按datasheet的極限值把SCK設到了108MHz但板級走線過長、EMI干擾、Flash芯片批次稍差的情況下高速時鐘下的數據采樣窗口不夠臨界時序導致毛刺。解決把SCK降到50MHz左右把FPGA內部IO約束加上IOBUF用于改善信號完整性同時在PCB上保證Flash的VCC旁路電容盡量靠近芯片引腳。這些“小事”能解決很大概率的偶發(fā)故障。我的習慣是量產前的驗證板用108MHz跑100小時不間斷讀寫老化測試如果沒問題再放開到高速實驗室環(huán)境直接降頻到50MHz能省掉80%的疑難雜癥。6.3 坑三IPROG后系統(tǒng)沒有從Update啟動表象IPROG觸發(fā)了但設備起來后發(fā)現運行的還是老版本固件。根因三個原因按概率排序WBSTAR寫入的地址沒有按32位字對齊跳到錯誤地址加載失敗后7系列會自動fallback到SPI的0x000000也就是Golden區(qū)所以看起來是“還運行老版本”ICAP的CE和WRITE信號時序不對IPROG命令根本沒有被識別Update區(qū)起始地址其實寫錯了和下載固件時用的地址不一致跳轉到了空白區(qū)域。排查用Vivado Hardware Manager連上JTAG檢查配置地址寄存器。如果你用的7系列UG570里面有個命令xil_rdcfg和xil_wrcfg可以用直接讀WBSTAR的值和預期比對。我印象最深的一次是花了很長時間檢查ICAP例化最后發(fā)現是WBSTAR地址左移兩位的問題這種低級錯誤往往最隱蔽。6.4 坑四斷電導致Update區(qū)損壞但擦除策略不合理表象升級過程中有時會出“部分扇區(qū)擦除了寫了一半另外一半還是舊數據”的現場。根因大固件升級時擦除和編程的時間窗口拉得很長。如果在這個窗口中發(fā)生掉電Update區(qū)的數據是一團漿糊——有些扇區(qū)是舊的、有些是擦除后的0xFF、有些是半寫狀態(tài)。解決設計上升級流程必須支持“基于Sector的雙緩沖升級”。比如你的Update區(qū)分為A/B兩個子區(qū)先在一個子區(qū)完成整包的擦除寫入校驗校驗成功后再切斷另一個子區(qū)的配置啟動路徑。如果中途掉電最壞情況就是當前子區(qū)壞了另一個子區(qū)還能啟動不會觸發(fā)IPROG。這在兩個子區(qū)都可用的情況下把掉電風險窗口縮短到只有一次切換動作的幾十毫秒。6.5 坑五驅動函數沒有做“至少一次”的解鎖保護表象設備在某些批次上工作正常另一些批次升級必失敗重新上電后又恢復。根因Flash的狀態(tài)寄存器保護位可能被上電時外部下拉或上拉電阻設置成了保護狀態(tài)不同批次的Flash上電默認值有差異。你把解鎖邏輯省略了直接把一切初始化建立在“保護位關閉”的假設上。解決每次寫操作前無條件執(zhí)行“WREN→寫狀態(tài)寄存器0x00→讀回確認”三步曲寧可多花幾毫秒也不能賭默認狀態(tài)。后續(xù)在加電初始化階段同步讀回狀態(tài)寄存器并斷言發(fā)現保護位異常就亮告警燈把問題在升級前暴露。這五個坑覆蓋了我在遠程升級項目上遇到的80%問題。剩下的20%基本都是網絡協(xié)議層和上層應用的問題不在本文討論范圍內但內核里的邏輯思想是通用的永遠假設最壞情況會發(fā)生然后為最壞情況留好后路。7. 寫在最后一個建議和一點體會如果只讓我留一條建議那就是在N25Q128這種SPI Flash上做遠程升級把驅動的每一條指令都當成“可能被下一次上電打斷”的原子操作來設計。寫使能、狀態(tài)輪詢、擦除、編程、讀回校驗每一步都要有軍規(guī)一樣的時序紀律缺失任何一環(huán)都可能把一個簡單的寫Flash任務變成拉鋸戰(zhàn)。實際項目里遠程升級的代碼量并不大難的是把各種異常路徑想清楚、測徹底。建議在實驗室做一個掉電矩陣測試在升級流程的每個狀態(tài)隨機斷電然后上電確認系統(tǒng)一定回到Golden區(qū)并能正常啟動。這個測試如果能連續(xù)跑上一百次不出問題我對現場的信心就很足了。好了這一路的坑和東XC細節(jié)都在這了。祝各位現場升級順利永不翻車。