1. 程式人生 > >基於FPGA的AM調製與解調(Verilog語言)

基於FPGA的AM調製與解調(Verilog語言)

一、概述

     說是概述,但是你還是得必須容我先瞎扯一番的。又是課程的作業,要通過FPGA實現AM訊號的產生與解調。我們最開始手上是有硬體的板卡的,型號是叫Nexys Video。(當然現在被老師收走了,所以下面的程式只能講解到模擬的層次)要求是通過VIO控制載波頻率、調製訊號頻率、調製深度可調,然後通過ILA觀察AM訊號和解調後的訊號。我記得載波訊號的頻率要求是1M~10M,調製訊號的頻率要求是1K~10K,調製深度從0到1、步進0.1。當然他規定了一定的精度。(VIO與ILA只能通過硬體板卡實現,下面的講解中演示不了)。這個程式雖然說不算難吧,但是我確實忙活了一個星期才搞明白,每天都得超過12點睡覺。最讓我崩潰的是:考試驗收的那一天,我竟然忘記把昨天晚上最後改好的程式考到U盤上來,又是各種原因不能回去拿電腦。我的那個心涼的。。。從頭一點點開始寫啊,然後還有各種各樣的波折,害的我都以為這門課是要來年重修的節奏。還好,最後一刻順利完成。好,瞎扯到此為止,下面進入正題。

二、平臺

   軟體: Vivado 2016.4

    硬體:Nexys Video(這個不重要)

三、要求

   為了更好的說明下面一些引數設定的意義,把我們課程的部分要求貼上來
完成AM訊號調製和解調功能,具體要求如下:
(1)載波訊號頻率範圍:1M-10MHz,解析度0.01MHz;
(2)調製訊號為單頻正弦波訊號,頻率範圍:1kHz-10kHz,解析度0.01kHz;
(3)調製深度0-1.0,步進0.1,精度優於5%;
(4)調製訊號和解調訊號位寬為8位,AM訊號16位,其他訊號位寬自定義。

四、原理

    雖然這部分簡單,但卻是最最重要的,把這部分看懂,所有的程式也就明白了。

    1.  AM訊號:(A+ma*cos(w0

t))*cos(wct

    一步步來嘛,首先肯定要產生兩個頻率不同的餘弦波cos(w0t),cos(wct)。立馬想到呼叫系統自帶的DDS IP核來實現嘛,這是最簡單的方法。我在網上還看到一個自己通過導coe檔案來模擬DDS然後來產生餘弦波的,這裡就不說了。

    產生兩個餘弦波後,再來兩個乘法器(現在沒有徹底想明白“*”這個符號和乘法器的區別,這裡就不說了,我還是乖乖的調系統的乘法器吧)、一個加法器(後面程式直接用的“+”號,沒有用加法器ip 核),運算一把不就搞定了嗎。

    2.AM訊號生成中的注意點(這個有點繞

    首先看一下調製深度的問題。調製深度通常為已調波的最大振幅與最小振幅之差對載波最大振幅與最小振幅之和的比。就是生成AM波包絡的

最大值與最小值之差除以最大值與最小值之和。包絡其實就是(A+ma*cos(w0t))。它的最大值是A+ma,最小值是A-ma。最後可以算出調製深度就是ma/A。A為1時,調製深度就是ma,其實只要A的值和後面的餘弦波的最大值是相同的,調製深度就會為ma,為0~1之間。

    但是有一個重要的問題不要忘了,就是在硬體描述語言中表示小數並不像C語言那麼簡單直接(其實所謂的小數只是我們對每個位元組中的0和1的解釋方式不同而已在硬體描述語言中,我們會很自然的會把0和1兩種狀態直接轉換為十進位制,比如8'b0000_0011,我們會很自然的把它看做3,那麼這樣的話,硬體描述語言中是沒有小數的)。我們上面生成的餘弦訊號cos(w0t)並不是在0~1範圍內。假如我們讓DDS的輸出位寬為8位,那麼這個餘弦訊號的幅度大小-128~127。我們就當做是-127~127,那麼這裡先假設A為127。再來再算一下調製深度。這時包絡最大值為127+ma*127,最小值為127-ma*127。最後調製深度還會是ma,這個ma的範圍還是0~1。ma是從0~1之間變的話,還是有不能直接表示小數這個問題。有兩種解決方法:

    1.讓A=1270,也就是包絡最大值為1270+ma*127,最小值1270+ma*127。算出調製深度最後應該為ma/10的。這時便可以設定ma為1~10來改變調製深度了。

    2,.下面的程式是用的是以下這種方法。比如說(127*256)>>8(要知道右移一位相等於除2,右移8位的話等於除以256),就相當於127*1。而(127*128)>>8就相當於127*0.5=63。也就是這樣產生我們的小數ma通過設定一個8位的變數depth_con乘以127,然後將得到的結果右移8位,那麼我們就可以通過depth_con來控制調製深度depth了。他們的關係也就是depth=depth_con/256。當然這些推理是在保持A=127的情況下進行的,也就是AM訊號包絡為127+(depth_con*COS)>>8。(COS是DDS產生的8位訊號)

    前面的127+(depth_con*COS)>>8得到後,再經過乘法器乘以8位的載波就行了,這個乘法器的輸出的就是我們要的AM訊號了。這裡還有一點就是127+(depth_con*COS)>>8的結果的範圍是0~256。這時可以將乘法器的一個輸入改為8無符號數,正好可以滿足0~256的範圍。乘以8位的載波訊號後,得到的AM波正好是我們要求的那樣,輸出16位的AM波。(主要就是因為這才選的這種方法)

    3.AM訊號的解調

    首先來談一下解調方法:

    相干解調,就是在AM波的基礎上再乘以載波一次,然後經過低通濾波,隔直便可以得到我們要的解調訊號。在頻域分析也就是乘以載波後有了會產生一個w0頻率分量和幾個高頻分量,將這幾個高頻分量濾除便可以得到原始的調製訊號。這個原理簡單。

    然而我用的還是下面的這種方法不知道算不算包絡解調(我覺得不算吧),就是將AM波進行全波整流(就是取絕對值)或者半波整流(就是把負半軸的訊號不要),然後低通濾波便可以得到我們要的解調訊號了。這個原理最開始我是百思不得其解,因為最開始不知聽誰說這種方法算包絡解調,所以我一直在想為什麼AM訊號直接低通濾波後提取不了包絡,而整流後低通濾波就可以提取包絡了呢?但是其實再回頭看,原理也挺簡單。跟本不算提取包絡吧。下面是AM訊號進行全波整流後的頻譜圖(MATLAB)。調製訊號頻率為1KHz,載波頻率為100KHz。

放大前


放大後


可以看出全波整流後的AM波在調製訊號頻率處有了新的頻率分量,所以再經過低通濾波就可以得到最開始的調製訊號。

理論推導的話,這幾天也大概搞明白了。首先(1+cosw0t)coswct全波整流後得到(1+cosw0t)|coswct|(肯定要保證(1+cosw0t)大於0的)

其實你只要得到|coswct|的傅立葉級數就可以明白了。|coswct|的傅立葉級數具體我記不清了,但是我記得展開後有一個常數項2/pi,然後加上後面的一大坨(上網上搜一下就知道了)。主要就是有了這個常數項2/pi,它和前面的(1+cosw0t相乘後就會得到w0的頻率分量,也就是我們調製訊號的頻率。然後濾波一下,你懂得。

至於半波整流一樣分析吧。

再來說一下濾波器在vivado裡的實現

直接呼叫系統自帶的fir IP核,點開後第一個介面。選COE檔案,然後匯入。

    這個COE檔案可以通過MATLAB生成(我也只知道這一種方式)。在MATLAB視窗輸入命令:》fdatool


有些東西並不一定按照我這麼選,比如FIR濾波器的種類(Window,Hamming),選其餘的應該沒什麼大問題。主要是要保證截止頻率,我設的是20K,因為要求調製訊號的頻率是1~10K,我稍微設高了一點。取樣頻率的話最少比載波頻率大2倍(取樣定理)我設的100M

還要進行量化後才能匯出COE檔案。如下

選擇Fixed-point後可能要等幾秒鐘。稍微等一會。量化完後就可以匯出了,就是點Targets裡的選項,就不給圖了

fir IP核第二,第三個設定視窗基本上不用改什麼。下面的Input Sampling Frequency最好不要太大。我設成100時,程式執行好久都執行不完,太慢。


fir IP核設定完後,基本就沒什麼說的了。直接給程式吧,對照著前面的原理看,看懂應該不是問題。

五、程式

   程式碼的話,先直接全部貼上來吧,還是比較簡單的,不像我最開始從網上看到的一個,各種各樣的模組疊加在一起。一些注意點講解在後面。

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: Haerbin Engineering University
// Engineer: huangshang
// 
// Create Date: 2018/05/16 14:02:46
// Design Name: 
// Module Name: AM_generate2
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module AM_generate2(
    input sysclk
    );
    wire [15:0] AM_out;
    wire [15:0] carrier_con=2620 ;
    wire [23:0] modulate_con=1679;//用vio輸入時,將下面的vio程式碼啟用,並且要這幾個變數不能賦初值(carrier_con,modulate_con,depth)
    wire [3:0] depth= 9;
//    vio_0 vio_instance_name (
//      .clk(sysclk),                // input wire clk
//      .probe_in0(AM_out),    // input wire [22 : 0] AM signal
//      .probe_out0(depth),  // output wire [3 : 0] shen to control the depth of modulation
//      .probe_out1(carrier_con),  // output wire [15 : 0] carrier_con to control the frequence of carrier signal
//      .probe_out2(modulate_con)  // output wire [15 : 0] modulate_con to control the frequence of modulate signal
//    );
    
   
    wire [7:0] carrier_out;
    dds_compiler_0 carrier_instance_name (          //DDS輸出載波訊號
      .aclk(sysclk),                                  // input wire aclk
      .s_axis_config_tvalid(1),  // input wire s_axis_config_tvalid
      .s_axis_config_tdata(carrier_con),    // input wire [15 : 0] s_axis_config_tdata
      .m_axis_data_tvalid(),      // 這個括號裡的變數只要不要有和其餘的重名的就行,全刪掉只留括號也行。
      .m_axis_data_tdata(carrier_out)        // output wire [7 : 0] m_axis_data_tdata
    );
    
    wire [7:0] modulate_out;
modulating_signalDDS modulate_instance_name (         //dds輸出調製訊號
      .aclk(sysclk),                                  // input wire aclk
      .s_axis_config_tvalid(1),  // input wire s_axis_config_tvalid
      .s_axis_config_tdata(modulate_con),    // input wire [23 : 0] s_axis_config_tdata
      .m_axis_data_tvalid(),      // output wire m_axis_data_tvalid
      .m_axis_data_tdata(modulate_out)        // output wire [7 : 0] m_axis_data_tdata
    );
    
    reg  [7:0] A= 127;          //AM波直流分量 
    reg signed[8:0] depth_con;  
 
    [email protected](posedge sysclk)         //設定調製深度
    begin
       case (depth)
            0:  depth_con <= 0 ;       //調製深度為0,直流分量對應值
            1:  depth_con <= 13;       //調製深度為0.1,直8流分量對應值
            2:  depth_con <= 28;       //調製深度為0.2,直流分量對應值
            3:  depth_con <= 45;       //調製深度為0.3,直流分量對應值
            4:  depth_con <= 64;       //調製深度為0.4,直流分量對應值
            5:  depth_con <= 85;       //調製深度為0.5,直流分量對應值
            6:  depth_con <= 110;       //調製深度為0.6,直流分量對應值
            7:  depth_con <= 138;       //調製深度為0.7,直流分量對應值  
            8:  depth_con <= 171;       //調製深度為0.8,直流分量對應值               
            9:  depth_con <= 209;       //調製深度為0.9.,直流分量對應值
            10:  depth_con <= 255;       //調製深度為1,直流分量對應值       
       endcase
    end
    (* use_dsp48 = "yes" *)    //這個沒太大關係
    
    wire signed[16:0] modulate_mul8ma;
    wire signed[8:0] modulate_mulma;
    mult_genx your_instance_name (        //呼叫乘法IP核,就是進行前面說的得到小數的那一部分操作
      .CLK(sysclk),  // input wire CLK
      .A(modulate_out),      // input wire [7 : 0] A
      .B(depth_con),      // input wire [8 : 0] B
      .P(modulate_mul8ma)      // output wire [16 : 0] P
    );
    assign modulate_mulma = modulate_mul8ma>>8;
    reg [7:0] modulate_withdc;
    [email protected](posedge sysclk)
    begin
        modulate_withdc <=  modulate_mulma + 127;
    end
    

    mult_gen_0 mult_instance_name (         //與載波相乘
      .CLK(sysclk),  // input wire CLK
      .A(modulate_withdc),      // input wire [7 : 0] A
      .B(carrier_out),      // input wire [7 : 0] B
      .P(AM_out)      // output wire [15 : 0] P
    );

reg [15:0] AM_abs;
always @(posedge sysclk ) begin

    if(AM_out[15] == 1)    begin        //全波整流
        AM_abs <= -{AM_out};        //如果符號位是1,對資料取反
    end
    else if(AM_out[15] == 0)    begin
        AM_abs <= AM_out;           //如果符號位是0,資料不變
    end
    else    begin
        AM_abs <= AM_abs;
    end
end

wire [39:0] demolate_signal;
fir_my yfir_instance_name (             //低通濾波
  .aclk(sysclk),                              // input wire aclk
  .s_axis_data_tvalid(1),  // input wire s_axis_data_tvalid
  .s_axis_data_tready(s_axis_dalta_tready),  // output wire s_axis_data_tready
  .s_axis_data_tdata(AM_abs),    // input wire [15 : 0] s_axis_data_tdata
  .m_axis_data_tvalid(),  // output wire m_axis_data_tvalid
  .m_axis_data_tdata(demolate_signal)    // output wire [39 : 0] m_axis_data_tdata
);
    wire [7:0] demolate_final; 
      assign demolate_final[7:0] = demolate_signal[34:27];  //截位
//ila_0 ila_instance_name (
//        .clk(sysclk), // input wire clk
    
    
//        .probe0(AM_out), // input wire [15:0]  probe0  
//        .probe1(demolate_final) // input wire [7:0]  probe1
//    );


    
endmodule

還有模擬模組的頂層檔案,就幾行。

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: Haerbin Engineering University
// Engineer: huangshang
// 
// Create Date: 2018/05/16 15:41:00
// Design Name: 
// Module Name: textbench
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module textbench(

    );
    
    reg sysclk;
    AM_generate2 instance_AM(
        .sysclk(sysclk)
    );
    
    
    initial
    begin
        sysclk <= 0;
    end 
    
    
    always
    #5 sysclk = ~sysclk;
    
endmodule

六、注意事項

   從最容易出現的錯誤開始說吧

1、fir iP核

    在從U盤裡往電腦裡拷程式時一定會出現錯誤的,首先要把fir IP核的COE檔案再導一遍。如果還是報錯的話要把以前的coe檔案刪掉。就是下面紅色的檔案

2.DDS的設定

    前面沒怎麼說DDS的事,比較簡單嘛。就是調系統的ip核,改一下頻率控制字的位寬和輸出位寬,其餘的基本上不用改。計算輸出訊號的頻率:100M/2^24是頻率解析度,再用它乘以頻率控制字就是輸出訊號的頻率了。程式前面的modulate_con,,carrier_con就是這樣算的,分別是10K,1M。

需要注意的是,當DDS的頻率控制字的位寬太小時,輸出的AM訊號可能會失真的,這時只要把位寬改大點就好我最開始調製訊號dds的位寬設定16位,出來的就是失真AM訊號。改完dds位寬後,不要忘了改前面定義的wire modulate_con的位寬。他們是對應的。

3.截位

由於最後要求輸出的是8位的解調訊號,而通過fir IP 核濾波後的輸出訊號遠遠大於8位,所以這裡就有一個截位的問題。截位其實是通過看模擬圖來覺定截哪幾位的。

    可以看到39到35位都是0,所以就從第34位開始往下擷取8位嘛。要注意的是,我們這樣截的話,連符號位也截去了,但符號位一直是0。所以在觀察最後解調出來的訊號的波形時,要設為無符號數來觀察。

    最後放一張完整的圖

最後,這個程式同學也在硬體板子上試過的沒有問題的。把那個VIO,和前面的幾個變數(carrier_con,modulate_con,depth)的初始化去掉。綜合生成bit流,匯入板子。完工~

又想了一下,在網上查了一下包絡解調的定義:

包絡解調又稱包絡檢波,適用於普通調幅訊號的解調,指產生的輸出訊號與已調訊號包絡線成正比的幅度解調。

那這樣說的話,這種方法算是包絡解調吧。(原諒我的知識有限)

另外,本人水平一般,如果各位發現錯誤的地方,希望能熱心指正,不勝感激。