鐘提?。合嚓P(guān)峰相位與Verilog實(shí)現(xiàn))
簡(jiǎn)介面向FPGA與數(shù)字通信開(kāi)發(fā)者這份壓縮包聚焦M序列在FPGA中的同步時(shí)鐘提取完整覆蓋從最長(zhǎng)線性反饋移位寄存器LFSR生成、VHDL工程實(shí)現(xiàn)到全數(shù)字鎖相環(huán)DPLL位同步的完整鏈路適合通信專業(yè)學(xué)生、硬件工程師以及學(xué)習(xí)自同步系統(tǒng)的讀者。資源共68個(gè)文件大小2.66MB主要包含VHDL/VHO源碼與工程配置、prj/xst設(shè)計(jì)工程文件、xwv仿真波形、PDF與DOC原理文檔等兼顧代碼閱讀、仿真復(fù)現(xiàn)和理論對(duì)照目錄結(jié)構(gòu)便于按需檢索。目前已有507人瀏覽學(xué)習(xí)。資料中給出了63位M序列的VHDL生成程序、基于DPLL的位同步時(shí)鐘提取設(shè)計(jì)以及Cyclone系列FPGA同步設(shè)計(jì)說(shuō)明從LFSR反饋網(wǎng)絡(luò)到鑒相器、低通濾波、壓控振蕩器再到時(shí)鐘分配網(wǎng)絡(luò)優(yōu)化與時(shí)鐘門控均有對(duì)應(yīng)源碼或文檔支撐。讀者可據(jù)此完成從算法仿真到FPGA工程移植的實(shí)踐也可直接用作課程設(shè)計(jì)、畢業(yè)設(shè)計(jì)或通信基帶同步模塊的方案參考。1. m序列在FPGA中的同步時(shí)鐘提取相關(guān)峰的相位才是真正的時(shí)鐘邊沿做接收機(jī)同步時(shí)我踩過(guò)最深的坑是示波器上明明有一條“時(shí)鐘”可解調(diào)出來(lái)的數(shù)據(jù)還是錯(cuò)的。因?yàn)楸镜夭蓸訒r(shí)鐘和信號(hào)碼時(shí)鐘來(lái)自兩個(gè)獨(dú)立振蕩器邊沿是騙人的相位差才是要害。m序列在FPGA中的同步時(shí)鐘提取做的就是利用m序列自相關(guān)峰的位置把遠(yuǎn)端碼時(shí)鐘的相位和頻率“摳”出來(lái)再修正本地時(shí)鐘。它不需要復(fù)雜的載波環(huán)路一段LFSR、一組滑動(dòng)相關(guān)器加一個(gè)小NCO就能在幾百行Verilog內(nèi)落地。適合做擴(kuò)頻突發(fā)同步、測(cè)距測(cè)速和低速載波恢復(fù)的開(kāi)發(fā)者。下面從原理講到RTL實(shí)現(xiàn)再給避坑清單。2. 為什么用m序列做同步時(shí)鐘提取自相關(guān)峰、多徑容限和三種實(shí)現(xiàn)結(jié)構(gòu)2.1 m序列的尖銳自相關(guān)峰是時(shí)鐘提取的“尺子”m序列是最長(zhǎng)線性反饋移位寄存器序列周期固定為2^n - 1n是移位寄存器階數(shù)。它有一個(gè)讓同步工程師非常舒服的性質(zhì)自相關(guān)函數(shù)在一個(gè)周期內(nèi)只有一個(gè)主峰主峰高度等于周期長(zhǎng)度其他所有旁瓣都是-1。用雙極性±1表示m序列、周期為N時(shí)歸一化自相關(guān)滿足R(τ)1當(dāng)τ≡0 (mod N)R(τ)-1/N當(dāng)τ≠0。這個(gè)“只有一根針”的峰就是我們要找的時(shí)鐘參考點(diǎn)。接收信號(hào)只要和本地m序列做滑動(dòng)相關(guān)當(dāng)本地序列與接收序列在碼相位上對(duì)齊時(shí)相關(guān)值會(huì)突然抬高。這個(gè)抬高的時(shí)間位置對(duì)應(yīng)著發(fā)送端碼片周期的起點(diǎn)也就是碼時(shí)鐘的邊沿。FPGA里的同步時(shí)鐘提取實(shí)際上是在跟蹤這個(gè)峰“滑到哪里”而不是在波形上硬找上升沿。為什么不用隨機(jī)PN序列因?yàn)閙序列是確定生成的接收機(jī)可以用同一個(gè)初始狀態(tài)復(fù)現(xiàn)完整的參考序列又因?yàn)樗哂袀坞S機(jī)性即使在噪聲下相關(guān)峰仍然尖銳。m序列的周期越長(zhǎng)相干處理增益越高同步時(shí)鐘提取的抗噪聲能力越強(qiáng)但代價(jià)是捕獲時(shí)間和相關(guān)器資源增加。工程上常用n7127碼片到n1532767碼片再高的階數(shù)一般交給匹配濾波器或頻域相關(guān)處理。多徑容限也來(lái)自這個(gè)尖銳峰。如果信道存在滯后路徑接收信號(hào)變成直達(dá)徑和多徑疊加相關(guān)函數(shù)附近會(huì)出現(xiàn)多個(gè)交疊的旁瓣。但只要直達(dá)徑的相關(guān)峰幅度明顯峰的位置仍可作為時(shí)鐘參考。后面避坑部分會(huì)講到副峰誤鎖這里先記住m序列的自相關(guān)峰是判斷碼相位最直接的依據(jù)。m序列的生成依賴本原多項(xiàng)式。同樣是n7抽頭選擇不同輸出的序列可能完全不同但只要抽頭是本原的周期都是127。設(shè)計(jì)時(shí)要注意初始狀態(tài)不能全0否則反饋永遠(yuǎn)為0LFSR會(huì)卡死在0狀態(tài)。由于m序列中0和1的個(gè)數(shù)僅相差1用0/1電平直接做相關(guān)會(huì)有直流偏移更穩(wěn)妥的做法是把0/1映射成雙極性1/-1或者在相關(guān)器里用“同或”代替“異或”把“相同位數(shù)”作為對(duì)齊判據(jù)。后續(xù)RTL代碼會(huì)體現(xiàn)這個(gè)選擇。2.2 滑動(dòng)相關(guān)、匹配濾波、延遲鎖定環(huán)FPGA里怎么選在FPGA里實(shí)現(xiàn)m序列同步時(shí)鐘提取常見(jiàn)有三條路線。結(jié)構(gòu)捕獲速度資源占用跟蹤精度適用場(chǎng)景滑動(dòng)相關(guān)器慢串行搜相位低一個(gè)相關(guān)器一般依賴搜索步進(jìn)突發(fā)短幀、低速率捕獲匹配濾波器快一拍出全部相位高L個(gè)相關(guān)器并行高相位分辨率高連續(xù)信號(hào)、捕獲時(shí)間敏感延遲鎖定環(huán)中等需先捕獲中超前/滯后兩個(gè)相關(guān)器很高閉環(huán)連續(xù)跟蹤長(zhǎng)期跟蹤、頻偏變化大我一般這樣選型捕獲階段用滑動(dòng)相關(guān)器鎖定后切到延遲鎖定環(huán)跟蹤。這是資源和性能最平衡的組合。純匹配濾波器在碼片速率高或周期長(zhǎng)時(shí)會(huì)吃掉大量寄存器和加法器。以周期127、8倍過(guò)采樣為例一個(gè)相關(guān)器需要1016級(jí)移位寄存器和1016個(gè)累加點(diǎn)并行做8個(gè)相位就是8000多個(gè)加法器很多中小型FPGA裝不下。純滑動(dòng)相關(guān)器結(jié)構(gòu)簡(jiǎn)單但捕獲時(shí)間隨周期和過(guò)采樣倍數(shù)線性增長(zhǎng)。假設(shè)每個(gè)碼相位駐留P個(gè)采樣時(shí)鐘過(guò)采樣OVS周期LEN則最壞捕獲時(shí)間約為L(zhǎng)ENOVSP個(gè)采樣時(shí)鐘。如果P8、OVS8、LEN127就是8128個(gè)時(shí)鐘周期對(duì)同步時(shí)間要求不高的系統(tǒng)可以接受。那么什么時(shí)候可以用滑動(dòng)相關(guān)器作為唯一方案當(dāng)m序列周期不超過(guò)255、過(guò)采樣倍數(shù)不超過(guò)8且接收機(jī)能容忍幾百個(gè)碼片周期的捕獲時(shí)間時(shí)完全可以用一組滑動(dòng)相關(guān)器加峰值檢測(cè)完成同步時(shí)鐘提取。這是本文后續(xù)實(shí)現(xiàn)所采用的結(jié)構(gòu)。如果需要連續(xù)跟蹤長(zhǎng)時(shí)間漂移再引入閉環(huán)環(huán)路。還有一個(gè)容易被忽略的參數(shù)過(guò)采樣倍數(shù)OVS決定了時(shí)鐘提取的分辨率。假設(shè)采樣時(shí)鐘為Fs碼片速率為Rc則OVSFs/Rc。滑動(dòng)相關(guān)器搜索的相位間隔是1/Fs也就是一個(gè)采樣周期同步時(shí)鐘使能的最大量化誤差為±0.5/Fs。OVS不夠大時(shí)相關(guān)峰即使能檢測(cè)到恢復(fù)出的時(shí)鐘也會(huì)在多個(gè)采樣點(diǎn)之間跳抖動(dòng)很大。所以后文的RTL都按OVS8來(lái)寫這也是工程上比較穩(wěn)妥的起點(diǎn)。如果硬件資源緊張至少保證OVS4并通過(guò)后面的DLL和內(nèi)插把精度補(bǔ)回來(lái)。3. 用Verilog實(shí)現(xiàn)m序列同步時(shí)鐘提取LFSR、滑動(dòng)相關(guān)器和峰值判決3.1 LFSR生成m序列初始狀態(tài)與反饋抽頭怎么定m序列在FPGA里就是線性反饋移位寄存器。不同階數(shù)的本原多項(xiàng)式對(duì)應(yīng)不同抽頭。例如7階常用多項(xiàng)式x^7 x^6 1周期127。下面是最小實(shí)現(xiàn)// 7階m序列生成器多項(xiàng)式 x^7 x^6 1 module m_seq_gen #( parameter N 7 )( input wire clk, input wire rst_n, input wire load, input wire [N-1:0] seed, output wire seq_out ); reg [N-1:0] lfsr; // 反饋抽頭來(lái)自本原多項(xiàng)式x^7和x^6對(duì)應(yīng)bit6和bit5 wire feedback lfsr[6] ^ lfsr[5]; always (posedge clk or negedge rst_n) begin if (!rst_n) lfsr 0; else if (load) lfsr seed; else // 左移后把反饋值補(bǔ)到最低位 lfsr {lfsr[N-2:0], feedback}; end assign seq_out lfsr[N-1]; endmodule邏輯說(shuō)明load有效時(shí)載入seed否則每個(gè)時(shí)鐘左移一位并把反饋位補(bǔ)到最低位。本原多項(xiàng)式?jīng)Q定反饋計(jì)算方式TAP_MASK和位序在不同工具里可能定義不同如果你綜合后發(fā)現(xiàn)輸出序列周期不對(duì)先交換抽頭方向再試。這不是玄學(xué)是LFSR左移/右移約定不一致導(dǎo)致的。初始狀態(tài)不能全0所以復(fù)位后要給一個(gè)非全0的seed。常用抽頭組合如下表都是本原多項(xiàng)式n階數(shù)周期抽頭位置(bit)531bit4, bit27127bit6, bit59511bit8, bit4112047bit10, bit81532767bit14, bit13參數(shù)說(shuō)明周期2^n - 1n越大相關(guān)增益越高但相關(guān)器長(zhǎng)度也越長(zhǎng)。用模板ROM存本地序列比每次從LFSR生成更省事也方便在相關(guān)器里生成任意相位偏移。工程上我會(huì)把整個(gè)過(guò)采樣模板在用前寫好而不是讓相關(guān)器動(dòng)態(tài)等LFSR輸出。這樣相關(guān)器的時(shí)序更可控。3.2 滑動(dòng)相關(guān)器寄存器鏈、累加器和符號(hào)位處理滑動(dòng)相關(guān)器把接收序列和本地模板逐位比較。接收信號(hào)經(jīng)過(guò)硬判決后是0/1本地模板也是0/1用異或門做比較即可。下面是核心代碼// 滑動(dòng)相關(guān)器輸入為過(guò)采樣后的硬判決比特輸出為對(duì)齊度 module sliding_correlator #( parameter LEN 127, // m序列長(zhǎng)度 parameter OVS 8 // 過(guò)采樣倍數(shù) )( input wire clk, input wire rst_n, input wire data_in, input wire [LEN*OVS-1:0] local_template, output wire [$clog2(LEN*OVS1)-1:0] corr_val ); reg [LEN*OVS-1:0] shift_reg; reg [$clog2(LEN*OVS1)-1:0] corr; integer i; // 每個(gè)采樣時(shí)鐘把輸入移入寄存器鏈 always (posedge clk or negedge rst_n) begin if (!rst_n) shift_reg {LEN*OVS{1b0}}; else shift_reg {shift_reg[LEN*OVS-2:0], data_in}; end // 組合邏輯累加同或結(jié)果為1表示該位相同 always (*) begin corr 0; for (i 0; i LEN*OVS; i i 1) begin corr corr (~(shift_reg[i] ^ local_template[i])); end end assign corr_val corr; endmodule邏輯說(shuō)明每個(gè)采樣時(shí)鐘將data_in移入shift_reglocal_template不動(dòng)組合邏輯計(jì)算兩者逐位同或的和。同或結(jié)果1表示相同因此corr_val越高表示越對(duì)齊最大值是LEN*OVS。對(duì)齊時(shí)出現(xiàn)尖銳主峰這個(gè)峰就是后續(xù)同步狀態(tài)機(jī)的輸入。參數(shù)說(shuō)明LEN*OVS是相關(guān)器長(zhǎng)度寄存器個(gè)數(shù)和累加器位寬都跟著它走。OVS不能太大否則相關(guān)器存儲(chǔ)量和組合邏輯延遲會(huì)快速上升也不能太小否則峰值量化誤差大。工程上OVS取4到8比較常見(jiàn)。若輸入不是單比特而是多比特軟信息則local_template也需要多比特異或改成乘法或查表資源會(huì)成倍增加。先用硬判決把方案跑通再升級(jí)軟相關(guān)是我常用的路徑。這里有一個(gè)容易忽略的邊界因?yàn)檫^(guò)采樣相關(guān)峰出現(xiàn)后會(huì)持續(xù)大約OVS個(gè)采樣時(shí)鐘。峰值檢測(cè)不能只看一個(gè)周期否則會(huì)把同一個(gè)峰的多個(gè)采樣點(diǎn)重復(fù)當(dāng)成新峰。更合理的做法是記錄“高值平臺(tái)”期間的峰值并在平臺(tái)結(jié)束時(shí)輸出峰值位置。3.3 峰值檢測(cè)與同步狀態(tài)機(jī)從“找到峰”到“鎖定時(shí)鐘”峰值檢測(cè)不能把corr_val超過(guò)閾值就立即認(rèn)為同步。過(guò)采樣后一個(gè)相關(guān)峰占了OVS個(gè)采樣點(diǎn)閾值命中會(huì)連續(xù)發(fā)生必須用狀態(tài)機(jī)把這些連續(xù)命中合并成一個(gè)同步事件。// 同步狀態(tài)機(jī)搜索 - 驗(yàn)證 - 鎖定 module sync_acq_fsm #( parameter THRESHOLD 900, // 主峰閾值按相關(guān)器滿量程*0.7~0.8 parameter LOCK_LOSS 500 // 失鎖閾值 )( input wire clk, input wire rst_n, input wire [$clog2(LEN*OVS1)-1:0] corr_val, output reg sync_pulse, output reg locked ); localparam IDLE 2b00, PEAK_MONITOR 2b01, LOCK 2b10; reg [1:0] state; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; sync_pulse 1b0; locked 1b0; end else begin sync_pulse 1b0; case (state) IDLE: begin if (corr_val THRESHOLD) state PEAK_MONITOR; end PEAK_MONITOR: begin // 連續(xù)兩個(gè)采樣點(diǎn)高于閾值才確認(rèn)峰 if (corr_val THRESHOLD) begin state LOCK; locked 1b1; sync_pulse 1b1; end else begin state IDLE; end end LOCK: begin // 跟蹤階段由NCO產(chǎn)生同步使能這里只檢測(cè)失鎖 if (corr_val LOCK_LOSS) begin state IDLE; locked 1b0; end end endcase end end endmodule邏輯說(shuō)明IDLE看到第一個(gè)超過(guò)閾值的采樣點(diǎn)后進(jìn)入PEAK_MONITOR下一個(gè)采樣點(diǎn)再次確認(rèn)避免毛刺誤觸發(fā)。進(jìn)入LOCK后同步時(shí)鐘使能不再直接取相關(guān)峰而是由后續(xù)NCO產(chǎn)生相關(guān)器只做失鎖監(jiān)視。這樣即使相關(guān)峰有輕微幅度波動(dòng)NCO仍能穩(wěn)定輸出碼時(shí)鐘。參數(shù)說(shuō)明THRESHOLD按滿量程的70%~80%取。OVS8、LEN127時(shí)滿量程1016閾值900只接受非常接近主峰的值捕獲靈敏度會(huì)差一些信噪比低時(shí)可降到800附近。LOCK_LOSS通常取主峰一半這里500是保守值。實(shí)際需要根據(jù)噪聲統(tǒng)計(jì)調(diào)整。這里的“鎖定時(shí)鐘”還是粗同步采樣鐘和碼鐘之間的固定頻偏由第四章的NCO和環(huán)路濾波消除。4. 在FPGA上跑通一套完整同步時(shí)鐘提取系統(tǒng)頂層例化、仿真激勵(lì)和NCO參數(shù)4.1 頂層模塊的端口定義和關(guān)鍵信號(hào)一個(gè)完整的m序列同步時(shí)鐘提取系統(tǒng)至少包含本地序列生成、滑動(dòng)相關(guān)器、同步狀態(tài)機(jī)、NCO和環(huán)路濾波五部分。頂層模塊可以這樣寫module m_sync_clk_extract #( parameter LEN 127, parameter OVS 8, parameter NCO_W 32 )( input wire clk_adc, // 采樣時(shí)鐘 input wire rst_n, input wire rx_bit, // 硬判決后的接收數(shù)據(jù) output wire sync_clk_en, // 恢復(fù)出的碼時(shí)鐘使能 output wire [NCO_W-1:0] freq_word, // NCO頻率字便于觀察 output reg locked ); wire [6:0] local_seq; wire [LEN*OVS-1:0] local_tmpl; wire [10:0] corr_val; wire acq_pulse; wire nco_step; m_seq_gen #(.N(7)) u_seq( .clk(clk_adc), .rst_n(rst_n), .load(init_done), .seed(7b0000001), .seq_out(local_seq) ); template_rom #(.LEN(LEN), .OVS(OVS)) u_tmpl( .clk(clk_adc), .rst_n(rst_n), .seq_in(local_seq), .tmpl_out(local_tmpl) ); sliding_correlator #(.LEN(LEN), .OVS(OVS)) u_corr( .clk(clk_adc), .rst_n(rst_n), .data_in(rx_bit), .local_template(local_tmpl), .corr_val(corr_val) ); sync_acq_fsm #(.THRESHOLD(900), .LOCK_LOSS(500)) u_fsm( .clk(clk_adc), .rst_n(rst_n), .corr_val(corr_val), .sync_pulse(acq_pulse), .locked(locked) ); code_nco #(.NCO_W(NCO_W), .OVS(OVS)) u_nco( .clk(clk_adc), .rst_n(rst_n), .freq_word(nco_step), .sync_en(sync_clk_en) ); endmodule邏輯說(shuō)明本地m序列先串行生成再由template_rom展開(kāi)成過(guò)采樣模板。相關(guān)器在同一個(gè)采樣時(shí)鐘下工作同步狀態(tài)機(jī)輸出acq_pulseNCO根據(jù)頻率字產(chǎn)生穩(wěn)定的碼時(shí)鐘使能。這里的nco_step初始值是常量實(shí)際系統(tǒng)中會(huì)由環(huán)路濾波器修正。這個(gè)頂層骨架只展示信號(hào)關(guān)系沒(méi)有把環(huán)路濾波器畫(huà)出來(lái)。真實(shí)工程中sync_clk_en產(chǎn)生后需要比較它和狀態(tài)機(jī)峰位置之間的誤差然后更新freq_word。誤差計(jì)算在4.3節(jié)給出。4.2 仿真激勵(lì)怎么寫帶噪數(shù)據(jù)、相位偏移和時(shí)鐘頻偏寫testbench時(shí)模擬一個(gè)帶頻偏的發(fā)射信號(hào)比直接給理想方波更有價(jià)值。下面是一個(gè)常用做法// 發(fā)送端在tx_clk下生成m序列 wire tx_seq; m_seq_gen u_tx( .clk(tx_clk), .rst_n(rst_n), .load(1b1), .seed(7b0000001), .seq_out(tx_seq) ); // 將發(fā)射序列按8倍過(guò)采樣存入環(huán)形緩沖 reg [7:0] tx_buf [0:1015]; integer idx; always (posedge tx_clk) begin tx_buf[idx] tx_seq; idx (idx 1015) ? 0 : idx 1; end // 用相位累加器模擬收發(fā)時(shí)鐘偏移 reg [15:0] phase_acc; always (posedge clk_adc) begin phase_acc phase_acc data_step; // data_step模擬頻偏 end // 從發(fā)射序列緩沖中抽出一個(gè)采樣相位 wire rx_bit tx_buf[phase_acc[15:13]]; // 高3位作為8倍過(guò)采樣相位邏輯說(shuō)明發(fā)射序列每個(gè)碼片寫入8個(gè)相同值形成8倍過(guò)采樣連續(xù)波形。接收端phase_acc的累加步長(zhǎng)data_step如果等于碼速率對(duì)應(yīng)的期望步長(zhǎng)則收發(fā)完全同頻略大或略小就模擬出頻偏。rx_bit按相位累加器高3位從緩沖中取值相當(dāng)于接收端在發(fā)射序列的不同相位上采樣。這樣可以看到相關(guān)峰在時(shí)間軸上滑動(dòng)。參數(shù)說(shuō)明data_step的計(jì)算方式與NCO頻率字相同。如果完整m序列周期127碼片、8倍過(guò)采樣總計(jì)1016個(gè)采樣點(diǎn)。data_step 期望每采樣點(diǎn)相位增量 * 2^16 / 1。例如1%頻偏時(shí)每采樣點(diǎn)相位應(yīng)增加1.01data_step 1.01 * 65536 ≈ 66191。phase_acc位寬要遠(yuǎn)大于地址位寬否則量化噪聲會(huì)掩蓋頻偏。仿真時(shí)觀察兩個(gè)指標(biāo)一是sync_clk_en間隔是否穩(wěn)定為8個(gè)采樣時(shí)鐘二是freq_word收斂到與頻偏匹配的值。4.3 NCO步長(zhǎng)與環(huán)路濾波器系數(shù)兩個(gè)必調(diào)參數(shù)碼時(shí)鐘NCO的核心是相位累加器溢出一次代表一個(gè)碼片周期結(jié)束。// 碼時(shí)鐘NCO位寬NCO_W溢出一次表示一個(gè)碼片周期 module code_nco #( parameter NCO_W 32 )( input wire clk, input wire rst_n, input wire [NCO_W-1:0] freq_word, output reg sync_en ); reg [NCO_W-1:0] phase_acc; always (posedge clk or negedge rst_n) begin if (!rst_n) begin phase_acc 0; sync_en 1b0; end else begin // 加法溢出位就是使能脈沖 {sync_en, phase_acc} phase_acc freq_word; end end endmodule邏輯說(shuō)明相位累加器每采樣周期增加freq_word溢出一次代表一個(gè)碼片時(shí)間到達(dá)。采樣時(shí)鐘80MHz、碼速率10MHz時(shí)期望NCO步長(zhǎng)為0.125 * 2^32 536870912。如果仿真里發(fā)現(xiàn)同步使能間隔不是8個(gè)采樣點(diǎn)優(yōu)先檢查這個(gè)值。環(huán)路濾波器系數(shù)是第二個(gè)必調(diào)參數(shù)。常見(jiàn)做法是把峰值檢測(cè)得到的峰位置和NCO產(chǎn)生的同步位置做差然后一階修正freq_wordalways (posedge clk or negedge rst_n) begin if (!rst_n) freq_word STEP_INIT; else freq_word freq_word Kp * phase_error_signed; endKp的取值直接決定穩(wěn)定性。Kp太大相位誤差加上噪聲后頻率字亂跳恢復(fù)時(shí)鐘抖動(dòng)明顯Kp太小頻偏跟蹤慢長(zhǎng)幀后面仍有殘余誤差。我一般先把Kp設(shè)為2的負(fù)10次方量級(jí)用定點(diǎn)表示就是右移10位然后在仿真里看同步使能相對(duì)理想時(shí)鐘的誤差曲線。誤差曲線收斂時(shí)間超過(guò)1000個(gè)碼片就加大Kp收斂后震蕩明顯就減小Kp。參數(shù)參考值調(diào)節(jié)方向NCO_W32位寬越大頻率分辨率越高STEP_INIT2^32 * Rc / Fs按碼速率和采樣速率計(jì)算Kp2^-10 ~ 2^-6過(guò)大抖動(dòng)過(guò)小跟蹤慢THRESHOLD滿量程 * 0.7~0.8過(guò)低誤鎖多過(guò)高丟同步5. m序列同步時(shí)鐘提取避坑誤鎖、多徑、時(shí)序違規(guī)的三個(gè)排查清單5.1 現(xiàn)象相關(guān)峰找得到恢復(fù)出的時(shí)鐘卻不斷跳變?cè)?jīng)遇到的情況是仿真里相關(guān)峰很漂亮板卡上鎖定指示也拉高了但sync_clk_en的間隔在第8和第9個(gè)采樣點(diǎn)之間來(lái)回跳解調(diào)誤碼率下不來(lái)。檢查后發(fā)現(xiàn)問(wèn)題在“相關(guān)峰和NCO沒(méi)有閉環(huán)”上。當(dāng)時(shí)只做了峰值檢測(cè)把sync_pulse當(dāng)成了時(shí)鐘而相關(guān)峰本身受噪聲影響會(huì)有±1個(gè)采樣點(diǎn)的抖動(dòng)時(shí)鐘自然跟著抖。原因和解決峰值檢測(cè)輸出的只是捕獲階段的粗同步不是真正穩(wěn)定的時(shí)鐘。正確做法是進(jìn)入LOCK狀態(tài)后放棄直接使用sync_pulse改用NCO產(chǎn)生sync_clk_en再用相關(guān)峰位置和NCO相位的差值去修正NCO頻率字。這個(gè)閉環(huán)會(huì)讓NCO輸出逐漸收斂到平均碼速率而不是跟著單次相關(guān)峰抖。如果你已經(jīng)在用閉環(huán)還是抖檢查過(guò)采樣倍數(shù)OVS4時(shí)的相位誤差量化步長(zhǎng)是1/4碼片環(huán)路很難壓到0建議先提高到8。另一個(gè)常見(jiàn)問(wèn)題是環(huán)路增益Kp設(shè)置太大這時(shí)誤差曲線會(huì)衰減震蕩把Kp往小了調(diào)就能改善。5.2 現(xiàn)象相關(guān)值超過(guò)閾值卻鎖在多徑副峰上一次多徑環(huán)境下的調(diào)試中相關(guān)器明明輸出了高峰值但同步點(diǎn)壓碼不準(zhǔn)。原因是直達(dá)徑被遮擋后某條多徑比直達(dá)徑更強(qiáng)或者多徑與直達(dá)徑疊加形成了偏移的合成峰。只拿單點(diǎn)最大值判斷就會(huì)把副峰當(dāng)主峰。原因和解決要從“高度判據(jù)”改成“形狀判據(jù)”。m序列自相關(guān)主峰附近應(yīng)當(dāng)是一個(gè)以峰為中心的三角形狀左右兩側(cè)斜率基本對(duì)稱。可以在FPGA里同時(shí)保存峰值前一個(gè)和后一個(gè)相關(guān)值做形狀判斷// 取峰值前、中、后三點(diǎn) reg [10:0] prev_corr, peak_corr, next_corr; wire left_ok (prev_corr (peak_corr 1)); wire right_ok (next_corr (peak_corr 1)); wire shape_ok left_ok right_ok;邏輯說(shuō)明只有peak_corr超過(guò)閾值且前后兩點(diǎn)都達(dá)到峰值一半以上才認(rèn)為主峰有效。多徑環(huán)境下若左右明顯不對(duì)稱就繼續(xù)搜索。更徹底的做法是采用超前滯后延遲鎖定環(huán)用超前臂和滯后臂的差值驅(qū)動(dòng)跟蹤它天然有抑制偏鎖的能力。捕獲階段仍然用最大值搜索鎖定后切到DLL能大幅減少副峰誤鎖。5.3 現(xiàn)象RTL仿真通過(guò)上板后同步狀態(tài)機(jī)亂跳或失鎖這是FPGA實(shí)現(xiàn)里最典型的“仿真和板卡不一致”?,F(xiàn)象包括鎖定指示燈時(shí)亮?xí)r滅、相關(guān)值在板卡上比仿真低一截、NCO頻率字讀數(shù)不穩(wěn)定。我最初以為是板卡噪聲后來(lái)用邏輯分析儀抓相關(guān)值發(fā)現(xiàn)corr_val在靠近時(shí)鐘沿的位置出現(xiàn)了毛刺。原因和解決一是相關(guān)器的組合累加邏輯沒(méi)有流水化輸入數(shù)據(jù)寬度LEN*OVS有1016位組合邏輯鏈太長(zhǎng)時(shí)鐘跑不高二是rx_bit來(lái)自外部接口沒(méi)有做時(shí)鐘域同步就進(jìn)入移位寄存器亞穩(wěn)態(tài)會(huì)污染整條相關(guān)鏈。處理辦法如下先給輸入打兩拍// 外部數(shù)據(jù)先同步避免亞穩(wěn)態(tài)進(jìn)入相關(guān)器 reg rx_bit_sync1, rx_bit_sync2; always (posedge clk_adc) begin rx_bit_sync1 rx_bit; rx_bit_sync2 rx_bit_sync1; end然后把for循環(huán)累加改成多級(jí)加法樹(shù)每級(jí)之間插入寄存器。綜合后看最差建立時(shí)間余量至少留正余量否則就把OVS降到4或降低采樣時(shí)鐘。另一個(gè)經(jīng)驗(yàn)是local_template用ROM或常量不要用大量寄存器扇出如果模板是動(dòng)態(tài)生成的相關(guān)器時(shí)序會(huì)很差。板卡調(diào)試時(shí)先用邏輯分析儀抓corr_val的高位看是否存在毛刺或寄生峰值再?zèng)Q定是改約束還是改結(jié)構(gòu)。6. 讓m序列同步時(shí)鐘提取更魯棒超前滯后延遲鎖定環(huán)與相關(guān)峰拋物線內(nèi)插6.1 超前-滯后DLL替換峰值檢測(cè)跟蹤態(tài)更穩(wěn)捕獲完成后如果要抑制多徑和噪聲我會(huì)把峰值狀態(tài)機(jī)切到超前滯后延遲鎖定環(huán)。超前臂和滯后臂分別用本地碼相位提前和滯后一點(diǎn)的相關(guān)值誤差e early - late。當(dāng)e0時(shí)本地碼正好對(duì)齊主峰。// DLL誤差計(jì)算early和late來(lái)自兩個(gè)相位差為1個(gè)采樣點(diǎn)的相關(guān)器 wire signed [15:0] early_corr, late_corr; wire signed [15:0] dll_err early_corr - late_corr; // 一階環(huán)路修正NCO步長(zhǎng)DLL_GAIN_SHIFT控制增益 freq_word freq_word (dll_err DLL_GAIN_SHIFT);DLL比峰值檢測(cè)晚鎖定因?yàn)樾枰韧瓿煞逯挡东@但它能在鎖定后把相位誤差壓得更小而且不容易鎖在單點(diǎn)噪聲上。我現(xiàn)在常用的做法是捕獲用滑動(dòng)相關(guān)確認(rèn)后切DLL兩者復(fù)用同一組相關(guān)器只是把本地模板相位偏移半個(gè)采樣點(diǎn)。DLL_GAIN_SHIFT建議從10開(kāi)始調(diào)看誤差收斂速度和噪聲。6.2 相關(guān)峰拋物線內(nèi)插把同步分辨率提到1/16碼片當(dāng)OVS8時(shí)峰值檢測(cè)只能給出1/8碼片分辨率的相位。如果還想進(jìn)一步提高可以用峰值附近三個(gè)采樣點(diǎn)做拋物線內(nèi)插。設(shè)峰值前一點(diǎn)C1峰值C0峰值后一點(diǎn)C2則峰頂點(diǎn)相對(duì)C0的偏移為delta (C1 - C2) / (2 * (C1 - 2*C0 C2))delta在-0.5到0.5之間。FPGA里實(shí)現(xiàn)時(shí)如果怕除法占用資源可以做一次乘法后右移因?yàn)榉帜冈谥鞣甯浇兓淮?。?nèi)插后的同步定位精度可以做到0.1采樣點(diǎn)以下等價(jià)于1/80碼片足夠應(yīng)對(duì)大多數(shù)低速擴(kuò)頻同步。加到這個(gè)精度后freq_word的每次修正要改成小步長(zhǎng)否則內(nèi)插帶來(lái)的高分辨率會(huì)被環(huán)路噪聲吃掉。我最早做同步時(shí)鐘提取時(shí)只用峰值檢測(cè)后來(lái)發(fā)現(xiàn)多普勒變化會(huì)讓峰值在幾個(gè)采樣點(diǎn)間移動(dòng)恢復(fù)時(shí)鐘抖動(dòng)明顯?,F(xiàn)在我的固定套路是峰值捕獲 - DLL跟蹤 - 拋物線內(nèi)插修正NCO相位三段式接在一起性能和資源都過(guò)得去。希望這一篇能讓你少走我當(dāng)初的彎路也希望你能在工程里把這些參數(shù)調(diào)成自己的組合真正把m序列的同步時(shí)鐘提取跑穩(wěn)。本文還有配套的精品資源點(diǎn)擊獲取