0%

Verilog之边码边学

本文主要利用Vivado工具,在码代码并仿真的过程中学习Verilog的知识点以及设计思路

分频计数器设计

  • 将输入时钟100MHZ分频形成1MHZ

1.关键点

  • 最大计数值CNT_MAX为$\frac{输入时钟频率}{分频得到的频率}$,计数时从0计数到CNT_MAX-1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //产生0~100的时钟计数器
    always @(posedge clk) begin
    if(!rst_n)
    cnt_clk<=8'd0;
    else if(cnt_clk<(`CNT_MAX-1))//从0计到99(cnt_clk为99时,其会消耗一个时钟周期回到0),故总共消耗100个时钟周期
    cnt_clk<=cnt_clk+1'b1;
    else
    cnt_clk<=8'd0;
    end
    • 上面代码看上去是不是像从0计数到98,但是当cnt_clk等于99时,也会进入这段代码,只是之后cnt_clk的值变为了0,故实际上这段代码是从0计数到99
  • 产生分频时钟,计数为0-49时为高电平,计数为50-99时为低电平

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //产生1MHZ的时钟
    always @(posedge clk) begin
    if(!rst_n)
    clk_1mhz<=1'b0;
    else if(cnt_clk<`CNT_MAX_div2)//从0计到49,总共花费50个时钟周期
    clk_1mhz<=1'b1;
    else
    clk_1mhz<=1'b0;
    end
    • 上述分频时钟的产生实际上会比计数器的产生延时一个系统时钟周期,因为计数器cnt_clk在时钟上升沿变化时,不会被立马捕捉到,只有在再下一个时钟周期时,才能捕捉到变化后的值

2.源代码

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
`define CNT_MAX 100
`define CNT_MAX_div2 `CNT_MAX/2

module vlg_design(
input clk,
input rst_n,
output reg clk_1mhz
);

reg [7:0] cnt_clk;

//产生0~100的时钟计数器
always @(posedge clk) begin
if(!rst_n)
cnt_clk<=8'd0;
else if(cnt_clk<(`CNT_MAX-1))//从0计到99(cnt_clk为99时,其会消耗一个时钟周期回到0),故总共消耗100个时钟周期
cnt_clk<=cnt_clk+1'b1;
else
cnt_clk<=8'd0;
end

//产生1MHZ的时钟
always @(posedge clk) begin
if(!rst_n)
clk_1mhz<=1'b0;
else if(cnt_clk<`CNT_MAX_div2)//从0计到49,总共花费50个时钟周期
clk_1mhz<=1'b1;
else
clk_1mhz<=1'b0;
end

endmodule

3.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
`timescale 1ns / 1ps

`define CLK_PERIOD 10

module vlg_design_tb;
reg clk;
reg rst_n;
wire clk_1mhz;

vlg_design vlg(
.clk(clk),
.rst_n(rst_n),
.clk_1mhz(clk_1mhz)
);

//时钟信号的产生
initial clk=0;
always #(`CLK_PERIOD/2) clk=~clk;

initial begin
rst_n=0;
#1000 rst_n=1;
#10_000 $stop;
end

endmodule

4.仿真结果

image-20230105133849414

image-20230105133924479

  • 由仿真结果可以很明显看到cnt_clk是从0-99计数,且输出的分频时钟延时了一个周期,看上去是从1-50为高电平

使能时钟设计

  • 输入时钟100MHz,产生一个5MHz的时钟使能信号,并使能此时钟使能信号进行0~15的周期计数

1.关键点

  • 时钟使能信号只是持续了一个时钟周期,故用divcnt==(DIVCNT_MAX-1)来判断是否拉高

  • 时序逻辑中,数据变化时,其他模块获取不到该数据变化的值,只有在下一个时钟周期时才能获取变化后的值,也就是相应数据的变化会在对应数据持续一个时钟周期之后再改变

2.源代码

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
module clk_en(
clk,
rst_n,
cnt
);
////////////////////////////////////////
//输入输出端口的定义
input wire clk;
input wire rst_n;
output reg [3:0] cnt;//输出计数器

////////////////////////////////////////
//中间变量的定义
reg [4:0] divcnt;//分频计数器
localparam DIVCNT_MAX = 5'd20;//完成分频需要计数的最大值
reg clk_en;

///////////////////////////////////////
//计数器计数模块
always @(posedge clk) begin
if(!rst_n)
divcnt<=5'b0;
else if(divcnt<(DIVCNT_MAX-1))
divcnt<=divcnt+1;
else
divcnt<=5'b0;
end

//////////////////////////////////////
//时钟使能信号产生模块
always @(posedge clk) begin
if(!rst_n)
clk_en<=1'b0;
else if(divcnt==(DIVCNT_MAX-1))
clk_en<=1'b1;
else
clk_en<=1'b0;
end

//////////////////////////////////////
//时钟使能信号使能计数模块
always @(posedge clk) begin
if(!rst_n)
cnt<=4'b0;
else if(clk_en==1'b1)
cnt<=cnt+1'b1;
else
cnt<=cnt;
end
endmodule

3.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
`timescale 1ns / 1ps

`define CLK_PERIOD 10

module clk_en_tb;
reg clk;
reg rst_n;
wire [3:0] cnt;

clk_en clk_en_U1(
.clk(clk),
.rst_n(rst_n),
.cnt(cnt)
);

//时钟信号的产生
initial clk=0;
always #(`CLK_PERIOD/2) clk=~clk;

initial begin
rst_n=0;
#1000 rst_n=1;
#33_000 $stop;
end

endmodule

4.仿真结果

image-20230106162208489

image-20230106162114767


基于Xlinx BUFGCE原语的门控时钟设计

  • FPGA系统时钟100MHz,系统每秒进行一次数据采集与处理,每次维持10ms,其余时间空闲,在空闲时间内希望关闭100MHz的工作时钟
image-20230108234158354

1.关键点

  • BUFGCE原语的例化

    image-20230108234846962 image-20230108235053079 image-20230108235240084
  • 利用define、ifdef、endif语句方便仿真测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    `define SIMULATION//在真实情况下运行时,将此行注释掉就行
    `ifdef SIMULATION
    //仿真情况下用较小的计数值代替,不然仿真时间过长
    localparam TIMER_CNT_1s = 30'd1_000-1'b1;//1s计数的最大值
    localparam TIMER_CNT_10ms=30'd1_0-1'b1;//10ms计数的最大值
    `else
    localparam TIMER_CNT_1S = 30'd100_000_000-1'b1; //1s计数的最大值
    localparam TIMER_CNT_10MS = 30'd1_000_000-1'b1; //10ms计数的最大值
    `endif

2.源代码

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
module BUFGCE_design(
clk,
rst_n,
outclk
);
input wire clk;
input wire rst_n;
output wire outclk;

`define SIMULATION//在真实情况下运行时,将此行注释掉就行
`ifdef SIMULATION
//仿真情况下用较小的计数值代替,不然仿真时间过长
localparam TIMER_CNT_1s = 30'd1_000-1'b1;//1s计数的最大值
localparam TIMER_CNT_10ms=30'd1_0-1'b1;//10ms计数的最大值
`else
localparam TIMER_CNT_1S = 30'd100_000_000-1'b1; //1s计数的最大值
localparam TIMER_CNT_10MS = 30'd1_000_000-1'b1; //10ms计数的最大值
`endif

////////////////////////////////////////////
//中间变量
reg [29:0] cnt;
reg en_10ms;

////////////////////////////////////////////
//1s周期计数模块
always @(posedge clk) begin
if(!rst_n)
cnt<=30'b0;
else if(cnt<TIMER_CNT_1s)
cnt<=cnt+1'b1;
else
cnt<=30'b0;
end

/////////////////////////////////////////////
//10ms使能信号产生
always @(posedge clk) begin
if(!rst_n)
en_10ms<=1'b0;
else if(cnt<=TIMER_CNT_10ms)
en_10ms<=1'b1;
else
en_10ms<=1'b0;
end

/////////////////////////////////////////////
//例化BUFGCE原语
BUFGCE BUFGCE_inst (
.O(outclk), // 1-bit output: Clock output
.CE(en_10ms), // 1-bit input: Clock enable input for I0
.I(clk) // 1-bit input: Primary clock
);
endmodule

3.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
`timescale 1ns / 1ps

`define CLK_PERIOD 10
module BUFGCE_design_tb;

reg clk;
reg rst_n;
wire outclk;

BUFGCE_design BUFGCE_design_1(
.clk(clk),
.rst_n(rst_n),
.outclk(outclk)
);

//时钟信号的产生
initial clk=0;
always #(`CLK_PERIOD/2) clk=~clk;

initial begin
rst_n=0;
#1000 rst_n=1;
#3_000_000 $stop;
end

endmodule

4.仿真结果

image-20230108234639688


同步复位与异步复位

1.关键点

  • 经过同步处理的异步复位信号与同步信号受到时钟控制会同时复位,异步信号会不受时钟控制立马复位
  • 同步信号与异步信号同时退出复位,同步处理的异步信号会置后一个时钟周期后退出复位

2.源代码

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
`timescale 1ns / 1ps

module rst_signal_design(
i_clk,
i_rst_n,
i_data,
o_sync_data,
o_asyn_data,
o_asyn_data2//经过同步处理的异步复位信号
);

input wire i_clk;
input wire i_rst_n;
input wire [3:0] i_data;
output reg [3:0] o_sync_data;
output reg [3:0] o_asyn_data;
output reg [3:0] o_asyn_data2;

//同步复位
always @(posedge i_clk) begin
if(!i_rst_n)
o_sync_data<=4'd0;
else
o_sync_data<=i_data;
end

//异步复位
always @(posedge i_clk or negedge i_rst_n) begin
if(!i_rst_n)
o_asyn_data=4'd0;
else
o_asyn_data<=i_data;
end

//对复位信号做同步处理,产生新的异步复位信号
reg r_rst_n;
always @(posedge i_clk)
r_rst_n<=i_rst_n;
always @(posedge i_clk or negedge r_rst_n) begin
if(!r_rst_n)
o_asyn_data2<=4'd0;//此信号将和同步复位同时做复位操作
else
o_asyn_data2<=i_data;//相比同步复位退出复位会延时一个时钟周期
end

endmodule

3.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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
`timescale 1ns / 1ps

`define CLK_PERIOD 10
module rst_signal_design_tb;
reg i_clk;
reg i_rst_n;
reg [3:0] i_data;
wire [3:0] o_sync_data;
wire [3:0] o_asyn_data;
wire [3:0] o_asyn_data2;

rst_signal_design rst_signal_design_U1(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.i_data(i_data),
.o_sync_data(o_sync_data),
.o_asyn_data(o_asyn_data),
.o_asyn_data2(o_asyn_data2)
);

//时钟信号的产生
initial i_clk=0;
always #(`CLK_PERIOD/2) i_clk=~i_clk;

//复位信号的产生
initial begin
i_rst_n=0;
#1000 i_rst_n=1;
repeat(10) begin @(posedge i_clk); end
#4 i_rst_n=0;
repeat(10) begin @(posedge i_clk); end
i_rst_n=1;
end

//输入激励的产生
initial begin
i_data<=4'b1111;
@(posedge i_rst_n);//等待复位完成
repeat(30_000) begin
@(posedge i_clk);
end
$stop;
end

endmodule

4.仿真结果

image-20230109104555400


脉冲边沿检测

1.关键点

  • 使用两级寄存器对输入脉冲进行缓存,低位寄存器保存的是最新输入数据,而高位寄存器保存的是低位寄存器的数据,故高位寄存器比低位寄存器慢一个时钟周期
image-20230109110344324
  • 关键代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    reg [1:0] r_pulse;//两级寄存器

    always @(posedge i_clk) begin
    if(!i_rst_n)
    r_pulse<=2'b00;
    else
    r_pulse<={r_pulse[0],i_pulse};
    //上述代码等效为:
    //r_pulse[0]<=i_pulse;
    //r_pulse[1]<=r_pulse[0];
    end

2.源代码

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
`timescale 1ns / 1ps

module edge_detection_design(
i_clk,
i_rst_n,
i_pulse,
o_rise_flag,
o_down_flag
);

input wire i_clk;
input wire i_rst_n;
input wire i_pulse;
output wire o_rise_flag;
output wire o_down_flag;

reg [1:0] r_pulse;//两级寄存器

always @(posedge i_clk) begin
if(!i_rst_n)
r_pulse<=2'b00;
else
r_pulse<={r_pulse[0],i_pulse};
//上述代码等效为:
//r_pulse[0]<=i_pulse;
//r_pulse[1]<=r_pulse[0];
end

//上升沿的判断
assign o_rise_flag=r_pulse[0]&~r_pulse[1];
//下降沿的判断
assign o_down_flag=~r_pulse[0]&r_pulse[1];

endmodule

3.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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
`timescale 1ns / 1ps

`define CLK_PERIOD 10
module edge_detection_design_tb;

reg i_clk;
reg i_rst_n;
reg i_pulse;
wire o_rise_flag;
wire o_down_flag;

edge_detection_design edge_detection_design_U1(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.i_pulse(i_pulse),
.o_rise_flag(o_rise_flag),
.o_down_flag(o_down_flag)
);

//时钟信号的产生
initial i_clk=0;
always #(`CLK_PERIOD/2) i_clk=~i_clk;

//复位信号的产生
initial begin
i_rst_n=0;
#1000 i_rst_n=1;
end

//输入激励的产生
initial begin
i_pulse<=1'b0;
@(posedge i_rst_n);//等待复位完成

repeat(10) begin
@(posedge i_clk);
end
#4 i_pulse<=1'b1;

repeat(10) begin
@(posedge i_clk);
end
#4 i_pulse<=1'b0;

repeat(10) begin
@(posedge i_clk);
end

$stop;
end
endmodule

4.仿真结果

image-20230109112028556


脉冲计数器

  • 在使能信号高电平进行计数,在低电平对计数清零

1.源代码

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
`timescale 1ns / 1ps

module pulse_counter_design(
i_clk,
i_rst_n,
i_pulse,
i_en,
o_pulse_cnt
);

input wire i_clk;
input wire i_rst_n;
input wire i_pulse;
input wire i_en;
output reg [15:0] o_pulse_cnt;

reg [1:0] r_pulse;
always @(posedge i_clk) begin
if(!i_rst_n)
r_pulse<=2'b00;
else
r_pulse={r_pulse[0],i_pulse};
end

//上升沿检测
wire w_rise_edge;
assign w_rise_edge=r_pulse[0]&~r_pulse[1];

always @(posedge i_clk) begin
if(i_en) begin
if(w_rise_edge)
o_pulse_cnt<=o_pulse_cnt+1'b1;
else
o_pulse_cnt<=o_pulse_cnt;
end
else
o_pulse_cnt<=16'd0;
end

endmodule

2.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
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
64
65
66
67
68
`timescale 1ns / 1ps

`define CLK_PERIOD 10
module pulse_counter_tb;
reg i_clk;
reg i_rst_n;
reg i_pulse;
reg i_en;
wire [15:0] o_pulse_cnt;

pulse_counter_design pulse_counter_design_U1(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.i_pulse(i_pulse),
.i_en(i_en),
.o_pulse_cnt(o_pulse_cnt)
);

//时钟信号的产生
initial i_clk=0;
always #(`CLK_PERIOD/2) i_clk=~i_clk;

//复位信号的产生
initial begin
i_rst_n=0;
#1000 i_rst_n=1;
end

integer i;
//输入激励的产生
initial begin
i_pulse<=1'b0;
i_en<=1'b0;
@(posedge i_rst_n);//等待复位完成

repeat(10) begin
@(posedge i_clk);
end
#4 i_en<=1'b1;

//i_pulse激励的产生
for(i=0;i<50;i=i+1) begin
#500 i_pulse<=1'b1;
#300 i_pulse<=1'b0;
end
i_en<=1'b0;
#10_000;

i_en<=1'b1;
for(i=0;i<69;i=i+1) begin
#500 i_pulse<=1'b1;
#300 i_pulse<=1'b0;
end
i_en<=1'b0;
#10_000;

i_en<=1'b0;
for(i=0;i<15;i=i+1) begin
#500 i_pulse<=1'b1;
#300 i_pulse<=1'b0;
end
i_en<=1'b0;
#10_000;

$stop;
end

endmodule

3.仿真结果

image-20230109113345645


generate语法的使用

  • 设计一个脉冲计数器,功能如下:

  • 输入脉冲:16路脉冲信号,分别对每路进行脉冲检测并计数

  • 使能信号:高电平进行计数,低电平清零计数器

1.关键点

  • generate语法有generate for、generate if和generate case三种,可以在generate中使用的语法语句包括module、UDP(用户自定义原语)、门级原语、连续赋值语句assign、always语句和initial语句等
  • 定义genvar作为generate我的循环变量
  • 随机数的产生:{$random}%65536;//产生0~65535的随机数,若不加括号,即为$random%65536,则产生-65535-+65535之间的随机数

2.源代码

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
module generate_for_design(
i_clk,
i_rst_n,
i_pulse,
i_en,
o_pulse_cnt0,
o_pulse_cnt1,
o_pulse_cnt2,
o_pulse_cnt3,
o_pulse_cnt4,
o_pulse_cnt5,
o_pulse_cnt6,
o_pulse_cnt7,
o_pulse_cnt8,
o_pulse_cnt9,
o_pulse_cnta,
o_pulse_cntb,
o_pulse_cntc,
o_pulse_cntd,
o_pulse_cnte,
o_pulse_cntf
);

input wire i_clk;
input wire i_rst_n;
input wire [15:0] i_pulse;
input wire i_en;
output [15:0] o_pulse_cnt0;
output [15:0] o_pulse_cnt1;
output [15:0] o_pulse_cnt2;
output [15:0] o_pulse_cnt3;
output [15:0] o_pulse_cnt4;
output [15:0] o_pulse_cnt5;
output [15:0] o_pulse_cnt6;
output [15:0] o_pulse_cnt7;
output [15:0] o_pulse_cnt8;
output [15:0] o_pulse_cnt9;
output [15:0] o_pulse_cnta;
output [15:0] o_pulse_cntb;
output [15:0] o_pulse_cntc;
output [15:0] o_pulse_cntd;
output [15:0] o_pulse_cnte;
output [15:0] o_pulse_cntf;

//二维数组:16个16位宽的计数器
wire [15:0] r_pulse_cnt[15:0];

//genvar for模块
genvar i;
generate
for(i=0;i<16;i=i+1) begin
pulse_counter_design pulse_counter_design_U(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.i_pulse(i_pulse[i]),
.i_en(i_en),
.o_pulse_cnt(r_pulse_cnt[i])
);
end
endgenerate

assign o_pulse_cnt0=r_pulse_cnt[0];
assign o_pulse_cnt1=r_pulse_cnt[1];
assign o_pulse_cnt2=r_pulse_cnt[2];
assign o_pulse_cnt3=r_pulse_cnt[3];
assign o_pulse_cnt4=r_pulse_cnt[4];
assign o_pulse_cnt5=r_pulse_cnt[5];
assign o_pulse_cnt6=r_pulse_cnt[6];
assign o_pulse_cnt7=r_pulse_cnt[7];
assign o_pulse_cnt8=r_pulse_cnt[8];
assign o_pulse_cnt9=r_pulse_cnt[9];
assign o_pulse_cnta=r_pulse_cnt[10];
assign o_pulse_cntb=r_pulse_cnt[11];
assign o_pulse_cntc=r_pulse_cnt[12];
assign o_pulse_cntd=r_pulse_cnt[13];
assign o_pulse_cnte=r_pulse_cnt[14];
assign o_pulse_cntf=r_pulse_cnt[15];

endmodule

3.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
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
`timescale 1ns / 1ps

`define CLK_PERIOD 10
module generate_for_tb;
reg i_clk;
reg i_rst_n;
reg [15:0] i_pulse;
reg i_en;
wire [15:0] o_pulse_cnt0;
wire [15:0] o_pulse_cnt1;
wire [15:0] o_pulse_cnt2;
wire [15:0] o_pulse_cnt3;
wire [15:0] o_pulse_cnt4;
wire [15:0] o_pulse_cnt5;
wire [15:0] o_pulse_cnt6;
wire [15:0] o_pulse_cnt7;
wire [15:0] o_pulse_cnt8;
wire [15:0] o_pulse_cnt9;
wire [15:0] o_pulse_cnta;
wire [15:0] o_pulse_cntb;
wire [15:0] o_pulse_cntc;
wire [15:0] o_pulse_cntd;
wire [15:0] o_pulse_cnte;
wire [15:0] o_pulse_cntf;

generate_for_design generate_for_design(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.i_pulse(i_pulse),
.i_en(i_en),
.o_pulse_cnt0(o_pulse_cnt0),
.o_pulse_cnt1(o_pulse_cnt1),
.o_pulse_cnt2(o_pulse_cnt2),
.o_pulse_cnt3(o_pulse_cnt3),
.o_pulse_cnt4(o_pulse_cnt4),
.o_pulse_cnt5(o_pulse_cnt5),
.o_pulse_cnt6(o_pulse_cnt6),
.o_pulse_cnt7(o_pulse_cnt7),
.o_pulse_cnt8(o_pulse_cnt8),
.o_pulse_cnt9(o_pulse_cnt9),
.o_pulse_cnta(o_pulse_cnta),
.o_pulse_cntb(o_pulse_cntb),
.o_pulse_cntc(o_pulse_cntc),
.o_pulse_cntd(o_pulse_cntd),
.o_pulse_cnte(o_pulse_cnte),
.o_pulse_cntf(o_pulse_cntf)
);

//时钟信号的产生
initial i_clk=0;
always #(`CLK_PERIOD/2) i_clk=~i_clk;

//复位信号的产生
initial begin
i_rst_n=0;
#1000 i_rst_n=1;
end

integer i;
//输入激励的产生
initial begin
i_pulse<=16'b0;
i_en<=1'b0;
@(posedge i_rst_n);//等待复位完成

repeat(10) begin
@(posedge i_clk);
end
#4 i_en<=1'b1;

//i_pulse激励的产生
for(i=0;i<50;i=i+1) begin
#500 i_pulse<={$random}%65536;//产生0~65535的随机数
#300 i_pulse<=16'b0;
end
i_en<=1'b0;
#10_000;

i_en<=1'b1;
for(i=0;i<69;i=i+1) begin
#500 i_pulse<={$random}%65536;
#300 i_pulse<=16'b0;
end
i_en<=1'b0;
#10_000;

i_en<=1'b0;
for(i=0;i<15;i=i+1) begin
#500 i_pulse<={$random}%65536;
#300 i_pulse<=16'b0;
end
i_en<=1'b0;
#10_000;

$stop;
end

endmodule

4.仿真结果

image-20230109122519817


频率计数器

  • 在使能信号控制下,计算输入脉冲的每两个上升沿之间的时钟周期数并输出,即输出脉冲频率的计数值

1.关键点

  • 遇到上升沿检测标志就读取脉冲数

  • 但是要滤除第一个上升沿检测标志,因为这时候刚刚使能,读取的第一个上升沿并不是两个上升沿之间的读数,滤除方式如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //////////////////////////////////////////
    //输出信号产生:需要滤除第一个w_rise_edge脉冲信号
    reg r_flag;

    always @(posedge i_clk) begin
    if(!i_rst_n)
    r_flag <= 'b0;
    else if(!i_en)
    r_flag <= 'b0;
    else if(w_rise_edge)
    r_flag <= 'b1;
    else
    r_flag<=r_flag;
    end

    assign o_vld = w_rise_edge & r_flag;
  • 因为计数值是从0开始计数,故输出计数值时需要加1

2.源代码

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
module frequency_counter_design(
i_clk,
i_rst_n,
i_pulse,
i_en,
o_vld,
o_pulse_cnt
);

input wire i_clk;
input wire i_rst_n;
input wire i_pulse;
input wire i_en;
output wire o_vld;
output wire [15:0] o_pulse_cnt;

reg[1:0] r_pulse;
wire w_rise_edge;

//////////////////////////////////////////
//脉冲边沿检测逻辑
always @(posedge i_clk)
if(!i_rst_n) r_pulse <= 2'b00;
else r_pulse <= {r_pulse[0],i_pulse};

assign w_rise_edge = r_pulse[0] & ~r_pulse[1];

//////////////////////////////////////////
//输出信号产生:需要滤除第一个w_rise_edge脉冲信号
reg r_flag;

always @(posedge i_clk) begin
if(!i_rst_n)
r_flag <= 'b0;
else if(!i_en)
r_flag <= 'b0;
else if(w_rise_edge)
r_flag <= 'b1;
else
r_flag<=r_flag;
end

assign o_vld = w_rise_edge & r_flag;

//////////////////////////////////////////
//脉冲计数逻辑
reg[15:0] r_pulse_cnt;

always @(posedge i_clk) begin
if(!i_en)
r_pulse_cnt <= 'b0;
else if(w_rise_edge)
r_pulse_cnt <= 'b0;
else
r_pulse_cnt <= r_pulse_cnt+1'b1;
end

assign o_pulse_cnt = r_pulse_cnt+1'b1;

endmodule

3.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
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
`timescale 1ns / 1ps

`define CLK_PERIORD 10 //时钟周期设置为10ns(100MHz)
module frequency_counter_tb;
////////////////////////////////////////////////////////////
//接口申明
reg clk;
reg rst_n;
reg i_pulse;
reg i_en;
wire o_vld;
wire[15:0] o_pulse_cnt;

////////////////////////////////////////////////////////////
//对被测试的设计进行例化
frequency_counter_design frequency_counter_design_U1(
.i_clk(clk),
.i_rst_n(rst_n),
.i_pulse(i_pulse),
.i_en(i_en),
.o_vld(o_vld),
.o_pulse_cnt(o_pulse_cnt)
);

////////////////////////////////////////////////////////////
//复位和时钟产生
//时钟和复位初始化、复位产生
initial begin
clk <= 0;
rst_n <= 0;
#1000;
rst_n <= 1;
end

//时钟产生
always #(`CLK_PERIORD/2) clk = ~clk;

////////////////////////////////////////////////////////////
//测试激励产生
integer i;
initial begin
i_pulse <= 1'b0;
i_en <= 1'b0;
@(posedge rst_n); //等待复位完成

@(posedge clk);
repeat(10) begin
@(posedge clk);
end
#4;

i_en <= 1'b1;
for(i=0; i<50; i=i+1) begin
#1000;
i_pulse <= 1'b1;
#1000;
i_pulse <= 1'b0;
end
i_en <= 1'b0;
#10_000;

i_en <= 1'b1;
for(i=0; i<69; i=i+1) begin
#5000;
i_pulse <= 1'b1;
#5000;
i_pulse <= 1'b0;
end
i_en <= 1'b0;
#10_000;

i_en <= 1'b0;
for(i=0; i<15; i=i+1) begin
#500;
i_pulse <= 1'b1;
#300;
i_pulse <= 1'b0;
end
i_en <= 1'b0;
#10_000;
$stop;
end

endmodule

4.仿真结果

image-20230109194755161

image-20230109194540797

image-20230109194734655


4位格雷码计数器

1.关键点

  • 系统显示任务$display$write
    • $display在一次输出后自动换行,而$write则不会
    • 默认情况下,输出显示的数值所占字符个数由输出信号的数值类型和位宽决定。
    • 用十进制显示时,高位的0会默认以空格填充,而用其他进制显示时,高位的0会显示出来。
    • 在“%”和格式字符之间可以添加数字0,可以隐藏前置的0或空格,使得第一个非0数字顶格显示
  • 监视任务$monitor
    • 当监视的信号发射变化时,会打印当前的值,通常与时间戳$time配合使用
    • $monitoroff关闭监视
    • $monitoron打开监视
  • 在always语句块中使用case语句描述组合逻辑,再使用assign将r_gray赋值给o_gray

2.源代码

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
64
65
66
67
68
module gray_counter_design(
i_clk,//50MHz的输入时钟
i_rst_n,
o_gray
);

input wire i_clk;
input wire i_rst_n;
output wire [3:0] o_gray;

localparam TIMER_1S_MAX_CNT = 32'd1_000_000_000/20;
//////////////////////////////////////
//1s计数模块
reg [31:0] cnt_1s;
always @(posedge i_clk) begin
if(!i_rst_n)
cnt_1s<=32'b0;
else if(cnt_1s<(TIMER_1S_MAX_CNT-1))
cnt_1s<=cnt_1s+32'b1;
else
cnt_1s<=32'b0;
end

////////////////////////////////////
//计数器递增模块
reg [3:0] counter;
always @(posedge i_clk) begin
if(!i_rst_n)
counter<=4'b0;
else if(cnt_1s==(TIMER_1S_MAX_CNT-1))
counter<=counter+4'b1;
else
counter<=counter;
end

/////////////////////////////////////
//将counter变成格雷码的形式
reg [3:0] r_gray;

always @(posedge i_clk) begin
if(!i_rst_n)
r_gray<=4'b0;
else begin
case(counter)
4'b0000:r_gray<=4'b0000;
4'b0001:r_gray<=4'b0001;
4'b0010:r_gray<=4'b0011;
4'b0011:r_gray<=4'b0010;
4'b0100:r_gray<=4'b0110;
4'b0101:r_gray<=4'b0111;
4'b0110:r_gray<=4'b0101;
4'b0111:r_gray<=4'b0100;
4'b1000:r_gray<=4'b1100;
4'b1001:r_gray<=4'b1101;
4'b1010:r_gray<=4'b1111;
4'b1011:r_gray<=4'b1110;
4'b1100:r_gray<=4'b1010;
4'b1101:r_gray<=4'b1011;
4'b1110:r_gray<=4'b1001;
4'b1111:r_gray<=4'b1000;
default:;
endcase
end
end

assign o_gray=r_gray;

endmodule

3.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
31
32
33
34
35
`timescale 1ns / 1ps

`define CLK_PERIOD 20
module gray_counter_tb;
reg i_clk;
reg i_rst_n;
wire [3:0] o_gray;

gray_counter_design gray_counter_design_U1(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.o_gray(o_gray)
);

//时钟信号的产生
initial i_clk=0;
always #(`CLK_PERIOD/2) i_clk=~i_clk;

//复位信号的产生
initial begin
i_rst_n=0;
#1000 i_rst_n=1;
end

//输入激励的产生
integer i;
initial begin
@(posedge i_rst_n);//等待复位完成

$monitor("o_gray is %b at %0dns",o_gray,$time);

$stop;
end

endmodule

4.仿真结果

image-20230203164007128


基于查找表的8位格雷码转换

1.关键点

  • 查找表

    • 查找表简单说就是一个预先存储好结果的数据表
    • 通过访问这张预先存储好结果的数据表,可以快速的获取不同输入的输出结果
  • ROM初始化COE文件制作

    image-20230203165642299

    image-20230203165922892 image-20230203170228814 image-20230203170655478 image-20230203170929138 image-20230203171454609 image-20230203171705965 image-20230203172000521
  • 打拍操作:其类似脉冲边沿检测

    image-20230203174713842

2.源代码

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
module gray8b_based_LUT_design(
i_clk,
i_rst_n,
i_en,
i_data,
o_vld,
o_gray
);

input wire i_clk;
input wire i_en;
input wire [7:0] i_data;
input wire i_rst_n;
output wire o_vld;
output [7:0] o_gray;

//打两拍操作
reg [1:0] r_vld;
always @(posedge i_clk) begin
if(!i_rst_n)
r_vld<=2'b00;
else
r_vld<={r_vld[0],i_en};
end

assign o_vld=r_vld[1];

blk_mem_gen_0 blk_mem_gen_0_U (
.clka(i_clk), // input wire clka
.addra(i_data), // input wire [7 : 0] addra
.douta(o_gray) // output wire [7 : 0] douta
);

endmodule

3.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
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
`timescale 1ns / 1ps

`define CLK_PERIOD 20
module grab8b_based_LUT_tb;
reg i_clk;
reg i_en;
reg [7:0] i_data;
reg i_rst_n;
wire o_vld;
wire [7:0] o_gray;

gray8b_based_LUT_design gray8b_based_LUT_design_U1(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.i_en(i_en),
.i_data(i_data),
.o_vld(o_vld),
.o_gray(o_gray)
);

//时钟信号的产生
initial i_clk<=0;
always #(`CLK_PERIOD/2) i_clk<=~i_clk;

//复位信号的产生
initial begin
i_rst_n<=0;
#1000 i_rst_n<=1;
end

//输入激励的产生
initial begin
i_en<=1'b0;
i_data<=8'b0;
@(posedge i_rst_n);//等待复位完成
@(posedge i_clk);

i_en<=1'b1;
i_data<=8'b0;
@(posedge i_clk);

repeat(255) begin
i_data<=i_data+8'b1;
@(posedge i_clk);
end
i_en<=1'b0;
i_data<=8'b0;

#1000;
$stop;
end

//使用always对输出数据进行监控
always @(posedge i_clk) begin
if(o_vld)
$display("o_gray is:%b at time %d",o_gray,$time);
else ;
end

endmodule

4.仿真结果

image-20230203175346897

image-20230203180812501


格雷码转换的快速算法

1.关键点

  • 二进制码的最高位直接赋值给格雷码的最高位

  • 对于次高位到最低位,使用二进制码相邻位异或的方式获取:

    • 若二进制码字的第i位和i+1位相同,则第i位的格雷码为0
    • 若二进制码字的第i为和i+1位不同,则第i位的格雷码位1
  • 用generate语法实现异或处理

    1
    2
    3
    4
    5
    6
    7
    8
    genvar i;
    generate
    for(i=MSB-1;i>=0;i=i-1) begin
    always @(posedge i_clk) begin
    o_gray[i]<=i_data[i]^i_data[i+1];
    end
    end
    endgenerate

2.源代码

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
module grayCode_quickMethod_design #(
parameter MSB = 3
)
(
i_clk,
i_rst_n,
i_en,
i_data,
o_vld,
o_gray
);

input wire i_clk;
input wire i_rst_n;
input wire i_en;
input wire [MSB:0] i_data;
output reg o_vld;
output reg [MSB:0] o_gray;

always @(posedge i_clk) begin
if(!i_rst_n)
o_vld<=1'b0;
else
o_vld<=i_en;
end

always @(posedge i_clk) begin
o_gray[MSB]<=i_data[MSB];
end

genvar i;
generate
for(i=MSB-1;i>=0;i=i-1) begin
always @(posedge i_clk) begin
o_gray[i]<=i_data[i]^i_data[i+1];
end
end
endgenerate

endmodule

3.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
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
64
65
66
67
`timescale 1ns / 1ps

`define CLK_PERIOD 20
module grayCode_quickMethod_tb;

parameter GRAY_MSB=7;

reg i_clk;
reg i_rst_n;
reg i_en;
reg [GRAY_MSB:0] i_data;
wire o_vld;
wire [GRAY_MSB:0] o_gray;

grayCode_quickMethod_design #(
.MSB(GRAY_MSB)
)
grayCode_quickMethod_design_U1(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.i_en(i_en),
.i_data(i_data),
.o_vld(o_vld),
.o_gray(o_gray)
);

//时钟信号的产生
initial i_clk<=0;
always #(`CLK_PERIOD/2) i_clk<=~i_clk;

//复位信号的产生
initial begin
i_rst_n<=0;
#1000 i_rst_n<=1;
end

//输入激励的产生
initial begin
i_en<=1'b0;
i_data<=0;
$display("The value of GRAY_MSB is %0d",GRAY_MSB);
@(posedge i_rst_n);//等待复位完成

@(posedge i_clk);
i_en<=1'b1;
i_data<=0;
@(posedge i_clk);

repeat(2**(GRAY_MSB+1)-1) begin
i_data<=i_data+1;
@(posedge i_clk);
end

@(posedge i_clk);
i_en<=1'b0;

$stop;
end

//使用always对输出数据进行监控
always @(posedge i_clk) begin
if(o_vld)
$display("o_gray is:%b",o_gray);
else ;
end

endmodule

4.仿真结果

image-20230203194948495


Testbench中文本文件写入操作

1.关键点

  • $fopen的用法
    • 文件描述符=$fopen("文件名","文件操作类型")
    • 文件操作类型:
      • “r”:读取文件
      • “w”:写入文件
      • “a”:在文件末尾追加文件
  • $fclose的用法
    • $fclose("文件描述符")
  • $fwrite的用法
    • $fwrite("文件描述符","字符串与数据格式",变量)

2.源代码

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
module gray8b_based_LUT_design(
i_clk,
i_rst_n,
i_en,
i_data,
o_vld,
o_gray
);

input wire i_clk;
input wire i_en;
input wire [7:0] i_data;
input wire i_rst_n;
output wire o_vld;
output [7:0] o_gray;

//打两拍操作
reg [1:0] r_vld;
always @(posedge i_clk) begin
if(!i_rst_n)
r_vld<=2'b00;
else
r_vld<={r_vld[0],i_en};
end

assign o_vld=r_vld[1];

blk_mem_gen_0 blk_mem_gen_0_U (
.clka(i_clk), // input wire clka
.addra(i_data), // input wire [7 : 0] addra
.douta(o_gray) // output wire [7 : 0] douta
);

endmodule

3.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
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
64
`timescale 1ns / 1ps

`define CLK_PERIOD 20
module grab8b_based_LUT_tb;
reg i_clk;
reg i_en;
reg [7:0] i_data;
reg i_rst_n;
wire o_vld;
wire [7:0] o_gray;

gray8b_based_LUT_design gray8b_based_LUT_design_U1(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.i_en(i_en),
.i_data(i_data),
.o_vld(o_vld),
.o_gray(o_gray)
);

//时钟信号的产生
initial i_clk<=0;
always #(`CLK_PERIOD/2) i_clk<=~i_clk;

//复位信号的产生
initial begin
i_rst_n<=0;
#1000 i_rst_n<=1;
end

integer wfile;
//输入激励的产生
initial begin
i_en<=1'b0;
i_data<=8'b0;
@(posedge i_rst_n);//等待复位完成
@(posedge i_clk);

i_en<=1'b1;
i_data<=8'b0;
@(posedge i_clk);

repeat(255) begin
i_data<=i_data+8'b1;
@(posedge i_clk);
end
i_en<=1'b0;
#1000;
$fclose(wfile);//关闭文件
$stop;
end

//文件的写入操作
initial begin
wfile=$fopen("D:/App_Data_File/Vivado_data/Vivado_project/gray8b_based_LUT_design_for_fileWrite/output_file/result_data.txt","w");
end
//使用always对输出数据进行监控
always @(posedge i_clk) begin
if(o_vld)
$fwrite(wfile,"%b\n",o_gray);//$fdisplay与$fwrite用法一致,只是$fdisplay可以自动换行
else ;
end

endmodule

4.仿真结果

image-20230204220423725


Testbench中文本文件读取操作

1.关键点

  • 十六进制文件读取$readmemh:$readmenh("文件名",存储器名,起始地址,结束地址)
  • 二进制文件读取$readmemb:$readmenb("文件名",存储器名,起始地址,结束地址)

2.源代码

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
module gray8b_based_LUT_design(
i_clk,
i_rst_n,
i_en,
i_data,
o_vld,
o_gray
);

input wire i_clk;
input wire i_en;
input wire [7:0] i_data;
input wire i_rst_n;
output wire o_vld;
output [7:0] o_gray;

//打两拍操作
reg [1:0] r_vld;
always @(posedge i_clk) begin
if(!i_rst_n)
r_vld<=2'b00;
else
r_vld<={r_vld[0],i_en};
end

assign o_vld=r_vld[1];

blk_mem_gen_0 blk_mem_gen_0_U (
.clka(i_clk), // input wire clka
.addra(i_data), // input wire [7 : 0] addra
.douta(o_gray) // output wire [7 : 0] douta
);

endmodule

3.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
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
64
65
66
67
68
69
70
71
`timescale 1ns / 1ps

`define CLK_PERIOD 20
module grab8b_based_LUT_tb;
reg i_clk;
reg i_en;
reg [7:0] i_data;
reg i_rst_n;
wire o_vld;
wire [7:0] o_gray;

gray8b_based_LUT_design gray8b_based_LUT_design_U1(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.i_en(i_en),
.i_data(i_data),
.o_vld(o_vld),
.o_gray(o_gray)
);

//时钟信号的产生
initial i_clk<=0;
always #(`CLK_PERIOD/2) i_clk<=~i_clk;

//复位信号的产生
initial begin
i_rst_n<=0;
#1000 i_rst_n<=1;
end

//文本文件的读取
reg [7:0] data_mem[255:0];
initial $readmemb("D:/App_Data_File/Vivado_data/Vivado_project/gray8b_based_LUT_design_for_ReadFile/input_file/gray8_data.txt",data_mem);

//输入激励的产生
initial begin
i_en<=1'b0;
i_data<=8'b0;
@(posedge i_rst_n);//等待复位完成
@(posedge i_clk);

i_en<=1'b1;
i_data<=8'b0;
@(posedge i_clk);

repeat(255) begin
i_data<=i_data+8'b1;
@(posedge i_clk);
end

i_en<=1'b0;
#1000;
$stop;
end

//比较读取文件中的数据与实际生成的数据是否相等
integer cnt;
always @(posedge i_clk) begin
if(!i_rst_n)
cnt<=0;
else if(o_vld)
cnt<=cnt+1;
end

always @(posedge i_clk) begin
if(o_vld)
$display("%b\n%b\n",o_gray,data_mem[cnt]);
else ;
end

endmodule

4.仿真结果

image-20230204224322669


基于随机数的自动化仿真测试

  • 通过自动生成随机数测试格雷码转换的快速算法模块是否正确

1.关键点

  • $random(seed)

  • $random表示产生一个32位的随机数,如果不设置seed,每次取得的随机数是相同的

  • {$random}%65536;//产生0~65535的随机数,若不加括号,即为$random%65536,则产生-65535-+65535之间的随机数

  • rand=min+{$random}&(max-min+1)产生一个在min-max之间的随机数

  • 因为格雷码快速算法要延后一个时钟周期才能计算出结果,故对文件中的数据寻址时寻址地址也要延迟一个时钟周期

    1
    2
    3
    4
    5
    reg[7:0] r_data;//用r_data作为寻址地址

    always @(posedge i_clk) begin
    r_data <= i_data;
    end

2.源代码

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
module gray8b_based_LUT_design #(
parameter MSB = 3
)
(
input i_clk,
input i_rst_n,
input i_en,
input[MSB:0] i_data,
output reg o_vld,
output reg[MSB:0] o_gray
);

always @(posedge i_clk) begin
if(!i_rst_n)
o_vld <= 'b0;
else
o_vld <= i_en;
end

always @(posedge i_clk) begin
o_gray[MSB] <= i_data[MSB];
end

genvar i;
generate
for(i=MSB-1; i>=0; i=i-1) begin
always @(posedge i_clk) begin
o_gray[i] <= i_data[i] ^ i_data[i+1];
end
end
endgenerate

endmodule

3.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
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
`timescale 1ns / 1ps

`define CLK_PERIOD 20
module grab8b_based_LUT_tb;

parameter GRAY_MSB = 7;

reg i_clk;
reg i_en;
reg [7:0] i_data;
reg i_rst_n;
wire o_vld;
wire [7:0] o_gray;

gray8b_based_LUT_design #(
.MSB(GRAY_MSB)
)
gray8b_based_LUT_design_U1(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.i_en(i_en),
.i_data(i_data),
.o_vld(o_vld),
.o_gray(o_gray)
);

//时钟信号的产生
initial i_clk<=0;
always #(`CLK_PERIOD/2) i_clk<=~i_clk;

//复位信号的产生
initial begin
i_rst_n<=0;
#1000 i_rst_n<=1;
end

//文本文件的读取
reg [7:0] data_mem[255:0];
initial begin
$readmemb("D:/App_Data_File/Vivado_data/Vivado_project/gray8b_based_LUT_design_for_RandomTest/input_file/gray8_data.txt",data_mem);
end

//输入激励的产生
initial begin
i_en<=1'b0;
i_data<=8'b0;
@(posedge i_rst_n);//等待复位完成

repeat(2**(GRAY_MSB+1)) begin
@(posedge i_clk);
i_en <= 'b1;
i_data <= {$random} % 256; //产生0~255的随机数
end
@(posedge i_clk);
i_en <= 'b0;
#100;

$display("Test Finish!");

$stop;
end

//比较读取文件中的数据与实际生成的数据是否相等
reg[7:0] r_data;

always @(posedge i_clk) begin
r_data <= i_data;
end

always @(posedge i_clk) begin
if(o_vld) begin
$display("i_data = %b\no_gray = %b\ndata_mem = %b",r_data,o_gray,data_mem[r_data]);
if(o_gray == data_mem[r_data]) begin
$display("Successful!\n");
end
else begin
$display("Fail!");
$stop;
end
end
end

endmodule

4.仿真结果

image-20230204234624012


可配置的PWM设计

  • PWM波的产生,有通过i_times控制产生PWM波重复的次数

1.关键点

时序分析:

  • 首先需要需要i_en信号的上升沿与计数结束的标志信号w_end_en置1,从而生成控制计数器工作的使能信号r_cnt_en
  • 在使能信号r_cnt_en的控制下进行PWM的周期计数,当周期计数达到其值时,次数计数加1,从而实现PWM波的次数计数

image-20230205235810859

image-20230206000138514

2.源代码

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
module PWM_design(
input i_clk, //50MHz(20ns)时钟
input i_rst_n, //复位
input i_en, //使能
input[31:0] i_periord, //PWM的周期
input[31:0] i_high, //高电平时间
input[15:0] i_times, //次数
output reg o_pwm //PWM信号
);

reg[1:0] r_en;
wire w_pos_en;
wire w_end_en;
reg r_cnt_en; //内部各个计数器工作的使能信号
reg[31:0] r_pcnt; //周期计数器
reg[15:0] r_tcnt; //周期次数的计数器

/////////////////////////////////////
//产生i_en的上升沿标志信号
always @(posedge i_clk) begin
if(!i_rst_n)
r_en <= 2'b00;
else
r_en <= {r_en[0],i_en};
end
assign w_pos_en = ~r_en[1] & r_en[0];

/////////////////////////////////////
//产生内部PWM结束信号
assign w_end_en = (r_pcnt == (i_periord-1)) && (r_tcnt == (i_times-1));

/////////////////////////////////////
//产生内部各个计数器工作的是能信号
always @(posedge i_clk) begin
if(!i_rst_n)
r_cnt_en <= 1'b0;
else if(w_pos_en)
r_cnt_en <= 1'b1;
else if(w_end_en)
r_cnt_en <= 1'b0;
else
r_cnt_en <= r_cnt_en;
end

/////////////////////////////////////
//周期计数
always @(posedge i_clk) begin
if(!r_cnt_en)
r_pcnt <= 'b0;
else if(r_pcnt < (i_periord-1))
r_pcnt <= r_pcnt+1;
else
r_pcnt <= 'b0;
end

/////////////////////////////////////
//周期次数计数
always @(posedge i_clk) begin
if(!r_cnt_en)
r_tcnt <= 'b0;
else if(r_pcnt == (i_periord-1))
r_tcnt <= r_tcnt+1;
else
r_tcnt <= r_tcnt;
end

/////////////////////////////////////
//输出PWM波形
always @(posedge i_clk) begin
if(!i_rst_n)
o_pwm <= 1'b0;
else if((r_pcnt > 32'd0) && (r_pcnt <= i_high))
o_pwm <= 1'b1;
else
o_pwm <= 1'b0;
end

endmodule

3.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
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
module PWM_tb;

////////////////////////////////////////////////////////////
//参数定义
`define CLK_PERIORD 20 //时钟周期设置为20ns(50MHz)

////////////////////////////////////////////////////////////
//接口申明
reg clk;
reg rst_n;
reg i_en; //使能
reg[31:0] i_periord; //周期,单位:40ns
reg[31:0] i_high; //高电平时间,单位:40ns
reg[15:0] i_times; //次数
wire o_pwm; //PWM信号

////////////////////////////////////////////////////////////
//对被测试的设计进行例化
PWM_design PWM_design_U1(
.i_clk(clk),
.i_rst_n(rst_n),
.i_en(i_en),
.i_periord(i_periord),
.i_high(i_high),
.i_times(i_times),
.o_pwm(o_pwm)
);

////////////////////////////////////////////////////////////
//复位和时钟产生
initial begin
clk <= 0;
rst_n <= 0;
#1000;
rst_n <= 1;
end
always #(`CLK_PERIORD/2) clk = ~clk;

////////////////////////////////////////////////////////////
//测试激励产生
initial begin
i_en <= 1'b0; //使能
i_periord <= 32'd0; //周期,单位:40ns
i_high <= 32'd0; //高电平时间,单位:40ns
i_times <= 32'd0; //次数

@(posedge rst_n); //等待复位完成
#1000;
@(posedge clk);

task_pwm_setting(2500,250,3);

#100_000;

task_pwm_setting(1000,500,5);

#10_000;
$stop;
end

task task_pwm_setting;
input[31:0] pwm_periord;
input[31:0] pwm_high;
input[15:0] pwm_times;
begin
@(posedge clk);
i_en <= 1'b1;

i_periord <= pwm_periord;
i_high <= pwm_high;
i_times <= pwm_times;
#(pwm_periord*pwm_times*`CLK_PERIORD);
@(posedge clk);
i_en <= 1'b0;
end
endtask

endmodule

4.仿真结果

image-20230206000713569


超声波测距设计

1.关键点

  • 超声波测距模块原理:每隔100ms时间,定时产生10us时间的TRIG高脉冲给到超声波测距模块,用于触发超声波模块工作,通过测量ECHO端口高电平持续的时间即可得到距离信息

    超声波测距模块

  • 模块划分与接口定义

    端口划分与接口定义

  • IP核实现乘法器

    image-20230206163556109

    image-20230206164233042

    image-20230206164149984

2.源代码

  • 顶层模块supersonic_wave_design.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
module supersonic_wave_design(
input i_clk,
input i_rst_n,
input i_echo,
output o_trig,
output [13:0] o_s_mm
);

localparam P_CLK_PERIORD=20;
wire w_clk_en;
wire [15:0] w_t_us;

en_design #(
.P_CLK_PERIORD(P_CLK_PERIORD) //i_clk的时钟周期为20ns
)
uut_en_design(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.o_clk_en(w_clk_en)
);

trig_design uut_trig_design(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.i_clk_en(w_clk_en),
.o_trig(o_trig)
);

echo_design uut_echo_design(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.i_clk_en(w_clk_en),
.i_echo(i_echo),
.o_t_us(w_t_us)
);

cal_design uut_cal_design(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.i_t_us(w_t_us),
.o_s_mm(o_s_mm)
);
endmodule
  • 子模块:en_design.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
module en_design #(parameter P_CLK_PERIORD = 20)
(
input i_clk,
input i_rst_n,
output reg o_clk_en
);

localparam P_DIVCLK_MAX = 1000/P_CLK_PERIORD-1;//分频计数器的最大值
reg [7:0] r_divcnt;

//////////////////////////////////////////////
//对输出时钟i_clk做分频计数,产生1us的时钟使能信号
always @(posedge i_clk) begin
if(!i_rst_n)
r_divcnt<=8'b0;
else if(r_divcnt<P_DIVCLK_MAX)
r_divcnt<=r_divcnt+8'b1;
else
r_divcnt<=8'b0;
end

//////////////////////////////////////////////
//输出1us的时钟使能信号
always @(posedge i_clk) begin
if(!i_rst_n)
o_clk_en<=1'b0;
else if(r_divcnt==P_DIVCLK_MAX)
o_clk_en<=1'b1;
else
o_clk_en<=1'b0;
end
endmodule
  • 子模块:trig_design.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
module trig_design(
input i_clk,
input i_rst_n,
input i_clk_en,
output reg o_trig
);

localparam P_TRIG_PERIORD_MAX=100_000-1;//100ms的计数最大值
localparam P_TRIG_HIGH_MAX=10;//10us高脉冲保持时间

reg [16:0] r_tricnt;

////////////////////////////////////////////
//100ms的周期计数
always @(posedge i_clk) begin
if(!i_rst_n)
r_tricnt<=17'b0;
else if(i_clk_en) begin
if(r_tricnt<P_TRIG_PERIORD_MAX)
r_tricnt<=r_tricnt+17'b1;
else
r_tricnt<=17'b0;
end
else
r_tricnt<=r_tricnt;
end

////////////////////////////////////////////
//产生保持10us的高脉冲o_trig信号
always @(posedge i_clk) begin
if(!i_rst_n)
o_trig<=1'b0;
else if(r_tricnt>17'b0 && r_tricnt<=P_TRIG_HIGH_MAX)
o_trig<=1'b1;
else
o_trig<=1'b0;
end
endmodule
  • 子模块:echo_design.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 echo_design(
input i_clk,
input i_rst_n,
input i_clk_en,
input i_echo,
output reg[15:0] o_t_us
);

reg[1:0] r_echo;
wire pos_echo,neg_echo;
reg r_cnt_en;
reg[15:0] r_echo_cnt;

/*
超声波测距模块的量程是2mm~4500mm
根据公式 s = 0.173*t , t = s/0.173
11 < t < 26011
*/

//////////////////////////////////////
//对i_echo信号锁存2拍,获取边沿检测信号,产生计数使能信号r_cnt_en
always @(posedge i_clk) begin
if(!i_rst_n)
r_echo <= 'b0;
else
r_echo <= {r_echo[0],i_echo};
end
assign pos_echo = ~r_echo[1] & r_echo[0];
assign neg_echo = r_echo[1] & ~r_echo[0];

always @(posedge i_clk) begin
if(!i_rst_n)
r_cnt_en <= 'b0;
else if(pos_echo)
r_cnt_en <= 'b1;
else if(neg_echo)
r_cnt_en <= 'b0;
else ;
end

////////////////////////////////////
//对i_echo信号的高电平保持时间进行1us为单位的计数
always @(posedge i_clk) begin
if(!i_rst_n)
r_echo_cnt <= 'b0;
else if(!r_cnt_en)
r_echo_cnt <= 'b0;
else if(i_clk_en)
r_echo_cnt <= r_echo_cnt+1;
else
r_echo_cnt <= r_echo_cnt;
end

////////////////////////////////////
//对r_echo_cnt计数最大值做锁存
always @(posedge i_clk) begin
if(!i_rst_n)
o_t_us <= 'b0;
else if(neg_echo)
o_t_us <= r_echo_cnt;
end

endmodule
  • 子模块:cal_design.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
module cal_design(
input i_clk,
input i_rst_n,
input [15:0] i_t_us,
output [13:0] o_s_mm
);

//s=0.173*t
//s*4096=0.173*t*4096
//s*4096=709*t
//s=709*t/4096
//s=709*t>>12

wire [25:0] w_mult_result;

mult_gen_0 mult_gen_0_U1 (
.CLK(i_clk), // input wire CLK
.A(10'd709), // input wire [9 : 0] A
.B(i_t_us), // input wire [15 : 0] B
.P(w_mult_result) // output wire [25 : 0] P
);

assign o_s_mm=w_mult_result[25:12];//右移12bit,即除以4096

endmodule

3.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
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
64
65
66
67
68
69
70
71
72
73
74
75
76
`timescale 1ns / 1ps

module supersonic_wave_tb;
////////////////////////////////////////////////////////////
//参数定义
`define CLK_PERIORD 20 //时钟周期设置为20ns(50MHz)

////////////////////////////////////////////////////////////
//接口申明
reg clk;
reg rst_n;
wire o_trig;
reg i_echo;
wire[13:0] o_s_mm;

////////////////////////////////////////////////////////////
//对被测试的设计进行例化
supersonic_wave_design supersonic_wave_design_U1(
.i_clk(clk),
.i_rst_n(rst_n),
.i_echo(i_echo),
.o_trig(o_trig),
.o_s_mm(o_s_mm)
);

////////////////////////////////////////////////////////////
//复位和时钟产生
initial begin
clk <= 0;
rst_n <= 0;
#1000;
rst_n <= 1;
end
always #(`CLK_PERIORD/2) clk = ~clk;

////////////////////////////////////////////////////////////
//测试激励产生
initial begin
i_echo = 0;
@(posedge rst_n); //等待复位完成

@(posedge clk);


#400_000_000;

$stop;
end

integer tricnt = 0;
integer dly_time;

always @(posedge o_trig) begin
tricnt = tricnt+1;
#5_000;
i_echo = 1;
dly_time = 11+{$random}%26001;//11 < t < 26011
$display("Test %0d:\ndistance of testbench = %0dmm",tricnt,function_t2s(dly_time));
#(dly_time*1000);
i_echo = 0;
end

initial begin
#1;
$monitor("o_s_mm = %0dmm",o_s_mm);
end

//函数实现运算 s=0.173t
function real function_t2s;
input real t;
begin
function_t2s = 0.173*t;
end
endfunction

endmodule

4.仿真结果

  • RTL视图

image-20230206165117621

  • 打印输出的结果:

image-20230206171150590


自动售卖机状态机设计

  • 有纸币1/2/5,每次投一张,当总投入金额大于等于6时输出为1

1.关键点

  • 状态机转换图的绘制

自动售卖机状态转化图

2.源代码

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
module FSM_design(
input i_clk,
input i_rst_n,
input i_1yuan,
input i_2yuan,
input i_5yuan,
output reg o_done
);

/////////////////////////////////////////////////
//参数和变量的申明
localparam IDLE = 4'd0,
IN_1 = 4'd1,
IN_2 = 4'd2,
IN_3 = 4'd3,
IN_4 = 4'd4,
IN_5 = 4'd5,
IN_6 = 4'd6,
DONE = 4'd7;
localparam MONEY_PAY = 4'd6;

reg[3:0] cstate,nstate;
reg[3:0] money_sum;

/////////////////////////////////////////////////
//时序逻辑,锁存状态
always @(posedge i_clk) begin
if(!i_rst_n)
cstate <= IDLE;
else
cstate <= nstate;
end

/////////////////////////////////////////////////
//组合逻辑实现状态变迁
always @(*) begin
case(cstate)
IDLE: begin
if(i_1yuan || i_2yuan || i_5yuan)
nstate = IN_1;
else
nstate = IDLE;
end
IN_1: begin
if(i_1yuan || i_2yuan || i_5yuan)
nstate = IN_2;
else
nstate = IN_1;
end
IN_2: begin
if(money_sum >= MONEY_PAY)
nstate = DONE;
else if(i_1yuan || i_2yuan || i_5yuan)
nstate = IN_3;
else
nstate = IN_2;
end
IN_3: begin
if(money_sum >= MONEY_PAY)
nstate = DONE;
else if(i_1yuan || i_2yuan || i_5yuan)
nstate = IN_4;
else
nstate = IN_3;
end
IN_4: begin
if(money_sum >= MONEY_PAY)
nstate = DONE;
else if(i_1yuan || i_2yuan || i_5yuan)
nstate = IN_5;
else
nstate = IN_4;
end
IN_5: begin
if(money_sum >= MONEY_PAY)
nstate = DONE;
else if(i_1yuan || i_2yuan || i_5yuan)
nstate = IN_6;
else
nstate = IN_5;
end
IN_6: begin
if(money_sum >= MONEY_PAY)
nstate = DONE;
else if(i_1yuan || i_2yuan || i_5yuan)
nstate = DONE;
else
nstate = IN_6;
end
DONE: nstate = IDLE;
default: nstate = IDLE;
endcase
end

/////////////////////////////////////////////////
//当前状态输入钱币的累加计算
always @(posedge i_clk) begin
if(!i_rst_n)
money_sum <= 'b0;
else begin
case(cstate)
DONE: money_sum <= 'b0;
default: begin
if(i_1yuan)
money_sum <= money_sum+1;
else if(i_2yuan)
money_sum <= money_sum+2;
else if(i_5yuan)
money_sum <= money_sum+5;
else
money_sum <= money_sum;
end
endcase
end
end

/////////////////////////////////////////////////
//状态机的输出赋值
always @(posedge i_clk) begin
if(!i_rst_n)
o_done <= 'b0;
else if(cstate == DONE)
o_done <= 'b1;
else
o_done <= 'b0;
end

endmodule

3.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
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
`timescale 1ns / 1ps

module FSM_tb;
////////////////////////////////////////////////////////////
//参数定义
`define CLK_PERIORD 10 //时钟周期设置为10ns(100MHz)

////////////////////////////////////////////////////////////
//接口申明
reg clk;
reg rst_n;
reg i_1yuan;
reg i_2yuan;
reg i_5yuan;
wire o_done;

////////////////////////////////////////////////////////////
//对被测试的设计进行例化
FSM_design FSM_design_U1(
.i_clk(clk),
.i_rst_n(rst_n),
.i_1yuan(i_1yuan),
.i_2yuan(i_2yuan),
.i_5yuan(i_5yuan),
.o_done(o_done)
);

////////////////////////////////////////////////////////////
//复位和时钟产生
initial begin
clk <= 0;
rst_n <= 0;
#1000;
rst_n <= 1;
end
always #(`CLK_PERIORD/2) clk = ~clk;

////////////////////////////////////////////////////////////
//测试激励产生
integer i;
initial begin
i_1yuan = 0;
i_2yuan = 0;
i_5yuan = 0;

@(posedge rst_n); //等待复位完成

@(posedge clk);

for(i=0; i<20; i=i+1) begin
task_random_pay();
end

#5000;

$stop;
end

task task_random_pay;
integer random_data;
begin
#1000;
random_data = {$random}%3;

@(posedge clk);
if(random_data == 0)
i_1yuan <= 1'b1;
else if(random_data == 1)
i_2yuan <= 1'b1;
else if(random_data == 2)
i_5yuan <= 1'b1;
else ;

@(posedge clk);
i_1yuan <= 0;
i_2yuan <= 0;
i_5yuan <= 0;
end
endtask

always @(posedge clk) begin
if(i_1yuan)
$display("Pay 1 yuan.");
else if(i_2yuan)
$display("Pay 2 yuan.");
else if(i_5yuan)
$display("Pay 5 yuan.");
else if(o_done)
$display("Got you want.");
else ;
end

endmodule

4.仿真结果

image-20230206174427148


串口指令帧解码设计

  • 接收串口数据接收,串(8个1bit)转并(8bit)

  • 译码串口(UART)命令帧,提取有效数据

    • 帧头(4个字节):0xaa + 0x55 + 0xa5 + 0x5a
    • 有效数据(10个字节):beep_periord(4个字节)+ beep_high(4个字节)+ beep_num(2个字节)32bit/16bit传输时,高字节在前
    • 帧尾(4个字节) :0xcc + 0x33 + 0xc3 + 0x3c
  • 按照命令控制蜂鸣器

1.关键点

  • UART

    • UART(Universal Asynchronous Receiver/Transmitter),即通用异步收发,它的数据传输不需要时钟,只要两条信号线分别进行数据的收(RX)和发(TX)
    • 收发双方首先需要约定好数据传输的速率(简单的讲就是约定好一个数据位传输的时间)和帧格式(即一帧的长短,一帧由哪些位组成,他们的功能都是什么
  • 串口(UART)协议:

    • 它由1个起始位(必须为0)、8个数据位(用户数据)、1个奇偶校验位(用于简单的纠错以保证传输可靠性)和1或2个停止位(必须为1)组成
    • 除了奇偶校验位,其他三个部分都是必须的
    • 当信号线空闲时,必须为高电平
    • 要发起数据传输时,1个低电平的脉冲表示起始位,然后连续传输8个数据位和若干个高电平的停止位

    image-20230206175550881

  • 模块划分与接口定义

    模块划分与接口定义

  • 串口数据串并转换模块时序分析

    串口接收串并转换时序

  • 指令译码模块的状态转换图:在状态的迁移过程中做判断

    译码模块状态转换图

2.源代码

  • 顶层模块:uart_decoder_design.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
64
65
66
67
68
69
70
module uart_decoder_design(
input i_clk,
input i_rst_n,
input i_uart_rx,
output o_pwm
);

`define CLK_PERIORD 20 //时钟周期设置为20ns(50MHz)
parameter UART_BPS_RATE = 115200; //串口波特率定义

wire w_bps_en;
wire w_bps_done;
wire w_rx_en;
wire[7:0] w_rx_data;

wire w_beep_en;
wire[31:0] w_beep_periord;
wire[31:0] w_beep_high;
wire[15:0] w_beep_num;

////////////////////////////////////////////////////
//串口波特率控制模块例化
bps_design #(
.UART_BPS_RATE(UART_BPS_RATE), //串口波特率设置(<=115200),单位:bps
.CLK_PERIORD(`CLK_PERIORD) //时钟周期设置,单位:ns
) uut_bps_design(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.i_bps_en(w_bps_en),
.o_bps_done(w_bps_done)
);

////////////////////////////////////////////////////
//串口数据串并转换模块例化
s2p_design uut_s2p_design(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.i_uart_rx(i_uart_rx),
.i_bps_done(w_bps_done),
.o_bps_en(w_bps_en),
.o_rx_en(w_rx_en),
.o_rx_data(w_rx_data)
);

////////////////////////////////////////////////////
//串口指令帧解码模块例化
decoder_design uut_decoder_design(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.i_rx_en(w_rx_en),
.i_rx_data(w_rx_data),
.o_beep_en(w_beep_en),
.o_beep_periord(w_beep_periord),
.o_beep_high(w_beep_high),
.o_beep_num(w_beep_num)
);

////////////////////////////////////////////////////
//PWM蜂鸣器控制模块例化
beep_design uut_beep_design(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.i_en(w_beep_en),
.i_periord(w_beep_periord),
.i_high(w_beep_high),
.i_times(w_beep_num),
.o_pwm(o_pwm)
);

endmodule
  • 子模块:bps_design.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
module bps_design#(
parameter UART_BPS_RATE = 115200, //串口波特率设置(<=115200),单位:bps
parameter CLK_PERIORD = 20 //时钟周期设置,单位:ns
)
(
input i_clk,
input i_rst_n,
input i_bps_en,
output reg o_bps_done
);

localparam BPS_CNT_MAX = 1000_000_000/UART_BPS_RATE/CLK_PERIORD-1;
localparam BPS_CNT_HALF = BPS_CNT_MAX/2;

reg[15:0] r_bps_cnt;

////////////////////////////////////////////
//波特率的分频计数
always @(posedge i_clk) begin
if(!i_rst_n)
r_bps_cnt <= 'b0;
else if(i_bps_en) begin
if(r_bps_cnt < BPS_CNT_MAX)
r_bps_cnt <= r_bps_cnt+1;
else
r_bps_cnt <= 'b0;
end
else
r_bps_cnt <= 'b0;
end

////////////////////////////////////////////
//产生o_bps_done信号
always @(posedge i_clk) begin
if(r_bps_cnt == BPS_CNT_HALF)
o_bps_done <= 1'b1;
else
o_bps_done <= 1'b0;
end

endmodule
  • 子模块:s2p_design.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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//串并转化模块
module s2p_design(
input i_clk,
input i_rst_n,
input i_uart_rx,
input i_bps_done,
output reg o_bps_en,
output reg o_rx_en,
output reg[7:0] o_rx_data
);

reg[3:0] r_bit_cnt;
reg[7:0] r_rx_data;

////////////////////////////////////////////
//产生i_uart_rx信号的下降沿
reg[1:0] r_uart_rx;
wire neg_uart_rx;
always @(posedge i_clk) begin
if(!i_rst_n)
r_uart_rx <= 'b0;
else
r_uart_rx <= {r_uart_rx[0],i_uart_rx};
end
assign neg_uart_rx = r_uart_rx[1] & ~r_uart_rx[0];

////////////////////////////////////////////
//产生波特率计数使能信号o_bps_en
always @(posedge i_clk) begin
if(!i_rst_n)
o_bps_en <= 'b0;
else if(neg_uart_rx && !o_bps_en)
o_bps_en <= 'b1;
else if(i_bps_done && (r_bit_cnt == 4'd9))
o_bps_en <= 'b0;
else
o_bps_en <= o_bps_en;
end

////////////////////////////////////////////
//i_uart_rx的数据位进行计数
always @(posedge i_clk) begin
if(!i_rst_n)
r_bit_cnt <= 'b0;
else if(!o_bps_en)
r_bit_cnt <= 'b0;
else if(i_bps_done)
r_bit_cnt <= r_bit_cnt+1;
else
r_bit_cnt <= r_bit_cnt;
end

////////////////////////////////////////////
//对i_uart_rx的有效数据进行串并转换的锁存
always @(posedge i_clk) begin
if(!i_rst_n)
r_rx_data <= 'b0;
else if(i_bps_done) begin
case(r_bit_cnt)
4'd1: r_rx_data[0] <= i_uart_rx;
4'd2: r_rx_data[1] <= i_uart_rx;
4'd3: r_rx_data[2] <= i_uart_rx;
4'd4: r_rx_data[3] <= i_uart_rx;
4'd5: r_rx_data[4] <= i_uart_rx;
4'd6: r_rx_data[5] <= i_uart_rx;
4'd7: r_rx_data[6] <= i_uart_rx;
4'd8: r_rx_data[7] <= i_uart_rx;
default: ;
endcase
end
else ;
end

////////////////////////////////////////////
//产生并行数据有效信号和并行数据锁存
always @(posedge i_clk) begin
if(!i_rst_n)
o_rx_en <= 'b0;
else if(i_bps_done && (r_bit_cnt == 4'd9))
o_rx_en <= 'b1;
else
o_rx_en <= 'b0;
end
always @(posedge i_clk) begin
if(i_bps_done && (r_bit_cnt == 4'd9))
o_rx_data <= r_rx_data;
else ;
end

endmodule
  • 子模块:beep_design.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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
module beep_design(
input i_clk, //50MHz(20ns)时钟
input i_rst_n, //复位
input i_en, //使能
input[31:0] i_periord, //PWM的周期
input[31:0] i_high, //高电平时间
input[15:0] i_times, //次数
output reg o_pwm //PWM信号
);

reg[1:0] r_en;
wire w_pos_en;
wire w_end_en;
reg r_cnt_en; //内部各个计数器工作的使能信号
reg[31:0] r_pcnt; //周期计数器
reg[15:0] r_tcnt; //周期次数的计数器

/////////////////////////////////////
//产生i_en的上升沿标志信号
always @(posedge i_clk) begin
if(!i_rst_n)
r_en <= 2'b00;
else
r_en <= {r_en[0],i_en};
end
assign w_pos_en = ~r_en[1] & r_en[0];

/////////////////////////////////////
//产生内部PWM结束信号
assign w_end_en = (r_pcnt == (i_periord-1)) && (r_tcnt == (i_times-1));

/////////////////////////////////////
//产生内部各个计数器工作的是能信号
always @(posedge i_clk) begin
if(!i_rst_n)
r_cnt_en <= 1'b0;
else if(w_pos_en)
r_cnt_en <= 1'b1;
else if(w_end_en)
r_cnt_en <= 1'b0;
else
r_cnt_en <= r_cnt_en;
end

/////////////////////////////////////
//周期计数
always @(posedge i_clk) begin
if(!r_cnt_en)
r_pcnt <= 'b0;
else if(r_pcnt < (i_periord-1))
r_pcnt <= r_pcnt+1;
else
r_pcnt <= 'b0;
end

/////////////////////////////////////
//周期次数计数
always @(posedge i_clk) begin
if(!r_cnt_en)
r_tcnt <= 'b0;
else if(r_pcnt == (i_periord-1))
r_tcnt <= r_tcnt+1;
else
r_tcnt <= r_tcnt;
end

/////////////////////////////////////
//输出PWM波形
always @(posedge i_clk) begin
if(!i_rst_n)
o_pwm <= 1'b0;
else if((r_pcnt > 32'd0) && (r_pcnt <= i_high))
o_pwm <= 1'b1;
else
o_pwm <= 1'b0;
end
endmodule
  • 子模块:decoder_design.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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
module decoder_design(
input i_clk,
input i_rst_n,
input i_rx_en,
input[7:0] i_rx_data,
output reg o_beep_en,
output reg[31:0] o_beep_periord,
output reg[31:0] o_beep_high,
output reg[15:0] o_beep_num
);

//帧头
localparam DATA_SOF1 = 8'haa;
localparam DATA_SOF2 = 8'h55;
localparam DATA_SOF3 = 8'ha5;
localparam DATA_SOF4 = 8'h5a;

//帧尾
localparam DATA_EOF1 = 8'hcc;
localparam DATA_EOF2 = 8'h33;
localparam DATA_EOF3 = 8'hc3;
localparam DATA_EOF4 = 8'h3c;

//状态
localparam IDLE = 4'd0;
localparam SOF1 = 4'd1;
localparam SOF2 = 4'd2;
localparam SOF3 = 4'd3;
localparam RXDB = 4'd4;
localparam EOF1 = 4'd5;
localparam EOF2 = 4'd6;
localparam EOF3 = 4'd7;
localparam EOF4 = 4'd8;
localparam DONE = 4'd9;

reg[3:0] r_cstate,r_nstate;
reg[3:0] r_bytecnt;

////////////////////////////////////////////
//时序逻辑状态切换
always @(posedge i_clk) begin
if(!i_rst_n)
r_cstate <= IDLE;
else
r_cstate <= r_nstate;
end

////////////////////////////////////////////
//组合逻辑切换状态
always @(*) begin
case(r_cstate)
IDLE: begin
if(i_rx_en) begin
if(i_rx_data == DATA_SOF1)
r_nstate = SOF1;
else
r_nstate = IDLE;
end
else
r_nstate = IDLE;
end
SOF1: begin
if(i_rx_en) begin
if(i_rx_data == DATA_SOF2)
r_nstate = SOF2;
else
r_nstate = IDLE;
end
else
r_nstate = SOF1;
end
SOF2: begin
if(i_rx_en) begin
if(i_rx_data == DATA_SOF3)
r_nstate = SOF3;
else
r_nstate = IDLE;
end
else
r_nstate = SOF2;
end
SOF3: begin
if(i_rx_en) begin
if(i_rx_data == DATA_SOF4)
r_nstate = RXDB;
else
r_nstate = IDLE;
end
else
r_nstate = SOF3;
end

RXDB: begin
if(i_rx_en) begin
if(r_bytecnt >= 4'd9)
r_nstate = EOF1;
else
r_nstate = RXDB;
end
else
r_nstate = RXDB;
end

EOF1: begin
if(i_rx_en) begin
if(i_rx_data == DATA_EOF1)
r_nstate = EOF2;
else
r_nstate = IDLE;
end
else
r_nstate = EOF1;
end
EOF2: begin
if(i_rx_en) begin
if(i_rx_data == DATA_EOF2)
r_nstate = EOF3;
else
r_nstate = IDLE;
end
else
r_nstate = EOF2;
end
EOF3: begin
if(i_rx_en) begin
if(i_rx_data == DATA_EOF3)
r_nstate = EOF4;
else
r_nstate = IDLE;
end
else
r_nstate = EOF3;
end
EOF4: begin
if(i_rx_en) begin
if(i_rx_data == DATA_EOF4)
r_nstate = DONE;
else
r_nstate = IDLE;
end
else
r_nstate = EOF4;
end

DONE: r_nstate = IDLE;
default: ;
endcase
end

////////////////////////////////////////////
//对10个有效数据字节进行计数
always @(posedge i_clk) begin
if(r_cstate == RXDB) begin
if(i_rx_en)
r_bytecnt <= r_bytecnt+1;
else
r_bytecnt <= r_bytecnt;
end
else
r_bytecnt <= 'b0;
end

////////////////////////////////////////////
//对有效数据进行锁存(采集)
always @(posedge i_clk) begin
if((r_cstate == RXDB) && i_rx_en) begin
case(r_bytecnt)
4'd0: o_beep_periord[31:24] <= i_rx_data;
4'd1: o_beep_periord[23:16] <= i_rx_data;
4'd2: o_beep_periord[15:8] <= i_rx_data;
4'd3: o_beep_periord[7:0] <= i_rx_data;

4'd4: o_beep_high[31:24] <= i_rx_data;
4'd5: o_beep_high[23:16] <= i_rx_data;
4'd6: o_beep_high[15:8] <= i_rx_data;
4'd7: o_beep_high[7:0] <= i_rx_data;

4'd8: o_beep_num[15:8] <= i_rx_data;
4'd9: o_beep_num[7:0] <= i_rx_data;
default: ;
endcase
end
end

////////////////////////////////////////////
//有效数据使能信号产生
always @(posedge i_clk) begin
if(!i_rst_n)
o_beep_en <= 'b0;
else if(r_cstate == DONE)
o_beep_en <= 'b1;
else
o_beep_en <= 'b0;
end

endmodule

3.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
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
`timescale 1ns / 1ps

module uart_decoder_tb;

////////////////////////////////////////////////////////////
//参数定义
`define CLK_PERIORD 20 //时钟周期设置为20ns(50MHz)
parameter UART_BPS_RATE = 115200;
parameter BPS_DLY_BIT = 1000000000/UART_BPS_RATE;


////////////////////////////////////////////////////////////
//接口申明
reg i_clk;
reg i_rst_n;
reg i_uart_rx;
wire o_pwm;

////////////////////////////////////////////////////////////
//对被测试的设计进行例化
uart_decoder_design uut_uart_decoder_design(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.i_uart_rx(i_uart_rx),
.o_pwm(o_pwm)
);

////////////////////////////////////////////////////////////
//复位和时钟产生
initial begin
i_clk <= 0;
i_rst_n <= 0;
#1000;
i_rst_n <= 1;
end
always #(`CLK_PERIORD/2) i_clk <= ~i_clk;

////////////////////////////////////////////////////////////
//测试激励产生
initial begin
i_uart_rx <= 'b1;
@(posedge i_rst_n); //等待复位完成

@(posedge i_clk);

#100_000;
task_cmd_tx(32'd1000,32'd100,16'd5);

#1_000_000;
task_cmd_tx(32'd5000,32'd1000,16'd6);

#1_000_000;
$stop;
end

integer i;
//模拟传输一个byte的任务
task task_uart_tx;
input[7:0] tx_db;
begin
i_uart_rx <= 'b0;
#BPS_DLY_BIT;
for(i=0; i<8; i=i+1) begin
i_uart_rx <= tx_db[i];
#BPS_DLY_BIT;
end
i_uart_rx <= 'b1;
#BPS_DLY_BIT;
end
endtask

//模拟一次完整的UART命令帧
task task_cmd_tx;
input[31:0] tx_beep_periord;
input[31:0] tx_beep_high;
input[15:0] tx_beep_num;
begin
task_uart_tx(8'haa);
task_uart_tx(8'h55);
task_uart_tx(8'ha5);
task_uart_tx(8'h5a);

task_uart_tx(tx_beep_periord[31:24]);
task_uart_tx(tx_beep_periord[23:16]);
task_uart_tx(tx_beep_periord[15:8]);
task_uart_tx(tx_beep_periord[7:0]);

task_uart_tx(tx_beep_high[31:24]);
task_uart_tx(tx_beep_high[23:16]);
task_uart_tx(tx_beep_high[15:8]);
task_uart_tx(tx_beep_high[7:0]);

task_uart_tx(tx_beep_num[15:8]);
task_uart_tx(tx_beep_num[7:0]);

task_uart_tx(8'hcc);
task_uart_tx(8'h33);
task_uart_tx(8'hc3);
task_uart_tx(8'h3c);
end
endtask

endmodule

4.仿真结果

  • RTL视图

image-20230206210526508

  • 仿真波形

image-20230206211033267


Reference

欢迎来到ssy的世界