基于FPGA的ASK信号生成及测量分析技术-西电通院随机信号实验

本文最后更新于:2022年11月5日 凌晨

基于FPGA的ASK信号生成及测量分析技术

) 随机信号实验选到了这个,就当学 Verilog 了。

(以下内容围绕西电通院随机信号实验:《基于FPGA的ASK信号生成及测量分析技术》展开。

模块框图

2ASK调制电路组成框图

2ASK调制的FPGA程序框图

模块分解

梳理一下:

  1. 分频模块
  2. 载波产生模块
    • 八进制计数器
    • ROM
  3. m序列产生模块
  4. 键控开关
  5. DAC输出模块(给了)
  6. 拓展输出口(给了)

所以需要编写5个模块。

1. 分频模块设计

2.1.1按原理2.1节设计分频器a(4分频、6分频、10分频等)将系统时钟sys_clk分频,作为载波产生模块的时钟,则载波频率为sys_clk/(分频值a*一个载波周期的存储点数n)(Hz)。(sys_clk=26MHz)

2.1.2按原理2.1节设计分频器b(分频值应设置为上步中a*n的整数倍)将系统时钟分频,作为m序列产生模块的时钟,则基带码元速率为sys_clk/b(bit/s)。

可以知道要设计两个分频模块,其中主频为 26MHz

分频模块端口示意图:

clk端为系统时钟信号输入,从out8、out16、out256可分别得到系统时钟的8分频、16分频和256分频信号。

经历了一晚上的折磨,我终于悟出了:所谓X分频,就是把时钟原来每一下变一次,变成现在是每X下变一次。 (有时候很明显的事情就是转不过来弯

所以就可以顺着这个思路编写 fenpin.v

module fenpin (
    input       wire    clk,
    output      reg     out8,
    output      reg     out16,
    output      reg     out256
);

// 所谓X分频,就是原来时钟是每一下变一次
// 现在是每X下变一次

// lb(256) = 8
reg [7:0] counter_256;
reg [3:0] counter_16;
reg [2:0] counter_8;

initial begin
    counter_256 = 8'b0;
    counter_16 = 4'b0;
    counter_8 = 3'b0;
    out8 = 1'b0;
    out16 = 1'b0;
    out256 = 1'b0;
end
    
// 折中计数到一半就行
// 8分频
always @(posedge clk) begin
    if(counter_8 < 8/2-1) begin
        counter_8 <= counter_8 + 1'b1;
        out8 <= out8;
    end
    else begin
        counter_8 <= 3'b0;
        out8 <= ~out8;
    end
end

// 16分频
always @(posedge clk) begin
    if(counter_16 < 16/2-1) begin
        counter_16 <= counter_16 + 1'b1;
        out16 <= out16;
    end
    else begin
        counter_16 <= 4'b0;
        out16 <= ~out16;
    end
end

// 256分频
always @(posedge clk) begin
    if(counter_256 < 256/2-1) begin
        counter_256 <= counter_256 + 1'b1;
        out256 <= out256;
    end
    else begin
        counter_256 <= 8'b0;
        out256 <= ~out256;
    end
end

endmodule 

testbench

`timescale  1ns/1ns
module  tb_fenpin();

reg     clk;
wire    out8;
wire    out16;
wire    out256;

initial begin
    clk  = 1'b0;
end

always #10 clk = ~clk;

fenpin fenpin_inst
(
    .clk (clk), 
    .out8 (out8),
    .out16 (out16),
    .out256 (out256)
);

endmodule 

仿真波形:

2. 载波产生模块

载波产生模块示意图:

载波产生模块示意图如上图所示,其中clock为载波采样时钟,q[2:0]为计数器输出,q[7:0]输出为载波信号。载波产生模块由一个计数器和一个ROM构成,其中ROM中存储着一个载波周期的样点值,则计数器的进制设置为一个载波周期包含的样点数。本实验中一个载波周期取八个样点,计数器设置为八进制计数器,ROM和计数器均可使用IP核实现。

结合示意图可以知道,载波产生模块又分为两部分:八进制计数器ROM。分开来写。

1) 八进制计数器

q 的值每个时钟加1,从0加到7。

lpm_counter0.v

module lpm_counter0 (
    input   wire            clk,
    output  reg     [2:0]   q
);

initial begin
    q = 3'b0;
end

always @(posedge clk) begin
    if(q == 3'b111) begin
        q <= 3'b0;
    end
    else begin
        q <= q + 1'b1;
    end
end

endmodule 

testbench

`timescale 1ns/1ns
module tb_lpm_counter0();

reg             clk;
wire    [2:0]   q;

initial begin
    clk = 1'b0;
end

always #10 clk = ~clk;

lpm_counter0 lpm_counter0_inst
(
    .clk(clk),
    .q(q)
);

endmodule 

仿真波形

2)ROM

使用 IP核 ,照着野火的教程学了学。

46-第二十六讲-ROM-IP核的调用(一)_哔哩哔哩_bilibili

经过大概分析,可以知道本实验使用 单端口ROM ,数据为 8位宽 ,地址为 3位宽 ,至少包含 8个 数据(采样了8个点),使用单时钟。

输出添加一个寄存器会延后两个周期输出。(原来延后一个,经过寄存器再延后一个。)

写操作是时钟的上升沿,读也是时钟的上升沿。

产生 mif 文件

matlab生成.mif文件 产生正弦信号数据_橘子FPGA的博客-CSDN博客_matlab生成正弦信号

本实验中一个载波周期取八个样点。

本次使用 python 进行生成(就8个点手写也行

根据 .mif 文件的格式一句一句打印出来。

一个普通余弦信号周期为 $2\pi$ ,取样8个点,就是 $cos(2{\pi}\times\frac{x}{8})$ ;

数据位宽为8位,所表示的数据在0~255之间,所以需要将 $cos(2{\pi}\times\frac{x-1}{8})$ 的幅值**-1~+1变化到0~255**。

具体做法是将 $cos(2{\pi}\times\frac{x}{8})\times128+128$ 。就是将原幅值变换至-128~+128,然后加上128,范围变为0~256。

python 代码:

import math

pi = math.pi
filename = "sin_8x8.mif"
with open(filename,"w+",encoding="utf-8") as file_object:
    file_object.write("WIDTH=8;\n")
    file_object.write("DEEPTH=8;\n")
    file_object.write("ADDRESS_RADIX=UNS;\n")
    file_object.write("DATA_RADIX=UNS;\n")
    file_object.write("CONTENT BEGIN\n")
    for i in range(0,8):
        x = (int)(math.cos(2*pi*i/8)*128+128)
        if x == 256:
            x = x-1
        file_object.write(f"{i}:{x}\n")
    file_object.write("END;\n")
    

效果(如果发现quartus报错就生成一个标准的mif文件然后把下面的复制进去替换:

WIDTH=8;
DEEPTH=8;
ADDRESS_RADIX=UNS;
DATA_RADIX=UNS;
CONTENT BEGIN
0:255
1:218
2:128
3:37
4:0
5:37
6:127
7:218
END;
生成IP核

注意深度选择的时候下拉没有8深度的选择,但是可以手动输入。

)不过我在其他文件调用ip核的时候一直仿真失败emm,所以ip核就直接用了,等我找到问题再看看。

编写代码及仿真

添加IP核,进行仿真。

testbench

`timescale 1ns/1ns
module tb_lpm_rom();

reg             clk;
reg     [2:0]   address2;
wire     [7:0]   q;

initial begin
    clk = 1'b1;
    address2 = 3'b000;
end

always #10 clk = ~clk;

always #20 begin
	if(address2 == 3'b111) begin
		address2 <= 3'b000;
	end
	else begin
		address2 <= address2 + 1'b1;
	end
end

cos_8x8	cos_8x8_inst (
	.address ( address2 ),
	.clock ( clk ),
	.q ( q )
	);
endmodule 

仿真波形

3.m序列产生模块

m序列产生原理及其性质_Angelo_pj的博客-CSDN博客_m序列

【verilog杂谈(一)】 2-16位长度的m序列发生器 - 知乎 (zhihu.com)

要求:按原理2.3节设计m序列产生模块,要求产生不同长度的m序列。

代码根据实验要求修改自参考链接,可以产生2~16位的m序列。

mxulie.v

module mxulie#(
        parameter       len = 4  // parameter range from 2 to 16
    )
    (
        input wire      clk,
        output wire     m_sequence
    );

    reg[(len-1):0] Q_r;

    assign m_sequence = Q_r[(len-1)];

    initial begin
        Q_r <= ~(0);
    end

    always @(posedge clk) begin
        Q_r <= Q_r<<1;  // shift reg
        case(len)
            2 : Q_r[0] <= Q_r[1]^Q_r[0];
            3 : Q_r[0] <= Q_r[2]^Q_r[1];
            4 : Q_r[0] <= Q_r[3]^Q_r[2];
            5 : Q_r[0] <= Q_r[4]^Q_r[2];
            6 : Q_r[0] <= Q_r[5]^Q_r[4];
            7 : Q_r[0] <= Q_r[6]^Q_r[3];
            8 : Q_r[0] <= Q_r[7]^Q_r[5]^Q_r[4]^Q_r[3];
            9 : Q_r[0] <= Q_r[8]^Q_r[4];
            10: Q_r[0] <= Q_r[9]^Q_r[6];
            11: Q_r[0] <= Q_r[10]^Q_r[8];
            12: Q_r[0] <= Q_r[11]^Q_r[10]^Q_r[7]^Q_r[5];
            13: Q_r[0] <= Q_r[12]^Q_r[11]^Q_r[9]^Q_r[8];
            14: Q_r[0] <= Q_r[13]^Q_r[12]^Q_r[7]^Q_r[3];
            15: Q_r[0] <= Q_r[14]^Q_r[13];
            16: Q_r[0] <= Q_r[15]^Q_r[14]^Q_r[12]^Q_r[3];
            default: Q_r[0] <= Q_r[0];
        endcase
    end

endmodule

testbench

//~ `New testbench
`timescale  1ns / 1ns 

module tb_mxulie;   

// M_series Parameters
parameter M_len = 5;

// M_series Inputs
reg   clk                                  = 0 ;

// M_series Outputs
wire  m_sequence                                    ;

always #10 clk=~clk;

mxulie #(
    .len ( M_len ))
mxulie_inst (
    .clk            (clk),
    .m_sequence     (m_sequence)
);

endmodule

仿真波形

4.键控开关

键控开关示意图图上图所示,其中data[7:0]端输入载波信号,gate端输入基带码元,当gate信号为1时,载波信号通过,如果gate信号为0时,载波信号不能通过。模块的输出端q[7:0]输出2ASK已调信号。

lpm_gate.v

module lpm_gate(
    input   wire                gate,            
    input   wire    [7:0]       data,
    output  reg     [7:0]       q
);

initial begin
    q <= 8'b0;
end

always@(*)  begin
    if(gate == 1'b1) begin
        q <= data;
    end
    else begin
        q <= 8'b01111111; // +127将0变换过去
    end
end

endmodule 

整合以及整体仿真

将以上各个模块添加进实验给定的模板,并生成原理图,然后连线。

要整体仿真,所以要先把原理图转换为 Verilog (File -> Create / Update -> Create HDL Design File from Current File…),之后从工程文件移除原理图,设置生成的 verilog 文件为顶层文件进行编译,然后仿真。

通过对比编译出来的 RTL 视图,符合给定框图。

整体仿真,只要注意 clk , m_squence , two_ask[7...0] 这几个信号就行。 testbench 如下:

`timescale 1ns/1ns
module tb_sim;

reg             clk;
wire            m_squence;
wire    [7:0]   two_ask;

initial begin
    clk = 1'b0;
end

always #10 clk = ~clk;

twoask twoask_inst(
    .clk(clk),
    
    .m_squence(m_squence),
    .two_ask(two_ask)
);

endmodule 

仿真波形:

管脚分配

管脚分配啥的给的模板文件里已经分配好了,直接用就行。

总结

毕竟是主要是记录 verilog 的一个学习过程,剩下的就不分析了,属于是实验报告里的活。这算是第一次用 verilog 干这种比较综合的活?接触到了一些新玩意,IP核,mif文件之类的。也锻炼了从s一样实验指导手册里提炼信息的能力?

(然后细节和要补充的等后面想起来再说

(我超突然想起来线下验收完忘了拍时域波形


基于FPGA的ASK信号生成及测量分析技术-西电通院随机信号实验
https://blog.ksfu.top/posts/5f6b/
作者
康师傅
发布于
2022年11月5日
许可协议