以前接觸過一些FPGA的相關知識,藉着實現一個簡單的DPSK系統,順便複習和記錄一下Verilog HDL的簡單使用方法。準備直接用一張圖展示DPSK的調製解調原理,再按照模塊介紹Verilog的實現步驟,而後進行軟件仿真,最後給出完整的代碼。ide
DPSK,中文叫差分相位鍵控,與最簡單的BPSK調相系統很像,只不過DPSK是把數字信號源作了一個差分處理,用載波的相位變化來承載信號信息。這裏咱們讓系統越簡單越好,不考慮同步、信道、噪聲等因素。首先直接用一個手繪流程圖來展示DPSK系統信號的變化過程:函數
是否是很神奇,只要通過這些步驟,信號源信號就能被恢復出來!spa
這裏我用的是Quartus13.0加ModelSim,一個比較老的版原本作仿真,因此想按照下面配置跟着一步一步實現的話,最好選用相同的軟件比較好。因爲只想簡單快速實現一下系統,其實也是太難的模塊寫不出來。。。因此稍微難一點的地方都用自帶的IP核來實現了,相關的配置也會以圖片的形式總結出來。設計
要生成各類信號核進行信號之間的運算,確定要用時鐘來控制。這裏先給出幾個時鐘設計要求:3d
咱們已經有了固定的系統時鐘爲50Mhz了,因此只用分頻獲得10Mhz核100Khz的時鐘就能夠了。code
分頻器模塊代碼以下:component
module divide( clk,rst_n,clkout); input clk,rst_n; //輸入信號 output clkout; //輸出信號,能夠鏈接到LED觀察分頻的時鐘 parameter WIDTH = 3; //計數器的位數,計數的最大值爲 2**WIDTH-1 parameter N = 5; //分頻係數,請確保 N < 2**WIDTH-1,不然計數會溢出 reg [WIDTH-1:0] cnt_p,cnt_n; //cnt_p爲上升沿觸發時的計數器,cnt_n爲降低沿觸發時的計數器 reg clk_p,clk_n; //clk_p爲上升沿觸發時分頻時鐘,clk_n爲降低沿觸發時分頻時鐘 //上升沿觸發時計數器的控制 always @ (posedge clk or negedge rst_n ) //posedge和negedge是verilog表示信號上升沿和降低沿 //當clk上升沿來臨或者rst_n變低的時候執行一次always裏的語句 begin if(!rst_n) cnt_p<=0; else if (cnt_p==(N-1)) cnt_p<=0; else cnt_p<=cnt_p+1; //計數器一直計數,當計數到N-1的時候清零,這是一個模N的計數器 end //上升沿觸發的分頻時鐘輸出,若是N爲奇數獲得的時鐘佔空比不是50%;若是N爲偶數獲得的時鐘佔空比爲50% always @ (posedge clk or negedge rst_n) begin if(!rst_n) clk_p<=0; else if (cnt_p<(N>>1)) //N>>1表示右移一位,至關於除以2去掉餘數 clk_p<=0; else clk_p<=1; //獲得的分頻時鐘正週期比負週期多一個clk時鐘 end //降低沿觸發時計數器的控制 always @ (negedge clk or negedge rst_n) begin if(!rst_n) cnt_n<=0; else if (cnt_n==(N-1)) cnt_n<=0; else cnt_n<=cnt_n+1; end //降低沿觸發的分頻時鐘輸出,和clk_p相差半個時鐘 always @ (negedge clk) begin if(!rst_n) clk_n<=0; else if (cnt_n<(N>>1)) clk_n<=0; else clk_n<=1; //獲得的分頻時鐘正週期比負週期多一個clk時鐘 end assign clkout = (N==1)?clk:(N[0])?(clk_p&clk_n):clk_p; //條件判斷表達式 //當N=1時,直接輸出clk //當N爲偶數也就是N的最低位爲0,N(0)=0,輸出clk_p //當N爲奇數也就是N最低位爲1,N(0)=1,輸出clk_p&clk_n。正週期多因此是相與 endmodule
下面咱們來生成正弦載波信號,這裏咱們先給出此模塊的參數設計實現要求:blog
要實現數字調相系統,正弦載波的正確生成相當重要。這裏咱們直接用NCO IP核能夠用來做爲生產正弦載波的模塊。設置好生成頻率或數據精度等IP核內部參數後,只須要在主程序模塊中例化IP核,接口輸入設計中要求的參數便可。接口
NCO IP核的配置圖以下:圖片
按上圖這樣生成好IP核以後,會自動生成一個IP核的接口函數:
module sine_ip ( phi_inc_i, clk, reset_n, clken, phase_mod_i, fsin_o, out_valid); input [31:0] phi_inc_i; input clk; input reset_n; input clken; input [13:0] phase_mod_i; output [13:0] fsin_o; output out_valid; sine_ip_st sine_ip_st_inst( .phi_inc_i(phi_inc_i), .clk(clk), .reset_n(reset_n), .clken(clken), .phase_mod_i(phase_mod_i), .fsin_o(fsin_o), .out_valid(out_valid)); endmodule
上面的接口函數有幾個參數要注意一下:
(1) phi_inc_i是相位增益,它的大小控制着輸出正弦信號的頻率,它的位寬控制着輸出的精度,它的位寬咱們設置爲32。
因爲輸入時鐘頻率爲10Mhz,咱們想要的輸出信號頻率爲100Khz,輸出信號位寬M爲14,由公式:
計算出\(\phi_{INC}\)約爲42949673。將此相位增量做爲輸入,即可獲得指望頻率的正弦波。
(2) phase_mod_i是相位調整參數,它的大小控制着輸出正弦信號的相位,它的位寬控制着輸出的精度,它的位寬咱們設置爲14。
輸出相位爲0時,此值爲二進制14'b10_0110_0000_1010(好像是我試出來的?)。想要獲得相位差\(\pi\)的兩個正弦載波,只須要將第二個載波的調相參數值在前者的基礎上加上二進制的100...0,位數爲14,因而即可很方便的產生兩相位差\(\pi\)的載波。
(3) fsin_o是輸出正弦波,設置的位寬爲14。
關於信號源的生成,直接隨機生成的話感受B格不夠,因此準備用僞隨機序列:PN碼(也叫m序列)來當成生成的數字信號源,此模塊的參數設計要求以下:
用下圖的移位寄存器方式便能源源不斷的生成周期爲\(2^5\)的PN碼:
話很少說,代碼奉上:
module PnCode ( rst,clk,pn); input rst; //復位信號,高電平有效 input clk; //分頻獲得的PN碼生成時鐘,100khz output pn; //輸出的PN碼序列 //設置PN碼的本原多項式及初始相位 parameter Len = 5; //寄存器長度 wire [Len-1:0] reg_state = 5'b10110; //寄存器初值 wire [Len:0] polynomial= 6'b100101; //本原多項式 reg [Len-1:0] pn_reg = 5'b10110; //初始化與寄存器初值相同 reg pncode = 1'b0; integer i; reg poly=1'b0; always @(posedge clk) //這裏必定要在rst信號來的時候處於時鐘上升沿,要否則無法賦初值 if (rst) begin pn_reg <= reg_state; pncode <= 1'b0; end else begin //第1位寄存器的值爲根據多項式異或運算後的值 pn_reg[0] <= poly; //最末位寄存器的值輸出爲pn碼 pncode <= pn_reg[Len-1]; //pn_reg中的內容左移1位,左高位右低位 for (i=0; i<=(Len-2); i=i+1) pn_reg[i+1] <= pn_reg[i]; end integer j; ///用reg //根據多項式的值產生組合邏輯電路 always @(*) /// 用posedage??? for (j=(Len-1); j>=0; j=j-1) begin if (j==(Len-1)) poly = pn_reg[j]; else if (polynomial[j+1]) poly = poly ^ pn_reg[j]; end assign pn = pncode; endmodule
這裏的差分不是指把信號源取反的差分,而是指把信號源看做絕對碼,給定一個初始值而後輸出相對碼的過程。形象的波形變化見最開始的那個DPSK流程圖。因爲取了信號源的差分,因此在調製的時候至關於用載波相位的變化來承載信號,非相干解調時只須要把載波做個延時,而後與未延時的載波相乘,就能夠獲得解調信號了。因此這個模塊咱們輸出信號源的差分碼便可。
module difcode ( clk,rst,pn,difpn); input clk; //與pn同頻率100khz input rst; input pn; output difpn; reg difpn1; always @(posedge clk or posedge rst) if(rst) difpn1 <= 1'b0; else begin if((pn == 0) && (difpn1 == 0)) difpn1 <= 0; else if((pn == 0) && (difpn1 == 1)) difpn1 <= 1; else if((pn == 1) && (difpn1 == 0)) difpn1 <= 1; else if((pn == 1) && (difpn1 == 1)) difpn1 <= 0; end assign difpn = difpn1; endmodule
這個其實都不能算做是一個模塊,由於太簡單了,就作一個開關,當上面數據源爲1時,輸出相位爲0的正弦載波;當數據源爲0時,輸出相位爲\(\pi\)的正弦載波。但爲何又把它做爲一個模塊呢?由於其實這就是信號的相位調製部分啊!不用考慮其餘複雜的處理的話,輸出的信號就可以發射出去通過信道,而後被接收了。
module data_sel ( clk,difpn,sine1,sine2,sine_mod); input clk; input difpn; input [13:0]sine1; input [13:0]sine2; output [13:0]sine_mod; reg [13:0]sine_mod1; always @(posedge clk) if(difpn == 1) sine_mod1 <= sine1; else sine_mod1 <= sine2; assign sine_mod = sine_mod1; endmodule
延時與相乘模塊已經算是DPSK的解調部分了,DPSK系統的好處就是它解調簡單,如最開始波形圖所示,只須要把接收到的載波信號與延時一個碼元長度的載波信號相乘,再通過低通濾波器的處理就能夠恢復源信號了
關於延時的部分,咱們只須要延時一個PN碼碼元長度便可,即100Khz時鐘的一個週期長度。本設計採用的是先將差分碼延時,再利用相位選擇法調製的方式,而不是先調製再延時。其中延時部分能夠根據PN碼的生成時鐘來設計,由於PN碼的每個碼都是在時鐘上升沿產生的,因此在100KHz的分頻時鐘降低沿到達時,將此時的信號存儲起來,置於寄存器中,在下一個分頻時鐘信號上升沿到達時輸出存儲的信號,便獲得了延時一個碼元長度的PN碼。通過數據選擇器後,相應的輸出波形也延時相同長度。
關於相乘的部分,這裏用的是相乘器IP核LPM_MULT,具體參數配置以下圖:
只用設置輸出位寬就OK了,生成的IP核接口代碼以下:
module mult18 ( dataa, datab, result); input [13:0] dataa; input [13:0] datab; output [27:0] result; wire [27:0] sub_wire0; wire [27:0] result = sub_wire0[27:0]; lpm_mult lpm_mult_component ( .dataa (dataa), .datab (datab), .result (sub_wire0), .aclr (1'b0), .clken (1'b1), .clock (1'b0), .sum (1'b0)); defparam lpm_mult_component.lpm_hint = "MAXIMIZE_SPEED=5", lpm_mult_component.lpm_representation = "SIGNED", lpm_mult_component.lpm_type = "LPM_MULT", lpm_mult_component.lpm_widtha = 14, lpm_mult_component.lpm_widthb = 14, lpm_mult_component.lpm_widthp = 28; endmodule
在進行最終的信號判決恢復以前,咱們還須要對解調信號進行低通濾波。從最前面的信號波形處理流程圖能夠看出,通過相乘模塊以後的信號從‘上下上下’這種正弦型振盪變成了‘上上下下’這種相似全波整流的振盪信號,但這還不足以讓咱們恢復原始的PN碼信號源,因而使用低通濾波器濾去高頻,使得信號更平滑、正和負區分的更爲明顯一些。
這裏咱們仍是使用IP核模塊FIR Compiler來設計低通濾波器,對於低通濾波器核的設計分爲如下四步:
第一步:在工程文件中新建一個FIR Compiler v13.0核。以後進入一個新的參數設置界面,具體界面以下圖所示。
第二步:設置FIR核參數。設置低通濾波器係數位寬爲12比特;濾波器實現結構選擇多時鐘週期結構(Multi-Cycle),不一樣的結構所須要的內部資源不一樣,運算速率也不一樣;根據前面乘法器輸出模塊數據的位寬,肯定濾波器輸入位寬爲28bit,而後FIR核會自動計算出濾波器輸出數據位寬爲45bit。
第三步:設置濾波器參數。進入Edit Coefficient Set界面。選擇低通濾波器類型,目的是濾去解調輸出的高頻信號,恢復基帶信號;在濾波器採樣頻率方面,因爲在正弦載波生成模塊使用的輸入時鐘信號爲10MHz,因而對數據進行採樣輸入濾波器中時,也設置相同的頻率10MHz;選擇矩形窗口類型。在濾波器截止頻率設置過程當中,考慮基帶PN碼生成時鐘頻率爲100KHz,因此濾波器截止頻率設置爲50KHz。這樣濾波後能正確地恢復原信號。
最終生成FIR濾波器IP核接口,下面是本身又寫了個小模塊來使用這個接口的代碼:
module fir_mod( clk,reset_n,sine_demod1,sine_demod2); input clk,reset_n; input signed [27:0] sine_demod1; output signed [44:0] sine_demod2; wire sink_valid, ast_source_ready, ast_source_valid; wire [1:0] ast_sink_error; wire [1:0] ast_source_error; assign ast_source_ready = 1'b1; assign ast_sink_error = 2'd0; //reg count; //1clk reg ast_sink_valid; always @(posedge clk or negedge reset_n) if (!reset_n) ast_sink_valid <= 1'b0; else ast_sink_valid <= 1'b1; //每一個時鐘信號都有1個輸入信號,因此ast_sink_valid一直爲1,不然應該有時候爲0的 assign sink_valid = ast_sink_valid; fir_lpf a1( //例化IP核接口 .clk(clk), .reset_n(reset_n), .ast_sink_data(sine_demod1), .ast_sink_valid(sink_valid), .ast_source_ready(ast_source_ready), ////111 .ast_sink_error(ast_sink_error), .ast_source_data(sine_demod2), .ast_sink_ready(ast_sink_ready), ////////1111 .ast_source_valid(ast_source_valid), .ast_source_error(ast_source_error)); endmodule
頂層代碼模塊和各個子模塊寫完了,接下來在軟件仿真前須要寫一個testbench文件來支持軟件仿真,其實也就是例化一下頂層模塊,而後看你心情給輸入參數誰便賦個值,賦值的時候必定要注意時鐘和復位信號要與頂層模塊計劃的值的大小相匹配,輸入用reg,輸出用wire。還有在setting裏必定要弄好仿真的相關設置!
`timescale 1ps / 1ps module test_tb(); reg rst1; reg clk; wire led1; DPSK_system u0( .rst1(rst1), .clk(clk), .led1(led1)); parameter PERIOD = 20; // 設置系統時鐘爲50Mhz always #20 clk = ~clk; initial begin clk = 1'b0; #40; rst1 = 1'b0; #40; rst1 = 1'b1; end endmodule
頂層代碼和各個模塊寫完了,接下來進行Modelsim軟件仿真,下面放一個DPSK系統調製解調波形的全家福,能夠對比開頭畫的那個波形流程圖看,完美的實現了。
module DPSK_system ( rst1,clk,led1); input rst1; // 復位信號,高電平有效 input clk; // FPGA系統時鐘:50MHz output led1; //亮個燈玩玩 wire reset_n,out_valid1,out_valid2,clken; wire [31:0] phi_inc_i1; // 相位增益,生成特定頻率正弦信號用 wire [31:0] phi_inc_i2; // 相位增益,生成特定頻率正弦信號用 wire [13:0]phase_mod_i1; // 相位調整,改變正弦信號相位用 wire [13:0]phase_mod_i2; // 相位調整,改變正弦信號相位用 wire [13:0]sine1; //產生的0相位正弦載波信號 wire [13:0]sine2; //產生的π相位正弦載波信號 wire rst; wire clk10m; //產生一個10Mhz時鐘分頻信號,用於正弦信號用 wire clk100k; //產生一個100khz時鐘分頻信號,用做pn碼週期 wire pn; //pn碼做爲信號源 wire difpn; //差分變換後的pn碼 wire difpn_dey; //總體延遲一個週期的差分pn碼,爲了差分解調 reg difpn_dey_reg; reg difpn_dey_reg1 = 0; wire [13:0]sine_mod; //DPSK已調信號波形 wire [13:0]sine_mod_dey; //DPSK已調波形總體延遲一個週期,爲了差分解調 wire [27:0]sine_demod1; //相干(相乘)解調後的DPSK解調信號 wire [44:0]sine_demod2; // 解調信號通過低通濾波器後的信號 wire sine_recover; //最終判決後的恢復信號(其實也沒有判決的步驟) assign rst = !rst1; assign reset_n = !rst; assign clken = 1'b1; assign phi_inc_i1 = 32'd42949673; //sine1相位增益,輸出100khz assign phi_inc_i2 = 32'd42949673; //sine2相位增益,輸出100khz assign phase_mod_i1 = 14'b10_0110_0000_1010; //sine1相位調整,輸出0相位 assign phase_mod_i2 = phase_mod_i1 + 14'b10_0000_0000_0000; // sine2相位調整,輸出Π相位 assign led1 = ~out_valid1; divide #(.WIDTH(3),.N(5)) u1 ( //分頻器模塊,產生一個10mHz(一個週期)時鐘分頻信號,正弦信號生成用 .clk(clk), .rst_n(reset_n), .clkout(clk10m)); divide #(.WIDTH(7),.N(100)) u1_1 ( //分頻器模塊,產生一個100kHz(一個週期)時鐘分頻信號,PN碼生成用 .clk(clk10m), .rst_n(reset_n), .clkout(clk100k)); sine_ip u2 ( //實例化nco ip核模塊,生成100khz,0相位正弦信號 .phi_inc_i (phi_inc_i1), //輸入相位增益信號,由時鐘頻率和輸出頻率計算獲得 .clk (clk10m), .reset_n (reset_n), .clken (clken), //時鐘使能信號 .phase_mod_i(phase_mod_i1), .fsin_o (sine1), //輸出正弦波sine1 .out_valid (out_valid1)); //正弦波輸出有效信號 sine_ip2 u2_1( .phi_inc_i(phi_inc_i2), //實例化nco ip核模塊,生成100khz,Π相位正弦信號 .clk(clk10m), //輸入相位增益信號,由時鐘頻率和輸出頻率計算獲得 .reset_n(reset_n), .clken(clken), //時鐘使能信號 .phase_mod_i(phase_mod_i2), .fsin_o(sine2), //輸出正弦波sine2 .out_valid(out_valid2)); //正弦波輸出有效信號 PnCode u3( //pn碼生成模塊,5階本原多項式 .rst(rst), .clk(clk100k), .pn(pn)); //輸出pn碼 difcode u4( //pn碼 --> 差分pn碼模塊 .clk(clk100k), .rst(rst), .pn(pn), //輸入pn碼 .difpn(difpn)); //輸出差分碼 data_sel u5( //相位選擇法調製模塊 .clk(clk10m), .difpn(difpn), //輸入差分碼 .sine1(sine1), //輸入0相位正弦載波 .sine2(sine2), //輸入Π相位正弦載波 .sine_mod(sine_mod)); //輸出已調信號 always@(negedge clk100k) difpn_dey_reg = difpn; always@(posedge clk100k) difpn_dey_reg1 = difpn_dey_reg; assign difpn_dey = difpn_dey_reg1; data_sel u6( //相位選擇法調製模塊 .clk(clk10m), .difpn(difpn_dey), //輸入延遲一個碼元事後的差分碼 .sine1(sine1), .sine2(sine2), .sine_mod(sine_mod_dey)); //輸出延遲一個碼元事後的已調信號 mult18 u7 ( //實例化相乘器ip核模塊,差分解調部分 .dataa (sine_mod), .datab (sine_mod_dey), .result (sine_demod1)); //輸出差分解調後的解調信號 fir_mod u8( //實例化低通濾波器ip核模塊 .clk(clk10m), .reset_n(reset_n), .sine_demod1(sine_demod1), //輸入解調信號 .sine_demod2(sine_demod2)); //輸出濾波後信號 assign sine_recover = sine_demod2[44];//最高位符號位爲恢復信號(爲了簡單) endmodule
[1] 杜勇. 數字調製解調技術的MATLAB與FPGA實現[M]. Altera/Verilog版. 北京:電子工業出版社,2015.
[2] 樊昌信,曹麗娜. 通訊原理[M]. 第六版. 北京:國防工業出版社,2006.