字IC設(shè)計(jì)基礎(chǔ):串并轉(zhuǎn)換電路原理與Verilog實(shí)現(xiàn)詳解)
1. 項(xiàng)目概述從“手撕代碼”到數(shù)字世界的橋梁搭建在數(shù)字集成電路Digital IC的設(shè)計(jì)與驗(yàn)證面試、筆試乃至日常工作中“手撕代碼”是一個(gè)繞不開(kāi)的經(jīng)典環(huán)節(jié)。它考驗(yàn)的不僅僅是編碼能力更是對(duì)數(shù)字電路底層邏輯、時(shí)序概念和硬件描述語(yǔ)言HDL精髓的深刻理解。而“串轉(zhuǎn)并”與“并轉(zhuǎn)串”作為其中最基礎(chǔ)、最高頻的考題之一其地位堪比算法面試中的“反轉(zhuǎn)鏈表”。乍一看這似乎只是簡(jiǎn)單的數(shù)據(jù)格式轉(zhuǎn)換但深究下去你會(huì)發(fā)現(xiàn)它串聯(lián)起了時(shí)鐘域、數(shù)據(jù)同步、有限狀態(tài)機(jī)FSM、面積與功耗權(quán)衡等一系列數(shù)字IC設(shè)計(jì)的核心命題。最近在各大IC設(shè)計(jì)公司的筆試題如相關(guān)熱詞中提到的場(chǎng)景和社群討論里這類題目反復(fù)出現(xiàn)但很多朋友給出的答案往往只實(shí)現(xiàn)了功能卻忽略了在實(shí)際硅片中運(yùn)行所必須考慮的穩(wěn)健性。今天我就結(jié)合自己多年在數(shù)字前端設(shè)計(jì)中的踩坑經(jīng)驗(yàn)來(lái)徹底拆解這兩個(gè)電路不僅讓你能寫(xiě)出可通過(guò)編譯的代碼更能寫(xiě)出在真實(shí)芯片里穩(wěn)定工作的“硅級(jí)”代碼。2. 核心概念與設(shè)計(jì)思路拆解2.1 串行與并行的本質(zhì)時(shí)間與空間的交換在深入代碼之前我們必須建立清晰的物理圖景。串行Serial和并行Parallel是數(shù)據(jù)傳輸?shù)膬煞N基本方式。串行傳輸數(shù)據(jù)位bit在單條物理線路上按照時(shí)間順序依次傳輸。好比一條單車道車輛數(shù)據(jù)位一輛接一輛地通過(guò)。其優(yōu)點(diǎn)是節(jié)省連線資源引腳少但速度受限于單條線路的速率。并行傳輸多個(gè)數(shù)據(jù)位在同一時(shí)刻通過(guò)多條物理線路同時(shí)傳輸。好比一條多車道高速公路多輛車并排同時(shí)行駛。其優(yōu)點(diǎn)是在相同時(shí)鐘頻率下數(shù)據(jù)吞吐量大但需要更多的連線資源引腳多且會(huì)面臨各路徑延遲不一致skew帶來(lái)的挑戰(zhàn)。“串轉(zhuǎn)并”Serial-to-Parallel, S2P和“并轉(zhuǎn)串”P(pán)arallel-to-Serial, P2S電路就是實(shí)現(xiàn)這兩種傳輸方式相互轉(zhuǎn)換的接口電路。它們的核心思想是用時(shí)間換空間或用空間換時(shí)間。串轉(zhuǎn)并將一段時(shí)間內(nèi)陸續(xù)到達(dá)的串行數(shù)據(jù)收集、排列最終以一個(gè)并行的數(shù)據(jù)包形式一次性輸出。這是用時(shí)間多個(gè)時(shí)鐘周期換取了空間更寬的數(shù)據(jù)總線。并轉(zhuǎn)串將一個(gè)寬位的并行數(shù)據(jù)拆解成多個(gè)位在多個(gè)時(shí)鐘周期內(nèi)依次發(fā)送出去。這是用空間寬數(shù)據(jù)總線換取了時(shí)間多個(gè)傳輸周期。2.2 設(shè)計(jì)核心同步時(shí)序與有限狀態(tài)機(jī)數(shù)字IC設(shè)計(jì)是同步設(shè)計(jì)的世界一切都圍繞著時(shí)鐘信號(hào)有序展開(kāi)。因此設(shè)計(jì)串并轉(zhuǎn)換電路的首要原則是完全同步化。所有寄存器的操作、狀態(tài)的轉(zhuǎn)移都必須由統(tǒng)一的時(shí)鐘邊沿通常是上升沿觸發(fā)并使用同步復(fù)位。其次有限狀態(tài)機(jī)FSM是描述此類控制邏輯最直觀、最不易出錯(cuò)的方法。無(wú)論是串轉(zhuǎn)并的“數(shù)據(jù)收集”過(guò)程還是并轉(zhuǎn)串的“數(shù)據(jù)分發(fā)”過(guò)程都可以清晰地劃分為幾個(gè)狀態(tài)空閑IDLE、接收/發(fā)送SHIFT、完成DONE等。使用FSM可以使代碼結(jié)構(gòu)清晰便于后續(xù)的時(shí)序分析和功能驗(yàn)證。2.3 關(guān)鍵設(shè)計(jì)參數(shù)與接口定義在動(dòng)手寫(xiě)代碼前我們必須明確設(shè)計(jì)規(guī)格數(shù)據(jù)寬度N并行數(shù)據(jù)的位寬例如8-bit, 16-bit, 32-bit。這決定了串行傳輸需要多少個(gè)時(shí)鐘周期。時(shí)鐘與復(fù)位主時(shí)鐘clk和同步復(fù)位信號(hào)rst_n。串行接口輸入數(shù)據(jù)ser_data_in(1-bit)輸入數(shù)據(jù)有效ser_valid_in(1-bit)【關(guān)鍵設(shè)計(jì)點(diǎn)】輸出數(shù)據(jù)ser_data_out(1-bit)輸出數(shù)據(jù)有效ser_valid_out(1-bit)【關(guān)鍵設(shè)計(jì)點(diǎn)】并行接口輸出數(shù)據(jù)par_data_out(N-bit)輸出數(shù)據(jù)有效par_valid_out(1-bit)輸入數(shù)據(jù)par_data_in(N-bit)輸入數(shù)據(jù)有效par_valid_in(1-bit)控制與狀態(tài)信號(hào)開(kāi)始信號(hào)start(可選可由valid信號(hào)兼任)忙指示信號(hào)busy(表示電路正在轉(zhuǎn)換中無(wú)法接收新數(shù)據(jù))完成信號(hào)done(一次轉(zhuǎn)換完成)注意valid信號(hào)是確保數(shù)據(jù)正確傳輸?shù)纳€。在串行端它指示當(dāng)前ser_data_in上的數(shù)據(jù)是否有效在并行端它指示par_data_out上的數(shù)據(jù)是否穩(wěn)定有效。很多教科書(shū)式的簡(jiǎn)單例子會(huì)忽略這個(gè)信號(hào)但在實(shí)際工程中沒(méi)有流控機(jī)制的設(shè)計(jì)幾乎是不可用的。3. 串轉(zhuǎn)并S2P電路設(shè)計(jì)與實(shí)現(xiàn)3.1 電路結(jié)構(gòu)與工作流程一個(gè)穩(wěn)健的串轉(zhuǎn)并電路通常包含以下幾個(gè)部分移位寄存器核心部件用于按位接收并暫存串行數(shù)據(jù)。位寬等于并行輸出位寬N。位計(jì)數(shù)器計(jì)數(shù)已接收到的串行數(shù)據(jù)位數(shù)從0計(jì)數(shù)到N-1。控制FSM管理整個(gè)接收流程的狀態(tài)遷移。輸出鎖存寄存器當(dāng)接收滿N位后將移位寄存器中的值鎖存到并行輸出端口并產(chǎn)生有效脈沖。工作流程如下空閑狀態(tài)電路復(fù)位后處于空閑狀態(tài)等待開(kāi)始。開(kāi)始接收當(dāng)檢測(cè)到ser_valid_in有效時(shí)FSM進(jìn)入接收狀態(tài)。同時(shí)位計(jì)數(shù)器清零。逐位移位在接下來(lái)的每個(gè)時(shí)鐘周期如果ser_valid_in持續(xù)有效則將ser_data_in移入移位寄存器的最高位或最低位取決于約定位計(jì)數(shù)器加1。完成與輸出當(dāng)位計(jì)數(shù)器指示已接收滿N位有效數(shù)據(jù)后FSM進(jìn)入完成狀態(tài)。將移位寄存器的當(dāng)前值賦值給輸出鎖存寄存器并拉高par_valid_out一個(gè)時(shí)鐘周期。返回空閑完成輸出后FSM返回空閑狀態(tài)準(zhǔn)備下一次轉(zhuǎn)換。在此期間busy信號(hào)可以保持有效阻止新數(shù)據(jù)涌入。3.2 Verilog代碼實(shí)現(xiàn)與深度解析以下是一個(gè)位寬可參數(shù)化、帶有完整流控的串轉(zhuǎn)并轉(zhuǎn)換器代碼。我們約定先接收到的位是并行數(shù)據(jù)的最高位MSB。module serial_to_parallel #( parameter WIDTH 8 // 并行數(shù)據(jù)位寬可配置 )( input wire clk, input wire rst_n, // 串行輸入接口 input wire ser_data_in, input wire ser_valid_in, // 并行輸出接口 output reg [WIDTH-1:0] par_data_out, output reg par_valid_out, // 狀態(tài)指示 output wire busy ); // 定義狀態(tài)機(jī)狀態(tài) localparam S_IDLE 2b00; localparam S_RECV 2b01; localparam S_DONE 2b10; reg [1:0] state, next_state; reg [WIDTH-1:0] shift_reg; // 移位寄存器 reg [$clog2(WIDTH)-1:0] cnt; // 位計(jì)數(shù)器位寬自適應(yīng) reg [WIDTH-1:0] data_latch; // 輸出鎖存 // 狀態(tài)機(jī)第一段時(shí)序邏輯狀態(tài)寄存器 always (posedge clk or negedge rst_n) begin if (!rst_n) state S_IDLE; else state next_state; end // 狀態(tài)機(jī)第二段組合邏輯次態(tài)生成 always (*) begin next_state state; // 默認(rèn)保持當(dāng)前狀態(tài) case (state) S_IDLE: begin if (ser_valid_in) // 檢測(cè)到有效串行數(shù)據(jù)輸入開(kāi)始轉(zhuǎn)換 next_state S_RECV; end S_RECV: begin if (cnt WIDTH-1) // 計(jì)數(shù)器計(jì)滿表示已接收完N位 next_state S_DONE; // 否則保持在S_RECV狀態(tài)繼續(xù)接收 end S_DONE: begin // 完成狀態(tài)只保持一個(gè)周期然后回到空閑 next_state S_IDLE; end default: next_state S_IDLE; endcase end // 狀態(tài)機(jī)第三段時(shí)序邏輯各個(gè)寄存器的操作 always (posedge clk or negedge rst_n) begin if (!rst_n) begin shift_reg {WIDTH{1b0}}; cnt 0; par_valid_out 1b0; data_latch {WIDTH{1b0}}; end else begin par_valid_out 1b0; // 默認(rèn)無(wú)效僅在特定周期拉高 case (state) S_IDLE: begin cnt 0; if (ser_valid_in) begin // 在進(jìn)入接收狀態(tài)的第一個(gè)周期移入第一位數(shù)據(jù) shift_reg {shift_reg[WIDTH-2:0], ser_data_in}; cnt cnt 1; end end S_RECV: begin if (ser_valid_in) begin // 僅當(dāng)輸入有效時(shí)才移位和計(jì)數(shù) shift_reg {shift_reg[WIDTH-2:0], ser_data_in}; cnt cnt 1; end // 如果ser_valid_in無(wú)效則暫停計(jì)數(shù)和移位等待數(shù)據(jù) end S_DONE: begin // 鎖存數(shù)據(jù)并產(chǎn)生有效脈沖 data_latch shift_reg; par_valid_out 1b1; // 狀態(tài)機(jī)將在下一個(gè)周期自動(dòng)回到S_IDLE end endcase end end // 并行輸出賦值 always (*) begin par_data_out data_latch; end // 忙信號(hào)生成只要不在空閑狀態(tài)就處于忙狀態(tài) assign busy (state ! S_IDLE); endmodule代碼關(guān)鍵點(diǎn)解析三段式狀態(tài)機(jī)這是工業(yè)界最推薦的標(biāo)準(zhǔn)寫(xiě)法。第一段管理狀態(tài)寄存器第二段用組合邏輯決定下一個(gè)狀態(tài)第三段在現(xiàn)態(tài)下更新其他寄存器。結(jié)構(gòu)清晰有效避免了組合邏輯輸出可能產(chǎn)生的毛刺。ser_valid_in的作用在S_RECV狀態(tài)移位和計(jì)數(shù)操作都嚴(yán)格受ser_valid_in控制。這模擬了真實(shí)的串行數(shù)據(jù)流可能斷續(xù)的情況使電路更具魯棒性。如果數(shù)據(jù)連續(xù)則valid信號(hào)一直拉高即可。計(jì)數(shù)器與比較cnt WIDTH-1作為轉(zhuǎn)換完成的條件。注意我們是在計(jì)到WIDTH-1時(shí)即接收了第N個(gè)數(shù)據(jù)后的那個(gè)周期跳轉(zhuǎn)到S_DONE狀態(tài)。這意味著計(jì)數(shù)器記錄的是已經(jīng)接收完成的有效數(shù)據(jù)位數(shù)。輸出寄存與有效脈沖在S_DONE狀態(tài)我們將移位寄存器的值鎖存到data_latch并僅在一個(gè)時(shí)鐘周期內(nèi)拉高par_valid_out。這符合標(biāo)準(zhǔn)總線協(xié)議中“有效信號(hào)伴隨數(shù)據(jù)”的約定便于下游電路采樣。3.3 仿真測(cè)試與結(jié)果分析設(shè)計(jì)完成后必須通過(guò)仿真驗(yàn)證。測(cè)試平臺(tái)Testbench需要模擬各種場(chǎng)景理想連續(xù)數(shù)據(jù)流ser_valid_in始終為高連續(xù)輸入N位數(shù)據(jù)。斷續(xù)數(shù)據(jù)流ser_valid_in隨機(jī)高低模擬數(shù)據(jù)間隔。復(fù)位測(cè)試在轉(zhuǎn)換過(guò)程中進(jìn)行復(fù)位檢查電路是否能正確回到初始狀態(tài)。背靠背Back-to-Back測(cè)試一次轉(zhuǎn)換完成后立即開(kāi)始下一次檢查busy信號(hào)和狀態(tài)機(jī)是否能正確切換。一個(gè)簡(jiǎn)單的測(cè)試片段可能如下initial begin // 初始化 rst_n 0; ser_data_in 0; ser_valid_in 0; #20 rst_n 1; #10; // 測(cè)試案例1發(fā)送8‘b1010_1101 ser_valid_in 1; ser_data_in 1; #10; // MSB ser_data_in 0; #10; ser_data_in 1; #10; ser_data_in 0; #10; ser_data_in 1; #10; ser_data_in 1; #10; ser_data_in 0; #10; ser_data_in 1; #10; // LSB ser_valid_in 0; // 此時(shí)應(yīng)觀察到par_data_out變?yōu)?‘b1010_1101且par_valid_out出現(xiàn)一個(gè)周期高脈沖 #100; $finish; end在仿真波形中你需要重點(diǎn)觀察shift_reg的移位過(guò)程是否正確cnt的計(jì)數(shù)是否與valid信號(hào)同步par_valid_out是否恰好在接收完最后一位后的下一個(gè)周期拉高busy信號(hào)是否準(zhǔn)確反映了電路狀態(tài)。4. 并轉(zhuǎn)串P2S電路設(shè)計(jì)與實(shí)現(xiàn)4.1 電路結(jié)構(gòu)與工作流程并轉(zhuǎn)串是串轉(zhuǎn)的逆過(guò)程其核心思想是“并行加載串行移位輸出”。一個(gè)典型的并轉(zhuǎn)串電路包含并行加載寄存器在轉(zhuǎn)換開(kāi)始時(shí)鎖存輸入的并行數(shù)據(jù)。移位寄存器從并行加載寄存器獲取數(shù)據(jù)然后每個(gè)時(shí)鐘周期右移或左移一位將最低位或最高位輸出。位計(jì)數(shù)器計(jì)數(shù)已輸出的串行數(shù)據(jù)位數(shù)??刂艶SM管理加載、移位和輸出的流程。工作流程如下空閑狀態(tài)等待開(kāi)始命令。加載與啟動(dòng)當(dāng)檢測(cè)到par_valid_in有效或獨(dú)立的start信號(hào)FSM進(jìn)入加載狀態(tài)。將par_data_in鎖存到內(nèi)部寄存器位計(jì)數(shù)器清零或置為初始值并立即輸出第一位數(shù)據(jù)通常是MSB。逐位移位輸出在接下來(lái)的每個(gè)時(shí)鐘周期FSM進(jìn)入移位狀態(tài)。移位寄存器移動(dòng)一位新的最低位LSB被送到ser_data_out位計(jì)數(shù)器加1。同時(shí)ser_valid_out保持有效。完成當(dāng)位計(jì)數(shù)器指示已輸出N-1位因?yàn)榈谝晃辉诩虞d周期已輸出后FSM進(jìn)入完成狀態(tài)。在最后一個(gè)周期輸出最后一位數(shù)據(jù)。返回空閑輸出完成后ser_valid_out拉低FSM返回空閑狀態(tài)。4.2 Verilog代碼實(shí)現(xiàn)與深度解析以下是一個(gè)同樣參數(shù)化、帶流控的并轉(zhuǎn)串轉(zhuǎn)換器代碼。約定并行數(shù)據(jù)的最高位MSB最先被串行輸出。module parallel_to_serial #( parameter WIDTH 8 )( input wire clk, input wire rst_n, // 并行輸入接口 input wire [WIDTH-1:0] par_data_in, input wire par_valid_in, // 串行輸出接口 output reg ser_data_out, output reg ser_valid_out, // 狀態(tài)指示 output wire busy ); localparam S_IDLE 2b00; localparam S_LOAD 2b01; // 加載并行數(shù)據(jù)并輸出第一位 localparam S_SHIFT 2b10; localparam S_LAST 2b11; // 輸出最后一位 reg [1:0] state, next_state; reg [WIDTH-1:0] data_reg; // 存儲(chǔ)待移位的數(shù)據(jù) reg [$clog2(WIDTH)-1:0] cnt; // 狀態(tài)寄存器 always (posedge clk or negedge rst_n) begin if (!rst_n) state S_IDLE; else state next_state; end // 次態(tài)邏輯 always (*) begin next_state state; case (state) S_IDLE: if (par_valid_in) next_state S_LOAD; S_LOAD: next_state S_SHIFT; // 加載后立即進(jìn)入移位 S_SHIFT: if (cnt WIDTH-2) next_state S_LAST; // 再移WIDTH-2次后到最后一個(gè)數(shù)據(jù) // 注意cnt從0開(kāi)始在S_LOAD周期已經(jīng)輸出第一位所以當(dāng)cntWIDTH-2時(shí)表示已經(jīng)輸出了WIDTH-1位還剩最后一位在寄存器里 S_LAST: next_state S_IDLE; // 輸出最后一位后回到空閑 default: next_state S_IDLE; endcase end // 輸出邏輯與寄存器更新 always (posedge clk or negedge rst_n) begin if (!rst_n) begin data_reg {WIDTH{1b0}}; cnt 0; ser_data_out 1b0; ser_valid_out 1b0; end else begin ser_valid_out 1b0; // 默認(rèn)無(wú)效 case (state) S_IDLE: begin cnt 0; if (par_valid_in) begin // 在下一個(gè)周期S_LOAD才加載數(shù)據(jù)這里只是準(zhǔn)備 end end S_LOAD: begin // 加載并行數(shù)據(jù)并立即輸出MSB data_reg par_data_in; ser_data_out par_data_in[WIDTH-1]; // 輸出最高位 ser_valid_out 1b1; cnt 0; // 計(jì)數(shù)器清零表示已輸出0位相對(duì)于移位階段 end S_SHIFT: begin // 右移并輸出新的最高位原次高位 data_reg {data_reg[WIDTH-2:0], 1b0}; // 邏輯右移低位補(bǔ)0 ser_data_out data_reg[WIDTH-2]; // 輸出當(dāng)前寄存器的次高位移位后變成最高位 ser_valid_out 1b1; cnt cnt 1; end S_LAST: begin // 輸出最后一位數(shù)據(jù)當(dāng)前data_reg的最高位即原始數(shù)據(jù)的最低位LSB ser_data_out data_reg[WIDTH-2]; // 注意經(jīng)過(guò)S_SHIFT后原始LSB已經(jīng)到了data_reg[WIDTH-2]位置這里需要仔細(xì)推演。 // 更清晰的寫(xiě)法在S_SHIFT狀態(tài)我們輸出的是移位前的data_reg[WIDTH-2]。 // 當(dāng)進(jìn)入S_LAST時(shí)上一個(gè)S_SHIFT周期已經(jīng)輸出了第WIDTH-1位。此時(shí)data_reg里剩下的是最后一位原始LSB它位于data_reg[WIDTH-1]嗎 // 讓我們重新審視在S_LOAD我們加載了數(shù)據(jù)并輸出了data_reg[WIDTH-1] (MSB)。此時(shí)data_reg保持原樣。 // 進(jìn)入第一個(gè)S_SHIFT我們執(zhí)行 data_reg {data_reg[WIDTH-2:0], 1‘b0}并輸出移位前的 data_reg[WIDTH-2] (即原始數(shù)據(jù)的bit[WIDTH-2])。 // 因此經(jīng)過(guò)WIDTH-2次S_SHIFT后我們輸出了原始數(shù)據(jù)的bit[WIDTH-2] 到 bit[1]。此時(shí)data_reg中只剩下原始數(shù)據(jù)的bit[0] (LSB)在最高位其余位為0。 // 所以在S_LAST狀態(tài)我們應(yīng)該輸出當(dāng)前data_reg的最高位即data_reg[WIDTH-1]。 ser_data_out data_reg[WIDTH-1]; // 修正輸出最后一位 ser_valid_out 1b1; // 下一個(gè)周期自動(dòng)回到S_IDLEser_valid_out會(huì)被拉低 end endcase end end assign busy (state ! S_IDLE); endmodule代碼關(guān)鍵點(diǎn)與修正上述代碼在S_LAST狀態(tài)的輸出邏輯上容易混淆。更清晰且常見(jiàn)的實(shí)現(xiàn)方式是將“加載”和“輸出第一位”合并然后進(jìn)行N-1次移位輸出。下面提供一個(gè)更簡(jiǎn)潔、不易出錯(cuò)的版本使用一個(gè)work_cnt計(jì)數(shù)器來(lái)統(tǒng)一控制輸出過(guò)程module parallel_to_serial_v2 #( parameter WIDTH 8 )( input wire clk, input wire rst_n, input wire [WIDTH-1:0] par_data_in, input wire par_valid_in, output reg ser_data_out, output reg ser_valid_out, output wire busy ); localparam S_IDLE 1b0; localparam S_WORK 1b1; reg state, next_state; reg [WIDTH-1:0] shift_reg; reg [$clog2(WIDTH):0] work_cnt; // 計(jì)數(shù)范圍需要到WIDTH // 狀態(tài)寄存器 always (posedge clk or negedge rst_n) begin if (!rst_n) state S_IDLE; else state next_state; end // 次態(tài)邏輯 always (*) begin next_state state; case (state) S_IDLE: if (par_valid_in) next_state S_WORK; S_WORK: if (work_cnt WIDTH) next_state S_IDLE; // 輸出完WIDTH位后結(jié)束 endcase end // 輸出與計(jì)數(shù)邏輯 always (posedge clk or negedge rst_n) begin if (!rst_n) begin shift_reg 0; work_cnt 0; ser_data_out 0; ser_valid_out 0; end else begin ser_valid_out 1b0; // 默認(rèn) case (state) S_IDLE: begin work_cnt 0; if (par_valid_in) begin shift_reg par_data_in; // 可以在下一個(gè)周期(S_WORK的第一個(gè)周期)開(kāi)始輸出也可以選擇在加載周期就輸出第一位。這里選擇后者更高效。 ser_data_out par_data_in[WIDTH-1]; // 輸出MSB ser_valid_out 1b1; work_cnt 1; // 已經(jīng)輸出了1位 end end S_WORK: begin if (work_cnt WIDTH) begin // 右移輸出新的最高位 shift_reg {shift_reg[WIDTH-2:0], 1b0}; ser_data_out shift_reg[WIDTH-2]; // 注意這里輸出的是移位前寄存器的次高位 ser_valid_out 1b1; work_cnt work_cnt 1; end else begin // work_cnt WIDTH轉(zhuǎn)換完成狀態(tài)機(jī)將在下一個(gè)周期回到IDLE ser_valid_out 1b0; end end endcase end end // 注意上述S_WORK中的輸出邏輯在work_cntWIDTH-1時(shí)即最后一次移位輸出的將是原始數(shù)據(jù)的最低位(LSB)。 // 因?yàn)楫?dāng)work_cnt從1計(jì)數(shù)到WIDTH-1時(shí)我們執(zhí)行了WIDTH-1次移位和輸出加上IDLE周期輸出的MSB正好輸出了WIDTH位。 assign busy (state ! S_IDLE); endmodule這個(gè)版本邏輯更清晰work_cnt記錄已經(jīng)輸出的位數(shù)。在S_IDLE收到有效數(shù)據(jù)后立即加載并輸出第一位MSB同時(shí)work_cnt置1。然后在S_WORK狀態(tài)每周期移位并輸出一位直到work_cnt達(dá)到WIDTH表示所有位都已輸出完畢。4.3 仿真測(cè)試要點(diǎn)并轉(zhuǎn)串的測(cè)試需要關(guān)注輸出時(shí)序驗(yàn)證ser_data_out的變化是否發(fā)生在時(shí)鐘上升沿之后ser_valid_out是否嚴(yán)格伴隨有效數(shù)據(jù)。數(shù)據(jù)順序檢查串行輸出流的位順序是否與并行輸入數(shù)據(jù)的約定一致MSB first or LSB first。背靠背傳輸連續(xù)輸入兩個(gè)并行數(shù)據(jù)包觀察busy信號(hào)和輸出流之間是否有間隙電路是否能無(wú)縫銜接。輸入valid脈沖寬度測(cè)試par_valid_in僅為單周期高脈沖時(shí)電路是否能正確鎖存數(shù)據(jù)。5. 高級(jí)話題、優(yōu)化與常見(jiàn)問(wèn)題5.1 雙向串并轉(zhuǎn)換與參數(shù)化設(shè)計(jì)在實(shí)際應(yīng)用中一個(gè)模塊可能同時(shí)需要串轉(zhuǎn)并和并轉(zhuǎn)串功能例如某些通信芯片的SerDes串行器/解串器接口。我們可以將上述兩個(gè)模塊整合并增加方向控制信號(hào)dir例如1為并轉(zhuǎn)串0為串轉(zhuǎn)并。關(guān)鍵是要處理好內(nèi)部數(shù)據(jù)通路和狀態(tài)機(jī)的復(fù)用與隔離避免沖突。參數(shù)化設(shè)計(jì)我們已經(jīng)做了parameter WIDTH這使得模塊具有很好的可重用性。更進(jìn)一步可以參數(shù)化數(shù)據(jù)順序MSB/LSB first、是否注冊(cè)輸出等。5.2 時(shí)序收斂與性能考量關(guān)鍵路徑對(duì)于串轉(zhuǎn)并關(guān)鍵路徑可能在從ser_data_in經(jīng)過(guò)多級(jí)選擇器MUX到par_data_out的路徑上尤其是位寬很大時(shí)??梢酝ㄟ^(guò)在適當(dāng)位置插入流水線寄存器Pipeline Register來(lái)切割路徑提高最大工作頻率Fmax。面積優(yōu)化移位寄存器是主要面積消耗者。如果位寬極大如64、128需要評(píng)估是否必要。有時(shí)可以用雙端口RAM或FIFO來(lái)替代但會(huì)引入更復(fù)雜的控制邏輯。功耗考慮移位寄存器每個(gè)時(shí)鐘周期都在翻轉(zhuǎn)動(dòng)態(tài)功耗可觀。如果數(shù)據(jù)率不高可以引入時(shí)鐘門(mén)控Clock Gating在空閑或無(wú)有效數(shù)據(jù)時(shí)關(guān)閉部分寄存器的時(shí)鐘以降低功耗。5.3 常見(jiàn)問(wèn)題與調(diào)試技巧實(shí)錄數(shù)據(jù)錯(cuò)位Off-by-one error這是最常見(jiàn)的問(wèn)題。比如串轉(zhuǎn)并輸出提前或延后了一個(gè)周期。調(diào)試方法仔細(xì)繪制時(shí)序圖明確每個(gè)狀態(tài)周期下計(jì)數(shù)器值、移位寄存器內(nèi)容和輸出信號(hào)應(yīng)有的值。在仿真中重點(diǎn)觀察狀態(tài)跳轉(zhuǎn)的邊界條件cnt WIDTH-1還是cnt WIDTH。valid信號(hào)與數(shù)據(jù)不同步下游電路在采樣到par_valid_out為高時(shí)發(fā)現(xiàn)par_data_out的值不是期望的。原因valid信號(hào)生成邏輯有誤可能用了組合邏輯產(chǎn)生了毛刺或者比數(shù)據(jù)晚了一個(gè)周期。解決確保valid信號(hào)和數(shù)據(jù)在同一時(shí)鐘沿從寄存器中輸出即用時(shí)序邏輯生成valid。復(fù)位后狀態(tài)機(jī)卡死電路復(fù)位后無(wú)法響應(yīng)啟動(dòng)信號(hào)。檢查首先確認(rèn)復(fù)位邏輯是否正確所有寄存器包括狀態(tài)寄存器是否都被復(fù)位到初始值IDLE。其次檢查狀態(tài)機(jī)次態(tài)邏輯中是否所有可能的狀態(tài)轉(zhuǎn)移分支都已覆蓋特別是default分支是否指向S_IDLE。仿真與綜合結(jié)果不一致行為仿真RTL仿真通過(guò)但門(mén)級(jí)仿真Gate-level Simulation或?qū)嶋H芯片測(cè)試失敗??赡茉蛭闯跏蓟募拇嫫髟赗TL中沒(méi)有復(fù)位信號(hào)的寄存器在門(mén)級(jí)網(wǎng)表中是未知的X。確保所有功能寄存器都有明確的復(fù)位值或上電初始化。異步邏輯設(shè)計(jì)中混入了不純的同步邏輯比如在always (*)塊中對(duì)多個(gè)寄存器賦值但產(chǎn)生了隱含的鎖存器Latch。綜合工具和仿真器對(duì)Latch的處理可能不同。堅(jiān)持使用同步時(shí)序設(shè)計(jì)避免Latch。時(shí)序違例工作頻率太高關(guān)鍵路徑建立時(shí)間Setup Time或保持時(shí)間Hold Time不滿足。需要通過(guò)時(shí)序約束SDC和靜態(tài)時(shí)序分析STA來(lái)保證。關(guān)于“手撕代碼”面試的建議先問(wèn)清楚動(dòng)筆前一定要和面試官確認(rèn)接口細(xì)節(jié)數(shù)據(jù)寬度、有效信號(hào)協(xié)議、MSB/LSB順序、是否有使能或背壓信號(hào)。畫(huà)圖輔助在白板上畫(huà)出模塊框圖、狀態(tài)轉(zhuǎn)移圖和關(guān)鍵時(shí)序波形能極大減少邏輯錯(cuò)誤。邊寫(xiě)邊講解釋你為什么要用三段式狀態(tài)機(jī)為什么valid信號(hào)要這樣設(shè)計(jì)讓面試官看到你的思考過(guò)程??紤]邊界和異常主動(dòng)提出“如果輸入valid在轉(zhuǎn)換中間突然變低怎么辦”“如果連續(xù)兩個(gè)數(shù)據(jù)包背靠背輸入怎么辦”并給出你的設(shè)計(jì)處理方式例如使用busy信號(hào)反壓這能體現(xiàn)你的工程思維。從最基本的串并轉(zhuǎn)換電路出發(fā)我們觸及了同步設(shè)計(jì)、狀態(tài)機(jī)、流控、時(shí)序分析等數(shù)字IC設(shè)計(jì)的基石。把這些基礎(chǔ)打牢無(wú)論是應(yīng)對(duì)“手撕代碼”的考試還是進(jìn)行復(fù)雜系統(tǒng)的開(kāi)發(fā)你都會(huì)發(fā)現(xiàn)它們不過(guò)是這些基礎(chǔ)模塊以不同方式的組合與延伸。