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

康师傅 Lv3

基于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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
`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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
`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 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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文件然后把下面的复制进去替换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
`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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//~ `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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
`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信号生成及测量分析技术-西电通院随机信号实验
  • 作者: 康师傅
  • 创建于 : 2022-11-05 03:55:20
  • 更新于 : 2022-11-05 04:11:32
  • 链接: https://blog.ksfu.top/posts/5f6b/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论