0%

Verilog之由浅入深学习

本文主要利用Vivado工具,结合实际应用进行巩固学习,重点对设计思路进行理解

PLL的IP核配置

1.配置过程

  • PLL实际相当于FPGA内部的一个时钟管理模块,能提供不同频率、相位、占空比的时钟

  • 配置过程:

    image-20230105112155627

    image-20230105105058413 image-20230105105258480

2.实例应用

  • 利用PLL进行时钟分频,并通过PLL产生的4个不同频率的时钟,分别驱动4个LED指示灯闪烁一样的频率

2.1源文件

  • PLL_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
    `timescale 1ns / 1ps
    module PLL_design(
    input i_sys_clk, //外部输入50MHz时钟信号
    input i_rst_n, //外部输入复位信号,低电平有效
    output[7:0] o_led //8个LED指示灯亮灭控制
    );

    wire clk_12m5; //PLL输出12.5MHz时钟
    wire clk_25m; //PLL输出25MHz时钟
    wire clk_50m; //PLL输出50MHz时钟
    wire clk_100m; //PLL输出100MHz时钟
    wire sys_rst_n; //PLL输出的locked信号,作为FPGA内部的复位信号,低电平复位,高电平正常工作

    //`define SIMULATION_AT7 //仿真取消注释,实际应用则加上注释
    `ifdef SIMULATION_AT7
    `define PARAMETER_COUNTER 9
    `else
    `define PARAMETER_COUNTER 23
    `endif

    clk_wiz_0 clk_wiz_0_U1(
    // Clock out ports
    .clk_out1(clk_12m5), // output clk_out1
    .clk_out2(clk_25m), // output clk_out2
    .clk_out3(clk_50m), // output clk_out3
    .clk_out4(clk_100m), // output clk_out4
    // Status and control signals
    .reset(!i_rst_n), // input reset
    .locked(sys_rst_n), // output locked
    // Clock in ports
    .clk_in1(i_sys_clk) // input clk_in1
    );

    //-------------------------------------
    //12.5MHz时钟进行分频闪烁,计数器为23位
    led_controller #(`PARAMETER_COUNTER)
    u2_led_controller_clk12m5(
    .clk(clk_12m5), //时钟信号
    .rst_n(sys_rst_n), //复位信号,低电平有效
    .sled(o_led[0]) //LED指示灯接口
    );

    //-------------------------------------
    //25MHz时钟进行分频闪烁,计数器为24位
    led_controller #(`PARAMETER_COUNTER+1)
    u3_led_controller_clk25m(
    .clk(clk_25m), //时钟信号
    .rst_n(sys_rst_n), //复位信号,低电平有效
    .sled(o_led[1]) //LED指示灯接口
    );

    //-------------------------------------
    //50MHz时钟进行分频闪烁,计数器为25位
    led_controller #(`PARAMETER_COUNTER+2)
    u4_led_controller_clk50m(
    .clk(clk_50m), //时钟信号
    .rst_n(sys_rst_n), //复位信号,低电平有效
    .sled(o_led[2]) //LED指示灯接口
    );

    //-------------------------------------
    //100MHz时钟进行分频闪烁,计数器为26位
    led_controller #(`PARAMETER_COUNTER+3)
    u5_led_controller_clk100m(
    .clk(clk_100m), //时钟信号
    .rst_n(sys_rst_n), //复位信号,低电平有效
    .sled(o_led[3]) //LED指示灯接口
    );

    //-------------------------------------
    //高4位LED指示灯关闭
    assign o_led[7:4] = 4'b1111;
    endmodule
  • led_controller.v

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
`timescale 1ns / 1ps

module led_controller(
input clk, //时钟信号
input rst_n, //复位信号,低电平有效
output sled //LED指示灯接口
);

parameter CNT_HIGH = 24; //计数器最高位

//-------------------------------------
reg[(CNT_HIGH-1):0] cnt; //24位计数器
//cnt计数器进行循环计数
always @ (posedge clk or negedge rst_n) begin
if(!rst_n)
cnt <= 0;
else
cnt <= cnt+1'b1;
end

assign sled = cnt[CNT_HIGH-1];

endmodule

2.2Testbench

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

module PLL_tb;

reg sys_clk_i; //50MHz时钟信号
reg ext_rst_n; //复位信号,低电平有效
wire[7:0] led; //8个LED指示灯接口

PLL_design PLL_design_U1(
.i_sys_clk(sys_clk_i), //外部输入50MHz时钟信号
.i_rst_n(ext_rst_n), //外部输入复位信号,低电平有效
.o_led(led) //8个LED指示灯接口
);

initial begin
sys_clk_i <= 0;
ext_rst_n <= 0; //复位中
#1000;
@(posedge sys_clk_i); #2;
ext_rst_n <= 1; //复位结束,正常工作
#200_000;
$finish;
end

always #10 sys_clk_i <= ~sys_clk_i; //50MHz时钟产生

endmodule

2.3仿真结果

image-20230105110145328

image-20230105110200795


自定义IP核的创建与配置

1.IP核的创建

工程源码:

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

module led_controller(
input i_clk, //时钟信号
input i_rst_n, //复位信号,低电平有效
output o_sled //LED 指示灯接口
);

parameter CLK_FREQUENCY = 25000; //clk 的时钟频率,KHz
parameter LED_FLASH_FREQUENCY = 1; //LED 输出的闪烁频率,Hz

//cnt 分频计数最大值
`define MAX_CNT ((CLK_FREQUENCY*1000/LED_FLASH_FREQUENCY)-1)
//cnt 分频计数最大值的一半
`define MAX_CNT_DIV2 (`MAX_CNT/2-1)

//-------------------------------------
reg[31:0] cnt; //计数器

//cnt 计数器进行循环计数
always @ (posedge i_clk or negedge i_rst_n) begin
if(!i_rst_n)
cnt <= 32'd0;
else if(cnt < `MAX_CNT)
cnt <= cnt+1'b1;
else
cnt <= 32'd0;
end

assign o_sled = (cnt < `MAX_CNT_DIV2) ? 1'b1:1'b0;

endmodule

image-20230105114939129

image-20230105114641913

image-20230105114807577

2.IP核的配置

  • 配置页面:

    image-20230105115328827

  • 第一个配置页面名为 Identification,即用户定制 IP 核相关的配置信息,如 IP 核供应商(Vendor)、库名称 (Library)、IP 核名称(Name)、版本号(Version)、IP 核显示名称(Display name)、描 述(Description)、供应商显示名称(Vendor display name)、公司网址(Company url) 等。特别提醒大家别忽略了最下面的 Categories 项,默认是空白的,若点击右侧的小加号, 可以增加一个名称,例如本实例增加了一个名为 UserIP 的名称选项,将来生成的用户定制 IP 核在我们的 IP 核配置面板中将会归类到名为 UserIP 类别的文件夹下

    image-20230105115813666

  • 第二个配置页面名为Compatibility,即设定该 IP 核所支持的器件家族(Family)

    image-20230105115838942

  • 第三个配置页面名为File Groups,可以预览 IP 核包含的相关源码文件,在源码工程中包含的所有Verilog源码或者仿真测试脚本,也都会出现在这里,被集成到IP核中

    image-20230105115905275

  • 第四个配置页面名为Customization Parameters,配置页面罗列源码中所有可配置的参数 (即parameter 所定义的)

    image-20230105120008185

    image-20230105120047146
  • 第五个配置页面名为Ports and Interfaces,配置页面显示IP核的对外接口

    image-20230105120323311

  • 第六个配置页面名为Addressing and Memory,配置页面则是针对含有总线接口,并且具有 多个寄存器需要寻址的 IP 核,我们的 IP 核则不需要,所以是空白的

  • 第七个配置页面名为Customization GUI,配置页面则显示当前接口在 GUI 上的 layout 和 preview 信息

    image-20230105120559933

  • 第八个配置页面名为Review and Package,在这里点击Package IP完成IP核的配置

  • 打开 IP Catalog 后,我们可以看到刚刚定义的用户 IP 核 led_controller_v1_0 已经出现在了 UserIP 文件夹下面

    image-20230105121227412

3.移植IP核

  • 找到创建IP核时所保存的位置,复制以下三个文件

    image-20230105124655320

  • 在另一工程的ip文件夹下创建led_controller的文件夹

    image-20230105124900805

  • 将刚刚复制的三个文件粘贴于此

    image-20230105125001515

  • 在新工程中按如下图所示操作

    image-20230105125355216

  • 添加led_controller所在路径

    image-20230105125543056

  • 最终可以在IP Catalog中看到此自定义IP

    image-20230105125650162


按键消抖

1.思路

  • 思路:每过20ms(在20ms内检测到有按键按下,则认为是抖动,计数器清零)才将输入的按键值锁存,当两次锁存的按键值发生变化时,则有相应按键按下

    image-20230106121303404

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

module Key_filter_design(
input i_clk, //外部输入50MHz时钟信号
input i_rst_n, //外部输入复位信号,低电平有效
input[3:0] i_key_h, //4个独立按键输入,未按下为高电平,按下后为低电平
output reg[7:0] o_led //8个LED指示灯接口
);

//-------------------------------------
//按键抖动判断逻辑
wire key; //所有按键值相与的结果,用于按键触发判断
reg[1:0] keyr; //按键值key的缓存寄存器

assign key = i_key_h[0] & i_key_h[1] & i_key_h[2] & i_key_h[3];

always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n)
keyr <= 2'b11;
else
keyr <= {keyr[0],key};
end

wire key_neg = ~keyr[0] & keyr[1]; //有按键被按下
wire key_pos = keyr[0] & ~keyr[1]; //有按键被释放

//-------------------------------------
//定时计数20ms时间,用于对按键的消抖判断
reg[19:0] cnt;

//按键消抖定时计数器
always @ (posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n)
cnt <= 20'd0;
else if(key_pos || key_neg)
cnt <= 20'd0;
else if(cnt < 20'd999_999)
cnt <= cnt + 1'b1;
else
cnt <= 20'd0;
end

//-------------------------------------
reg[3:0] key_halue[1:0];
//定时采集按键值
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
key_halue[0] <= 4'b1111;
key_halue[1] <= 4'b1111;
end
else begin
key_halue[1] <= key_halue[0];
if(cnt == 20'd999_999)
key_halue[0] <= i_key_h; //定时键值采集
else ;
end
end

//判断是哪个按键按下
wire[3:0] key_press = key_halue[1] & ~key_halue[0]; //消抖后按键值变化标志位

//-------------------------------------
//LED切换控制
always @ (posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n)
o_led <= 8'hff;
else if(key_press[0])
o_led[0] <= ~o_led[0];
else if(key_press[1])
o_led[1] <= ~o_led[1];
else if(key_press[2])
o_led[2] <= ~o_led[2];
else if(key_press[3])
o_led[3] <= ~o_led[3];
else ;
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
`timescale 1ns / 1ps

module Key_filter_tb;
reg sys_clk_i; //50MHz时钟信号
reg ext_rst_n; //复位信号,低电平有效
reg[3:0] key_h; //4个独立按键输入,未按下为高电平,按下后为低电平
wire[7:0] led; //8个LED指示灯接口

Key_filter_design Key_filter_design_U1(
.i_clk(sys_clk_i), //外部输入50MHz时钟信号
.i_rst_n(ext_rst_n), //外部输入复位信号,低电平有效
.i_key_h(key_h),
.o_led(led) //8个LED指示灯接口
);

initial begin
sys_clk_i = 0;
ext_rst_n = 0; //复位中
key_h = 4'b1111;
#1000;
@(posedge sys_clk_i); #2;
ext_rst_n = 1; //复位结束,正常工作
@(posedge sys_clk_i); #2;

//模拟按键抖动(实际未被按下)
key_h[0] = 1'b0;
#1_000_000; //1ms
key_h[0] = 1'b1;
#5_000_000; //5ms
key_h[0] = 1'b0;
#3_000_000; //3ms
key_h[0] = 1'b1;

//模拟按键动作
key_h[0] = 1'b0;
#1_000_000; //1ms
key_h[0] = 1'b1;
#5_000_000; //5ms
key_h[0] = 1'b0;
#3_000_000; //3ms
key_h[0] = 1'b1;
#3_000_000; //3ms
key_h[0] = 1'b0;
#500_000_000; //500ms,按下
key_h[0] = 1'b1;
#3_000_000; //3ms
key_h[0] = 1'b0;
#3_000_000; //3ms
key_h[0] = 1'b1;

#50_000_000;
$finish;
end

always #10 sys_clk_i = ~sys_clk_i; //50MHz时钟产生
endmodule

4.仿真结果

image-20230106124156404


数码管显示

1.思路

  • 通过8bit的分时计数器div_cnt,每隔64个时钟周期显示一个数码管

  • 片选数码管与显示数据的同步,此处时序很妙

2.源代码

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

module Seg8_display_design(
input sys_clk_i, //外部输入50MHz时钟信号
input ext_rst_n, //外部输入复位信号,低电平有效
output[3:0] dtube_cs_n, //7段数码管位选信号
output[7:0] dtube_data //7段数码管段选信号(包括小数点为8段)
);

wire clk_12m5; //PLL输出12.5MHz时钟
wire clk_25m; //PLL输出25MHz时钟
wire clk_50m; //PLL输出50MHz时钟
wire clk_100m; //PLL输出100MHz时钟
wire sys_rst_n; //PLL输出的locked信号,作为FPGA内部的复位信号,低电平复位,高电平正常工作

//-------------------------------------
//PLL例化
clk_wiz_0 u1_clk_wiz_0(
// Clock in ports
.clk_in1(sys_clk_i), // input clk_in1
// Clock out ports
.clk_out1(clk_12m5), // output clk_out1
.clk_out2(clk_25m), // output clk_out2
.clk_out3(clk_50m), // output clk_out3
.clk_out4(clk_100m), // output clk_out4
// Status and control signals
.reset(!ext_rst_n), // input reset
.locked(sys_rst_n) // output locked
);

//-------------------------------------
//25MHz时钟进行分频,产生每秒递增的16位数据
wire[15:0] display_num; //数码管显示数据,[15:12]--数码管千位,[11:8]--数码管百位,[7:4]--数码管十位,[3:0]--数码管个位

Counter_design uut_Counter_design(
.clk(clk_25m), //时钟信号
.rst_n(sys_rst_n), //复位信号,低电平有效
.display_num(display_num) //LED指示灯接口
);

//-------------------------------------
//4位数码管显示驱动
Seg_drive_design uut_Seg_drive_design(
.clk(clk_25m), //时钟信号
.rst_n(sys_rst_n), //复位信号,低电平有效
.display_num(display_num), //LED指示灯接口
.dtube_cs_n(dtube_cs_n), //7段数码管位选信号
.dtube_data(dtube_data) //7段数码管段选信号(包括小数点为8段)
);

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

module Counter_design(
input clk, //时钟信号,25MHz
input rst_n, //复位信号,低电平有效
output reg[15:0] display_num //数码管显示数据,[15:12]--数码管千位,[11:8]--数码管百位,[7:4]--数码管十位,[3:0]--数码管个位
);

`define SIMULATION_AT7
`ifdef SIMULATION_AT7
`define MAX_CNT 25'd9_999
`else
`define MAX_CNT 25'd24_999_999
`endif

//-------------------------------------------------
//1s定时产生逻辑
reg[24:0] timer_cnt; //1s计数器,0-24999999
//1s定时计数
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
timer_cnt <= 25'd0;
else if(timer_cnt < `MAX_CNT)
timer_cnt <= timer_cnt+1'b1;
else
timer_cnt <= 25'd0;
end
wire timer_1s_flag = (timer_cnt == `MAX_CNT); //1s定时的标志位,高有效一个时钟周期

//-------------------------------------------------
//递增数据产生逻辑
//显示数据每秒递增
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
display_num <= 16'd0;
else if(timer_1s_flag)
display_num <= display_num+1'b1;
end

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

module Seg_drive_design(
input clk, //时钟信号,25MHz
input rst_n, //复位信号,低电平有效
input[15:0] display_num, //数码管显示数据,[15:12]--数码管千位,[11:8]--数码管百位,[7:4]--数码管十位,[3:0]--数码管个位
output reg[3:0] dtube_cs_n, //7段数码管位选信号
output reg[7:0] dtube_data //7段数码管段选信号(包括小数点为8段)
);

//-------------------------------------------------
//参数定义
//数码管显示 0~F 对应段选输出
parameter NUM0 = 8'h3f,//0
NUM1 = 8'h06,//1
NUM2 = 8'h5b,//2
NUM3 = 8'h4f,//3
NUM4 = 8'h66,//4
NUM5 = 8'h6d,//5
NUM6 = 8'h7d,//6
NUM7 = 8'h07,//7
NUM8 = 8'h7f,//8
NUM9 = 8'h6f,//9
NUMA = 8'h77,//a
NUMB = 8'h7c,//b
NUMC = 8'h39,//c
NUMD = 8'h5e,//d
NUME = 8'h79,//e
NUMF = 8'h71,//f
NDOT = 8'h80;//小数点显示

//数码管位选 0~3 对应输出
parameter CSN = 4'b1111,
CS0 = 4'b1110,
CS1 = 4'b1101,
CS2 = 4'b1011,
CS3 = 4'b0111;

//-------------------------------------------------
//分时显示数据控制单元
reg[3:0] current_display_num; //当前显示数据
reg[7:0] div_cnt; //分时计数器

//分时计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
div_cnt <= 8'd0;
else
div_cnt <= div_cnt+1'b1;
end

//显示数据
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
current_display_num <= 4'h0;
else begin
case(div_cnt)//提前了一个时钟周期获得显示的数据
8'hff: current_display_num <= display_num[3:0];
8'h3f: current_display_num <= display_num[7:4];
8'h7f: current_display_num <= display_num[11:8];
8'hbf: current_display_num <= display_num[15:12];
default: ;
endcase
end
end

//段选数据译码
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
dtube_data <= NUM0;
else begin
case(current_display_num)
4'h0: dtube_data <= NUM0;
4'h1: dtube_data <= NUM1;
4'h2: dtube_data <= NUM2;
4'h3: dtube_data <= NUM3;
4'h4: dtube_data <= NUM4;
4'h5: dtube_data <= NUM5;
4'h6: dtube_data <= NUM6;
4'h7: dtube_data <= NUM7;
4'h8: dtube_data <= NUM8;
4'h9: dtube_data <= NUM9;
4'ha: dtube_data <= NUMA;
4'hb: dtube_data <= NUMB;
4'hc: dtube_data <= NUMC;
4'hd: dtube_data <= NUMD;
4'he: dtube_data <= NUME;
4'hf: dtube_data <= NUMF;
default: ;
endcase
end
end

//位选译码:每64个时钟周期选择一个数码管
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
dtube_cs_n <= CSN;
else begin
case(div_cnt[7:6])
2'b00: dtube_cs_n <= CS0;
2'b01: dtube_cs_n <= CS1;
2'b10: dtube_cs_n <= CS2;
2'b11: dtube_cs_n <= CS3;
default: dtube_cs_n <= CSN;
endcase
end
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
`timescale 1ns / 1ps

module Seg8_display_tb;
reg sys_clk_i; //50MHz时钟信号
reg ext_rst_n; //复位信号,低电平有效
wire[3:0] dtube_cs_n; //7段数码管位选信号
wire[7:0] dtube_data; //7段数码管段选信号(包括小数点为8段)

Seg8_display_design uut_Seg8_display_design(
.sys_clk_i(sys_clk_i), //外部输入50MHz时钟信号
.ext_rst_n(ext_rst_n), //外部输入复位信号,低电平有效
.dtube_cs_n(dtube_cs_n),
.dtube_data(dtube_data)
);

//数码管显示 0~F 对应段选输出
parameter NUM0 = 8'h3f,//c0,
NUM1 = 8'h06,//f9,
NUM2 = 8'h5b,//a4,
NUM3 = 8'h4f,//b0,
NUM4 = 8'h66,//99,
NUM5 = 8'h6d,//92,
NUM6 = 8'h7d,//82,
NUM7 = 8'h07,//F8,
NUM8 = 8'h7f,//80,
NUM9 = 8'h6f,//90,
NUMA = 8'h77,//88,
NUMB = 8'h7c,//83,
NUMC = 8'h39,//c6,
NUMD = 8'h5e,//a1,
NUME = 8'h79,//86,
NUMF = 8'h71,//8e;
NDOT = 8'h80;//小数点显示

initial begin
sys_clk_i = 0;
ext_rst_n = 0; //复位中
#1000;
@(posedge sys_clk_i); #2;
ext_rst_n = 1; //复位结束,正常工作
#100_000_000; //0.1s
$finish;
end

always #10 sys_clk_i = ~sys_clk_i; //50MHz时钟产生

reg[15:0] display_seg7; //模拟显示的数码管数据
reg[3:0] dtube_data_decoder;

always @(dtube_data) begin
case(dtube_data)
NUM0: dtube_data_decoder <= 4'h0;
NUM1: dtube_data_decoder <= 4'h1;
NUM2: dtube_data_decoder <= 4'h2;
NUM3: dtube_data_decoder <= 4'h3;
NUM4: dtube_data_decoder <= 4'h4;
NUM5: dtube_data_decoder <= 4'h5;
NUM6: dtube_data_decoder <= 4'h6;
NUM7: dtube_data_decoder <= 4'h7;
NUM8: dtube_data_decoder <= 4'h8;
NUM9: dtube_data_decoder <= 4'h9;
NUMA: dtube_data_decoder <= 4'ha;
NUMB: dtube_data_decoder <= 4'hb;
NUMC: dtube_data_decoder <= 4'hc;
NUMD: dtube_data_decoder <= 4'hd;
NUME: dtube_data_decoder <= 4'he;
NUMF: dtube_data_decoder <= 4'hf;
default: ;
endcase
end

always @(dtube_cs_n or dtube_data_decoder) begin
if(!dtube_cs_n[0])
display_seg7[3:0] <= dtube_data_decoder;
else ;
if(!dtube_cs_n[1])
display_seg7[7:4] <= dtube_data_decoder;
else ;
if(!dtube_cs_n[2])
display_seg7[11:8] <= dtube_data_decoder;
else ;
if(!dtube_cs_n[3])
display_seg7[15:12] <= dtube_data_decoder;
else ;
end

endmodule

4.仿真结果

image-20230106155844156

image-20230106160011927


4$\times$4矩阵按键

  • 采集4X4矩阵按键的键值,输出到数码管的末位,数码管每新输入一位数据,都会将原有数据左移一位

1.思路

  • 对列输入按键会触发行输出扫描信号,开始扫描,对于四行行信号,在同一时刻只会有一行信号被拉低,其余置高,只有被拉低的那一行信号是有效的,那么此时的列输入对应的就是这一组有效信号
  • 通过状态机实现行信号的扫描,主要思路是:当检测到有按键按下时,进入状态K_H1OL,在这个状态内将行信号0拉低;接着进入状态K_H2OL,在这个状态中,先判断列输入信号是否有按键按下(即为1110,1101,1011,0111),若有效则说明是第0行的按键被按下了,否则将行信号1拉低;在进入K_H3OL状态之前,首先需判断按键是否弹起,若弹起则回到空闲状态,否则则继续进行行扫描,进入K_H3OL状态,在K_H3OL状态中的判断与K_H2OL一致,后面的状态跳变与状态内的操作依此类推。

2.源代码

  • key4T4_sample_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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
`timescale 1ns / 1ps

module key4T4_sample_design(
input clk, //外部输入25MHz时钟信号
input rst_n, //外部输入复位信号,低电平有效
input[3:0] key_h, //4个行按键输入,未按下为高电平,按下后为低电平
output reg[3:0] key_v, //4个列按键输出
output reg[15:0] display_num //数码管显示数据,[15:12]--数码管千位,[11:8]--数码管百位,[7:4]--数码管十位,[3:0]--数码管个位
);

//-------------------------------------
//列按键键值采样
wire[3:0] keyh_value; //列按键按下键值,高电平有效

key_filter_design uut_key_filter_design(
.clk(clk), //外部输入25MHz时钟信号
.rst_n(rst_n), //外部输入复位信号,低电平有效
.key_h(key_h), //4个独立按键输入,未按下为高电平,按下后为低电平
.keyh_value(keyh_value) //行按键键值,高电平有效
);

//-------------------------------------
//状态机采样键值
reg[3:0] nstate,cstate;
parameter K_IDLE = 4'd0; //空闲状态,等待
parameter K_H1OL = 4'd1; //key_v[0]拉低
parameter K_H2OL = 4'd2; //key_v[1]拉低
parameter K_H3OL = 4'd3; //key_v[2]拉低
parameter K_H4OL = 4'd4; //key_v[3]拉低
parameter K_CHCK = 4'd5;

//状态切换
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cstate <= K_IDLE;
else
cstate <= nstate;
end

always @(cstate or keyh_value or key_h) begin
case(cstate)
K_IDLE: begin
if(keyh_value != 4'b0000)
nstate <= K_H1OL;
else
nstate <= K_IDLE;
end
K_H1OL: begin
nstate <= K_H2OL;
end
K_H2OL: begin
if(key_h != 4'b1111)
nstate <= K_IDLE;
else
nstate <= K_H3OL;
end
K_H3OL: begin
if(key_h != 4'b1111)
nstate <= K_IDLE;
else
nstate <= K_H4OL;
end
K_H4OL: begin
if(key_h != 4'b1111)
nstate <= K_IDLE;
else
nstate <= K_CHCK;
end
K_CHCK: begin
nstate <= K_IDLE;
end
default: ;
endcase
end

//-------------------------------------
//采样键值
reg[3:0] new_value; //新采样数据
reg new_rdy; //新采样数据有效

always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
key_v <= 4'b0000;
new_value <= 4'd0;
new_rdy <= 1'b0;
end
else begin
case(cstate)
K_IDLE: begin
key_v <= 4'b0000;
new_value <= 4'd0;
new_rdy <= 1'b0;
end
K_H1OL: begin
key_v <= 4'b1110;
new_value <= 4'd0;
new_rdy <= 1'b0;
end
K_H2OL: begin
case(key_h)
4'b1110: begin
key_v <= 4'b0000;
new_value <= 4'd0;
new_rdy <= 1'b1;
end
4'b1101: begin
key_v <= 4'b0000;
new_value <= 4'd1;
new_rdy <= 1'b1;
end
4'b1011: begin
key_v <= 4'b0000;
new_value <= 4'd2;
new_rdy <= 1'b1;
end
4'b0111: begin
key_v <= 4'b0000;
new_value <= 4'd3;
new_rdy <= 1'b1;
end
default: begin
key_v <= 4'b1101;
new_value <= 4'd0;
new_rdy <= 1'b0;
end
endcase
end
K_H3OL: begin
case(key_h)
4'b1110: begin
key_v <= 4'b0000;
new_value <= 4'd4;
new_rdy <= 1'b1;
end
4'b1101: begin
key_v <= 4'b0000;
new_value <= 4'd5;
new_rdy <= 1'b1;
end
4'b1011: begin
key_v <= 4'b0000;
new_value <= 4'd6;
new_rdy <= 1'b1;
end
4'b0111: begin
key_v <= 4'b0000;
new_value <= 4'd7;
new_rdy <= 1'b1;
end
default: begin
key_v <= 4'b1011;
new_value <= 4'd0;
new_rdy <= 1'b0;
end
endcase
end
K_H4OL: begin
case(key_h)
4'b1110: begin
key_v <= 4'b0000;
new_value <= 4'd8;
new_rdy <= 1'b1;
end
4'b1101: begin
key_v <= 4'b0000;
new_value <= 4'd9;
new_rdy <= 1'b1;
end
4'b1011: begin
key_v <= 4'b0000;
new_value <= 4'd10;
new_rdy <= 1'b1;
end
4'b0111: begin
key_v <= 4'b0000;
new_value <= 4'd11;
new_rdy <= 1'b1;
end
default: begin
key_v <= 4'b0111;
new_value <= 4'd0;
new_rdy <= 1'b0;
end
endcase
end
K_CHCK: begin
case(key_h)
4'b1110: begin
key_v <= 4'b0000;
new_value <= 4'd12;
new_rdy <= 1'b1;
end
4'b1101: begin
key_v <= 4'b0000;
new_value <= 4'd13;
new_rdy <= 1'b1;
end
4'b1011: begin
key_v <= 4'b0000;
new_value <= 4'd14;
new_rdy <= 1'b1;
end
4'b0111: begin
key_v <= 4'b0000;
new_value <= 4'd15;
new_rdy <= 1'b1;
end
default: begin
key_v <= 4'b0000;
new_value <= 4'd0;
new_rdy <= 1'b0;
end
endcase
end
default: ;
endcase
end
end

//-------------------------------------
//产生最新键值
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
display_num <= 16'h0000;
else if(new_rdy)
display_num <= {display_num[11:0],new_value}; //左移显示
end

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

module key_filter_design(
input clk, //外部输入25MHz时钟信号
input rst_n, //外部输入复位信号,低电平有效
input[3:0] key_h, //4个行按键输入,未按下为高电平,按下后为低电平
output[3:0] keyh_value //行按键按下键值,高电平有效
);

//-------------------------------------
//按键抖动判断逻辑
wire key; //所有按键值相与的结果,用于按键触发判断
reg[3:0] keyr; //按键值key的缓存寄存器

assign key = key_h[0] & key_h[1] & key_h[2] & key_h[3];

always @(posedge clk or negedge rst_n)
if (!rst_n)
keyr <= 4'b1111;
else
keyr <= {keyr[2:0],key};

wire key_neg = ~keyr[2] & keyr[3]; //有按键被按下
wire key_pos = keyr[2] & ~keyr[3]; //有按键被释放

//-------------------------------------
//定时计数逻辑,用于对按键的消抖判断
reg[19:0] cnt;
//按键消抖定时计数器
always @ (posedge clk or negedge rst_n) begin
if (!rst_n)
cnt <= 20'd0;
else if(key_pos || key_neg)
cnt <= 20'd0;
else if(cnt < 20'd999_999)
cnt <= cnt + 1'b1;
else
cnt <= 20'd0;
end

reg[3:0] key_halue[1:0];
//定时采集按键值
always @(posedge clk or negedge rst_n)
if (!rst_n) begin
key_halue[0] <= 4'b1111;
key_halue[1] <= 4'b1111;
end
else begin
key_halue[1] <= key_halue[0];
if(cnt == 20'd999_999) key_halue[0] <= key_h; //定时键值采集
else ;
end

assign keyh_value = key_halue[1] & ~key_halue[0]; //消抖后按键值变化标志位

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

module seg7_display_design(
input clk, //时钟信号,25MHz
input rst_n, //复位信号,低电平有效
input[15:0] display_num, //数码管显示数据,[15:12]--数码管千位,[11:8]--数码管百位,[7:4]--数码管十位,[3:0]--数码管个位
output reg[3:0] dtube_cs_n, //7段数码管位选信号
output reg[7:0] dtube_data //7段数码管段选信号(包括小数点为8段)
);

//-------------------------------------------------
//参数定义
//数码管显示 0~F 对应段选输出
parameter NUM0 = 8'h3f,//c0,
NUM1 = 8'h06,//f9,
NUM2 = 8'h5b,//a4,
NUM3 = 8'h4f,//b0,
NUM4 = 8'h66,//99,
NUM5 = 8'h6d,//92,
NUM6 = 8'h7d,//82,
NUM7 = 8'h07,//F8,
NUM8 = 8'h7f,//80,
NUM9 = 8'h6f,//90,
NUMA = 8'h77,//88,
NUMB = 8'h7c,//83,
NUMC = 8'h39,//c6,
NUMD = 8'h5e,//a1,
NUME = 8'h79,//86,
NUMF = 8'h71,//8e;
NDOT = 8'h80; //小数点显示

//数码管位选 0~3 对应输出
parameter CSN = 4'b1111,
CS0 = 4'b1110,
CS1 = 4'b1101,
CS2 = 4'b1011,
CS3 = 4'b0111;

//-------------------------------------------------
//分时显示数据控制单元
reg[3:0] current_display_num; //当前显示数据
reg[7:0] div_cnt; //分时计数器

//分时计数器
always @(posedge clk or negedge rst_n)
if(!rst_n) div_cnt <= 8'd0;
else div_cnt <= div_cnt+1'b1;

//显示数据
always @(posedge clk or negedge rst_n)
if(!rst_n) current_display_num <= 4'h0;
else begin
case(div_cnt)
8'hff: current_display_num <= display_num[3:0];
8'h3f: current_display_num <= display_num[7:4];
8'h7f: current_display_num <= display_num[11:8];
8'hbf: current_display_num <= display_num[15:12];
default: ;
endcase
end

//段选数据译码
always @(posedge clk or negedge rst_n)
if(!rst_n) dtube_data <= NUM0;
else begin
case(current_display_num)
4'h0: dtube_data <= NUM0;
4'h1: dtube_data <= NUM1;
4'h2: dtube_data <= NUM2;
4'h3: dtube_data <= NUM3;
4'h4: dtube_data <= NUM4;
4'h5: dtube_data <= NUM5;
4'h6: dtube_data <= NUM6;
4'h7: dtube_data <= NUM7;
4'h8: dtube_data <= NUM8;
4'h9: dtube_data <= NUM9;
4'ha: dtube_data <= NUMA;
4'hb: dtube_data <= NUMB;
4'hc: dtube_data <= NUMC;
4'hd: dtube_data <= NUMD;
4'he: dtube_data <= NUME;
4'hf: dtube_data <= NUMF;
default: ;
endcase
end

//位选译码
always @(posedge clk or negedge rst_n)
if(!rst_n) dtube_cs_n <= CSN;
else begin
case(div_cnt[7:6])
2'b00: dtube_cs_n <= CS0;
2'b01: dtube_cs_n <= CS1;
2'b10: dtube_cs_n <= CS2;
2'b11: dtube_cs_n <= CS3;
default: dtube_cs_n <= CSN;
endcase
end

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

module Keyboard4T4_design(
input sys_clk_i, //外部输入50MHz时钟信号
input ext_rst_n, //外部输入复位信号,低电平有效
input[3:0] key_h, //4个行按键输入,未按下为高电平,按下后为低电平
output[3:0] key_v, //4个列按键输出
output[3:0] dtube_cs_n, //7段数码管位选信号
output[7:0] dtube_data //7段数码管段选信号(包括小数点为8段)
);

wire clk_12m5; //PLL输出12.5MHz时钟
wire clk_25m; //PLL输出25MHz时钟
wire clk_50m; //PLL输出50MHz时钟
wire clk_100m; //PLL输出100MHz时钟
wire sys_rst_n; //PLL输出的locked信号,作为FPGA内部的复位信号,低电平复位,高电平正常工作

//-------------------------------------
//PLL例化
clk_wiz_0 u1_clk_wiz_0(
// Clock in ports
.clk_in1(sys_clk_i), // input clk_in1
// Clock out ports
.clk_out1(clk_12m5), // output clk_out1
.clk_out2(clk_25m), // output clk_out2
.clk_out3(clk_50m), // output clk_out3
.clk_out4(clk_100m), // output clk_out4
// Status and control signals
.reset(!ext_rst_n), // input reset
.locked(sys_rst_n) // output locked
);

//-------------------------------------
//键值采集,产生数码管显示数据
wire[15:0] display_num; //数码管显示数据,[15:12]--数码管千位,[11:8]--数码管百位,[7:4]--数码管十位,[3:0]--数码管个位
key4T4_sample_design uut_key4T4_sample_design(
.clk(clk_25m), //时钟信号
.rst_n(sys_rst_n), //复位信号,低电平有效
.key_h(key_h), //4个按键输入,未按下为高电平,按下后为低电平
.key_v(key_v), //4个行按键输出
.display_num(display_num) //数码管显示数据,[15:12]--数码管千位,[11:8]--数码管百位,[7:4]--数码管十位,[3:0]--数码管个位
);

//-------------------------------------
//4位数码管显示驱动
seg7_display_design uut_seg7_display_design(
.clk(clk_25m), //时钟信号
.rst_n(sys_rst_n), //复位信号,低电平有效
.display_num(display_num),
.dtube_cs_n(dtube_cs_n), //7段数码管位选信号
.dtube_data(dtube_data) //7段数码管段选信号(包括小数点为8段)
);

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

module Keyboard4T4_tb;
reg sys_clk_i; //50MHz时钟信号
reg ext_rst_n; //复位信号,低电平有效
reg[3:0] key_h; //4个行按键输入,未按下为高电平,按下后为低电平
wire[3:0] key_v; //4个列按键输出
wire[3:0] dtube_cs_n; //7段数码管位选信号
wire[7:0] dtube_data; //7段数码管段选信号(包括小数点为8段)

Keyboard4T4_design uut_Keyboard4T4_design(
.sys_clk_i(sys_clk_i), //外部输入50MHz时钟信号
.ext_rst_n(ext_rst_n), //外部输入复位信号,低电平有效
.key_h(key_h),
.key_v(key_v),
.dtube_cs_n(dtube_cs_n),
.dtube_data(dtube_data)
);


//数码管显示 0~F 对应段选输出
parameter NUM0 = 8'h3f,//c0,
NUM1 = 8'h06,//f9,
NUM2 = 8'h5b,//a4,
NUM3 = 8'h4f,//b0,
NUM4 = 8'h66,//99,
NUM5 = 8'h6d,//92,
NUM6 = 8'h7d,//82,
NUM7 = 8'h07,//F8,
NUM8 = 8'h7f,//80,
NUM9 = 8'h6f,//90,
NUMA = 8'h77,//88,
NUMB = 8'h7c,//83,
NUMC = 8'h39,//c6,
NUMD = 8'h5e,//a1,
NUME = 8'h79,//86,
NUMF = 8'h71,//8e;
NDOT = 8'h80; //小数点显示

reg[4:0] key_press_value; //模拟按键值

initial begin
sys_clk_i = 0;
ext_rst_n = 0; //复位中
key_press_value = 5'd16; //没有按下
key_h = 4'b1111;
#1000;
@(posedge sys_clk_i); #2;
ext_rst_n = 1; //复位结束,正常工作

press_key(4'hf);
press_key(4'he);
press_key(4'hd);
press_key(4'hc);
press_key(4'hb);
press_key(4'ha);
press_key(4'h9);
press_key(4'h8);
press_key(4'h7);
press_key(4'h6);
press_key(4'h5);
press_key(4'h4);
press_key(4'h3);
press_key(4'h2);
press_key(4'h1);
press_key(4'h0);

#200_000;
$finish;
end

always #10 sys_clk_i = ~sys_clk_i; //50MHz时钟产生

task press_key;
input[3:0] value;
begin
key_press_value = value;
#100_000_000; //100ms delay
key_press_value = 5'd16; //没有按下
#100_000_000; //400ms delay
end
endtask

always @(*) begin
if(key_press_value < 5'd16) begin
case(key_press_value)
5'd0: begin
if(!key_v[0]) key_h <= 4'b1110;
else key_h <= 4'b1111;
end
5'd1: begin
if(!key_v[0]) key_h <= 4'b1101;
else key_h <= 4'b1111;
end
5'd2: begin
if(!key_v[0]) key_h <= 4'b1011;
else key_h <= 4'b1111;
end
5'd3: begin
if(!key_v[0]) key_h <= 4'b0111;
else key_h <= 4'b1111;
end

5'd4: begin
if(!key_v[1]) key_h <= 4'b1110;
else key_h <= 4'b1111;
end
5'd5: begin
if(!key_v[1]) key_h <= 4'b1101;
else key_h <= 4'b1111;
end
5'd6: begin
if(!key_v[1]) key_h <= 4'b1011;
else key_h <= 4'b1111;
end
5'd7: begin
if(!key_v[1]) key_h <= 4'b0111;
else key_h <= 4'b1111;
end

5'd8: begin
if(!key_v[2]) key_h <= 4'b1110;
else key_h <= 4'b1111;
end
5'd9: begin
if(!key_v[2]) key_h <= 4'b1101;
else key_h <= 4'b1111;
end
5'd10: begin
if(!key_v[2]) key_h <= 4'b1011;
else key_h <= 4'b1111;
end
5'd11: begin
if(!key_v[2]) key_h <= 4'b0111;
else key_h <= 4'b1111;
end

5'd12: begin
if(!key_v[3]) key_h <= 4'b1110;
else key_h <= 4'b1111;
end
5'd13: begin
if(!key_v[3]) key_h <= 4'b1101;
else key_h <= 4'b1111;
end
5'd14: begin
if(!key_v[3]) key_h <= 4'b1011;
else key_h <= 4'b1111;
end
5'd15: begin
if(!key_v[3]) key_h <= 4'b0111;
else key_h <= 4'b1111;
end
default: key_h <= 4'b1111;
endcase
end
else ;
end

reg[15:0] display_seg7; //模拟显示的数码管数据
reg[3:0] dtube_data_decoder;

always @(dtube_data) begin
case(dtube_data)
NUM0: dtube_data_decoder <= 4'h0;
NUM1: dtube_data_decoder <= 4'h1;
NUM2: dtube_data_decoder <= 4'h2;
NUM3: dtube_data_decoder <= 4'h3;
NUM4: dtube_data_decoder <= 4'h4;
NUM5: dtube_data_decoder <= 4'h5;
NUM6: dtube_data_decoder <= 4'h6;
NUM7: dtube_data_decoder <= 4'h7;
NUM8: dtube_data_decoder <= 4'h8;
NUM9: dtube_data_decoder <= 4'h9;
NUMA: dtube_data_decoder <= 4'ha;
NUMB: dtube_data_decoder <= 4'hb;
NUMC: dtube_data_decoder <= 4'hc;
NUMD: dtube_data_decoder <= 4'hd;
NUME: dtube_data_decoder <= 4'he;
NUMF: dtube_data_decoder <= 4'hf;
default: ;
endcase
end

always @(dtube_cs_n or dtube_data_decoder) begin
if(!dtube_cs_n[0]) display_seg7[3:0] <= dtube_data_decoder;
else ;
if(!dtube_cs_n[1]) display_seg7[7:4] <= dtube_data_decoder;
else ;
if(!dtube_cs_n[2]) display_seg7[11:8] <= dtube_data_decoder;
else ;
if(!dtube_cs_n[3]) display_seg7[15:12] <= dtube_data_decoder;
else ;
end

endmodule

4.仿真结果

image-20230108232720454

image-20230108232946416


序列码状态机

1.思路

image-20230109202839388

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

module Sequential_detect_design(
input clk,
input rst_n, //外部输入复位信号,低电平有效
input in_code,
output reg out_rdy
);

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

parameter IDLE = 4'd0;
parameter S1 = 4'd1;
parameter S2 = 4'd2;
parameter S3 = 4'd3;
parameter S4 = 4'd4;
parameter S5 = 4'd5;
parameter S6 = 4'd6;

//两段式状态机
always @(posedge clk or negedge rst_n)
if(!rst_n) cstate <= IDLE;
else cstate <= nstate;

always @(*) begin
case(cstate)
IDLE: begin
out_rdy = 1'b0;
if(in_code == 1'b1)
nstate = S1;
else
nstate = IDLE;
end
S1: begin
out_rdy = 1'b0;
if(in_code == 1'b0)
nstate = S2;
else
nstate = S1;
end
S2: begin
out_rdy = 1'b0;
if(in_code == 1'b1)
nstate = S3;
else
nstate = IDLE;
end
S3: begin
out_rdy = 1'b0;
if(in_code == 1'b1)
nstate = S4;
else
nstate = S2;
end
S4: begin
out_rdy = 1'b0;
if(in_code == 1'b1)
nstate = S1;
else
nstate = S5;
end
S5: begin
out_rdy = 1'b0;
if(in_code == 1'b1)
nstate = S3;
else
nstate = S6;
end
S6: begin
out_rdy = 1'b1;
if(in_code == 1'b1)
nstate = S1;
else
nstate = IDLE;
end
default: begin
out_rdy = 1'b0;
nstate = IDLE;
end
endcase
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
`timescale 1ns / 1ps

module Sequential_detect_tb;

reg clk; //50MHz时钟信号
reg rst_n; //复位信号,低电平有效
reg in_code;
wire out_rdy;

Sequential_detect_design uut_Sequential_detect_design(
.clk(clk),
.rst_n(rst_n), //外部输入复位信号,低电平有效
.in_code(in_code),
.out_rdy(out_rdy)
);

initial begin
clk = 0;
rst_n = 0; //复位中
#1000;
@(posedge clk);
rst_n = 1; //复位结束,正常工作

code_generation(6'b000000);
code_generation(6'b111111);
code_generation(6'b101100);
code_generation(6'b100010);
code_generation(6'b001110);
code_generation(6'b101100);
code_generation(6'b000000);
#1000;
$finish;
end

always #10 clk = ~clk;

task code_generation;
input[5:0] code_bus;
begin
@(posedge clk);
in_code <= code_bus[5];
@(posedge clk);
in_code <= code_bus[4];
@(posedge clk);
in_code <= code_bus[3];
@(posedge clk);
in_code <= code_bus[2];
@(posedge clk);
in_code <= code_bus[1];
@(posedge clk);
in_code <= code_bus[0];
end
endtask

endmodule

4.仿真结果

image-20230109205926993


UART的loopback实例

  • 接收PC端发送的UART数据,原数据返回给PC端,即loopback功能

1.源代码

  • uart_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
//接收PC端发送的UART数据,原数据返回给PC端,即loopback功能
module uart_design(
input sys_clk_i, //外部输入50MHz时钟信号
input ext_rst_n, //外部输入复位信号,低电平有效
input uart_rx, // UART接收数据信号
output uart_tx // UART发送数据信号
);

//-------------------------------------
//PLL例化
wire clk_12m5; //PLL输出12.5MHz时钟
wire clk_25m; //PLL输出25MHz时钟
wire clk_50m; //PLL输出50MHz时钟
wire clk_100m; //PLL输出100MHz时钟
wire sys_rst_n; //PLL输出的locked信号,作为FPGA内部的复位信号,低电平复位,高电平正常工作

clk_wiz_0 u1_clk_wiz_0
(
// Clock in ports
.clk_in1(sys_clk_i), // input clk_in1
// Clock out ports
.clk_out1(clk_12m5), // output clk_out1
.clk_out2(clk_25m), // output clk_out2
.clk_out3(clk_50m), // output clk_out3
.clk_out4(clk_100m), // output clk_out4
// Status and control signals
.reset(!ext_rst_n), // input reset
.locked(sys_rst_n)); // output locked


//-------------------------------------
//下面的四个模块中,speed_rx和speed_tx是两个完全独立的硬件模块,可称之为逻辑复制
//(不是资源共享,和软件中的同一个子程序调用不能混为一谈)

wire bps_start1,bps_start2; //接收到数据后,波特率时钟启动信号置位
wire clk_bps1,clk_bps2; // clk_bps_r高电平为接收数据位的中间采样点,同时也作为发送数据的数据改变点
wire[7:0] rx_data; //接收数据寄存器,保存直至下一个数据来到
wire rx_int; //接收数据中断信号,接收到数据期间始终为高电平

//UART接收信号波特率设置
speed_setting u2_speed_rx(
.clk(clk_25m), //波特率选择模块
.rst_n(sys_rst_n),
.bps_start(bps_start1),
.clk_bps(clk_bps1)
);
//UART接收数据处理
my_uart_rx u3_my_uart_rx(
.clk(clk_25m), //接收数据模块
.rst_n(sys_rst_n),
.uart_rx(uart_rx),
.rx_data(rx_data),
.rx_int(rx_int),
.clk_bps(clk_bps1),
.bps_start(bps_start1)
);

//-------------------------------------
//UART发送信号波特率设置
speed_setting u4_speed_tx(
.clk(clk_25m), //波特率选择模块
.rst_n(sys_rst_n),
.bps_start(bps_start2),
.clk_bps(clk_bps2)
);
//UART发送数据处理
my_uart_tx u5_my_uart_tx(
.clk(clk_25m), //发送数据模块
.rst_n(sys_rst_n),
.rx_data(rx_data),
.rx_int(rx_int),
.uart_tx(uart_tx),
.clk_bps(clk_bps2),
.bps_start(bps_start2)
);

endmodule
  • my_uart_tx.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
module my_uart_tx(
clk,
rst_n,
rx_data,
rx_int,
uart_tx,
clk_bps,
bps_start
);

input clk; // 25MHz主时钟
input rst_n; //低电平复位信号
input clk_bps; // clk_bps_r高电平为接收数据位的中间采样点,同时也作为发送数据的数据改变点
input[7:0] rx_data; //接收数据寄存器
input rx_int; //接收数据中断信号,接收到数据期间始终为高电平,在该模块中利用它的下降沿来启动串口发送数据
output uart_tx; // RS232发送数据信号
output bps_start; //接收或者要发送数据,波特率时钟启动信号置位

//---------------------------------------------------------
reg rx_int0,rx_int1,rx_int2; //rx_int信号寄存器,捕捉下降沿滤波用
wire neg_rx_int; // rx_int下降沿标志位

always @ (posedge clk or negedge rst_n)
if(!rst_n) begin
rx_int0 <= 1'b0;
rx_int1 <= 1'b0;
rx_int2 <= 1'b0;
end
else begin
rx_int0 <= rx_int;
rx_int1 <= rx_int0;
rx_int2 <= rx_int1;
end

assign neg_rx_int = ~rx_int1 & rx_int2; //捕捉到下降沿后,neg_rx_int拉高保持一个主时钟周期

//---------------------------------------------------------
reg[7:0] tx_data; //待发送数据的寄存器
reg bps_start_r;
reg tx_en; //发送数据使能信号,高有效
reg[3:0] num;

always @ (posedge clk or negedge rst_n)
if(!rst_n) begin
bps_start_r <= 1'b0;
tx_en <= 1'b0;
tx_data <= 8'd0;
end
else if(neg_rx_int) begin //接收数据完毕,准备把接收到的数据发回去
bps_start_r <= 1'b1;
tx_data <= rx_data; //把接收到的数据存入发送数据寄存器
tx_en <= 1'b1; //进入发送数据状态中
end
else if(num == 4'd10) begin //数据发送完成,复位
bps_start_r <= 1'b0;
tx_en <= 1'b0;
end

assign bps_start = bps_start_r;

//---------------------------------------------------------
reg uart_tx_r;

always @ (posedge clk or negedge rst_n)
if(!rst_n) begin
num <= 4'd0;
uart_tx_r <= 1'b1;
end
else if(tx_en) begin
if(clk_bps) begin
num <= num+1'b1;
case (num)
4'd0: uart_tx_r <= 1'b0; //发送起始位
4'd1: uart_tx_r <= tx_data[0]; //发送bit0
4'd2: uart_tx_r <= tx_data[1]; //发送bit1
4'd3: uart_tx_r <= tx_data[2]; //发送bit2
4'd4: uart_tx_r <= tx_data[3]; //发送bit3
4'd5: uart_tx_r <= tx_data[4]; //发送bit4
4'd6: uart_tx_r <= tx_data[5]; //发送bit5
4'd7: uart_tx_r <= tx_data[6]; //发送bit6
4'd8: uart_tx_r <= tx_data[7]; //发送bit7
4'd9: uart_tx_r <= 1'b1; //发送结束位
default: uart_tx_r <= 1'b1;
endcase
end
else if(num == 4'd10) num <= 4'd0; //复位
end

assign uart_tx = uart_tx_r;

endmodule
  • my_uart_rx.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
module my_uart_rx(
clk,
rst_n,
uart_rx,
rx_data,
rx_int,
clk_bps,
bps_start
);

input clk; // 25MHz主时钟
input rst_n; //低电平复位信号
input uart_rx; // RS232接收数据信号
input clk_bps; // clk_bps的高电平为接收或者发送数据位的中间采样点
output bps_start; //接收到数据后,波特率时钟启动信号置位
output[7:0] rx_data; //接收数据寄存器,保存直至下一个数据来到
output rx_int; //接收数据中断信号,接收到数据期间始终为高电平

//----------------------------------------------------------------
reg uart_rx0,uart_rx1,uart_rx2,uart_rx3; //接收数据寄存器,滤波用
wire neg_uart_rx; //表示数据线接收到下降沿

always @ (posedge clk or negedge rst_n)
if(!rst_n) begin
uart_rx0 <= 1'b0;
uart_rx1 <= 1'b0;
uart_rx2 <= 1'b0;
uart_rx3 <= 1'b0;
end
else begin
uart_rx0 <= uart_rx;
uart_rx1 <= uart_rx0;
uart_rx2 <= uart_rx1;
uart_rx3 <= uart_rx2;
end

//下面的下降沿检测可以滤掉<40ns-80ns的毛刺(包括高脉冲和低脉冲毛刺),
//这里就是用资源换稳定(前提是我们对时间要求不是那么苛刻,因为输入信号打了好几拍)
//(当然我们的有效低脉冲信号肯定是远远大于80ns的)
assign neg_uart_rx = uart_rx3 & uart_rx2 & ~uart_rx1 & ~uart_rx0; //接收到下降沿后neg_uart_rx置高一个时钟周期

//----------------------------------------------------------------
reg bps_start_r;
reg[3:0] num; //移位次数
reg rx_int; //接收数据中断信号,接收到数据期间始终为高电平

always @ (posedge clk or negedge rst_n)
if(!rst_n) begin
bps_start_r <= 1'b0;
rx_int <= 1'b0;
end
else if(neg_uart_rx) begin //接收到串口接收线uart_rx的下降沿标志信号
bps_start_r <= 1'b1; //启动串口准备数据接收
rx_int <= 1'b1; //接收数据中断信号使能
end
else if(num == 4'd9) begin //接收完有用数据信息
bps_start_r <= 1'b0; //数据接收完毕,释放波特率启动信号
rx_int <= 1'b0; //接收数据中断信号关闭
end

assign bps_start = bps_start_r;

//----------------------------------------------------------------
reg[7:0] rx_data_r; //串口接收数据寄存器,保存直至下一个数据来到
reg[7:0] rx_temp_data; //当前接收数据寄存器

always @ (posedge clk or negedge rst_n)
if(!rst_n) begin
rx_temp_data <= 8'd0;
num <= 4'd0;
rx_data_r <= 8'd0;
end
else if(rx_int) begin //接收数据处理
if(clk_bps) begin //读取并保存数据,接收数据为一个起始位,8bit数据,1或2个结束位
num <= num+1'b1;
case (num)
4'd1: rx_temp_data[0] <= uart_rx; //锁存第0bit
4'd2: rx_temp_data[1] <= uart_rx; //锁存第1bit
4'd3: rx_temp_data[2] <= uart_rx; //锁存第2bit
4'd4: rx_temp_data[3] <= uart_rx; //锁存第3bit
4'd5: rx_temp_data[4] <= uart_rx; //锁存第4bit
4'd6: rx_temp_data[5] <= uart_rx; //锁存第5bit
4'd7: rx_temp_data[6] <= uart_rx; //锁存第6bit
4'd8: rx_temp_data[7] <= uart_rx; //锁存第7bit
default: ;
endcase
end
else if(num == 4'd9) begin //我们的标准接收模式下只有1+8+1(2)=11bit的有效数据
num <= 4'd0; //接收到STOP位后结束,num清零
rx_data_r <= rx_temp_data; //把数据锁存到数据寄存器rx_data中
end
end

assign rx_data = rx_data_r;

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
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
`timescale 1ns/1ps
module uart_tb();

reg sys_clk_i; //50MHz时钟信号
reg ext_rst_n; //复位信号,低电平有效
reg uart_rx; // UART接收数据信号
wire uart_tx; // UART发送数据信号

uart_design u1_uart_design(
.sys_clk_i(sys_clk_i), //外部输入50MHz时钟信号
.ext_rst_n(ext_rst_n), //外部输入复位信号,低电平有效
.uart_rx(uart_rx),
.uart_tx(uart_tx)
);

`define UART_BPS_DELAY_NS 1_000_000_000/9600 //串口波特率延时
reg[7:0] uart_data_from_fpga; //接收FPGA发送的串口数据
reg uart_en_from_fpga; //接收FPGA发送的串口数据有效

initial begin
sys_clk_i = 0;
ext_rst_n = 0; //复位中
uart_rx = 1;
uart_data_from_fpga = 0;
uart_en_from_fpga = 0;
#1000;
@(posedge sys_clk_i); #2;
ext_rst_n = 1; //复位结束,正常工作

#100_000; //延时100us

pc_uart_tx_task(8'haa); //发送数据

#(`UART_BPS_DELAY_NS*10);
$finish;
end

always #10 sys_clk_i = ~sys_clk_i; //50MHz时钟产生

//模拟发送一帧串口数据
task pc_uart_tx_task;
input[7:0] value;
begin
uart_rx = 0; //起始位
#(`UART_BPS_DELAY_NS);
uart_rx = value[0];
#(`UART_BPS_DELAY_NS);
uart_rx = value[1];
#(`UART_BPS_DELAY_NS);
uart_rx = value[2];
#(`UART_BPS_DELAY_NS);
uart_rx = value[3];
#(`UART_BPS_DELAY_NS);
uart_rx = value[4];
#(`UART_BPS_DELAY_NS);
uart_rx = value[5];
#(`UART_BPS_DELAY_NS);
uart_rx = value[6];
#(`UART_BPS_DELAY_NS);
uart_rx = value[7];
#(`UART_BPS_DELAY_NS);
uart_rx = 1; //停止位
#(`UART_BPS_DELAY_NS);
uart_rx = 1;
#(`UART_BPS_DELAY_NS);
end
endtask

//接收串口数据帧
always @(negedge uart_tx) begin
#(`UART_BPS_DELAY_NS);
#(`UART_BPS_DELAY_NS/2);
uart_data_from_fpga[0] = uart_tx;
#(`UART_BPS_DELAY_NS);
uart_data_from_fpga[1] = uart_tx;
#(`UART_BPS_DELAY_NS);
uart_data_from_fpga[2] = uart_tx;
#(`UART_BPS_DELAY_NS);
uart_data_from_fpga[3] = uart_tx;
#(`UART_BPS_DELAY_NS);
uart_data_from_fpga[4] = uart_tx;
#(`UART_BPS_DELAY_NS);
uart_data_from_fpga[5] = uart_tx;
#(`UART_BPS_DELAY_NS);
uart_data_from_fpga[6] = uart_tx;
#(`UART_BPS_DELAY_NS);
uart_data_from_fpga[7] = uart_tx;
uart_en_from_fpga = 1;
$display("uart_data_from_fpga = %x\n",uart_data_from_fpga);
#(`UART_BPS_DELAY_NS);
uart_en_from_fpga = 0;
#(`UART_BPS_DELAY_NS/2);
end

endmodule

3.仿真结果

  • RTL视图

image-20230210172236844

  • 仿真波形:

image-20230210201852096

  • 发送数据:

image-20230210200729375


SPI接口DAC驱动控制

  • DAC:数字-模拟转换器
  • 功能描述:DAC 芯片DAC081S101的模拟电压输出直接连接到D5指示灯的正端,它的电压值决定 了D5指示灯的亮暗程度。FPGA工程实例产生一个0-255循环递增的数据,通过SPI接口不断的写入到DAC中,输出的模拟电压可以控制LED的亮暗变化

1.思路

  • 发起一次DAC芯片的写入操作,需要先拉低同步信号SYNC,同时在紧接着的连续 16 个同步时钟SCLK上升沿送出数据DIN,DAC芯片内部则在时钟SCLK的下降沿采样数据DIN

    image-20230213213410699
  • DA芯片通信数据帧格式:FPGA 作为通讯的主机,若要控制DAC芯片DAC081S101完成一次转换,则一共需要传输16位的数据。最高 2 位(DB15-14)不用;DB13-12是当前传输数据的控制位,写 2’b00 时表示正常的DAC数据输出操作;DB11-DB4对应有效数据D7-0;最后4位数据DB3-0也不用

    image-20230213213555843
  • DAC使能转换信号dac_en时序

    image-20230213231306851

  • DAC帧同步信号dac_sync_n时序:

    image-20230213232632326

2.源代码

  • SPI_DAC_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
    module SPI_DAC_design(
    input sys_clk_i, //外部输入50MHz时钟信号
    input ext_rst_n, //外部输入复位信号,低电平有效
    output dac_sync_n, //DAC帧同步信号,低电平有效
    output dac_sclk, //DAC串行时钟信号
    output dac_data //DAC串行数据信号
    );

    wire clk_12m5; //PLL输出12.5MHz时钟
    wire clk_25m; //PLL输出25MHz时钟
    wire clk_50m; //PLL输出50MHz时钟
    wire clk_100m; //PLL输出100MHz时钟
    wire sys_rst_n; //PLL输出的locked信号,作为FPGA内部的复位信号,低电平复位,高电平正常工作

    //-------------------------------------
    //PLL例化
    clk_wiz_0 u1_clk_wiz_0
    (
    // Clock in ports
    .clk_in1(sys_clk_i), // input clk_in1
    // Clock out ports
    .clk_out1(clk_12m5), // output clk_out1
    .clk_out2(clk_25m), // output clk_out2
    .clk_out3(clk_50m), // output clk_out3
    .clk_out4(clk_100m), // output clk_out4
    // Status and control signals
    .reset(!ext_rst_n), // input reset
    .locked(sys_rst_n)); // output locked


    //-------------------------------------
    //产生递增的DAC转换数据
    wire[7:0] dac_out_bus; //DAC输出数据,模块内部自动判断该数据是否发生变化,若前后有变化,则通过IIC接口发起一次DAC转换数据写入操作,建议该数据变化速率不要超过1.5KHz
    dac_dbgene uut_dac_dbgene(
    .clk(clk_25m), //时钟信号
    .rst_n(sys_rst_n), //复位信号,低电平有效
    .dac_out_bus(dac_out_bus) //DAC转换数据
    );

    //-------------------------------------
    //DAC5571的IIC写DA转换数据模块
    dac_controller uut_dac_controller(
    .clk(clk_25m), //时钟信号
    .rst_n(sys_rst_n), //复位信号,低电平有效
    .dac_out_bus(dac_out_bus), //DAC输出数据,模块内部自动判断该数据是否发生变化,若前后有变化,则通过IIC接口发起一次DAC转换数据写入操作,建议该数据变化速率不要超过1.5KHz
    .dac_sync_n(dac_sync_n),
    .dac_sclk(dac_sclk),
    .dac_data(dac_data)
    );

    endmodule
  • dac_dbgene.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
    module dac_dbgene(
    input clk, //时钟信号,25MHz
    input rst_n, //复位信号,低电平有效
    output reg[7:0] dac_out_bus //DAC转换数据
    );

    parameter MAX_CNT_VALUE = 18'd249_999;

    //-------------------------------------------------
    //10ms定时计数
    reg[17:0] cnt; //10ms计数器
    always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
    cnt <= 18'd0;
    else if(cnt < MAX_CNT_VALUE)
    cnt <= cnt+1'b1;
    else
    cnt <= 18'd0;
    end

    //-------------------------------------------------
    //DA转换数据递增
    always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
    dac_out_bus <= 18'd0;
    else if(cnt == MAX_CNT_VALUE)
    dac_out_bus <= dac_out_bus+1'b1;
    end

    endmodule
  • dac_controller.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
    module dac_controller(
    input clk, //时钟信号,25MHz
    input rst_n, //复位信号,低电平有效
    input[7:0] dac_out_bus, //DAC输出数据,模块内部自动判断该数据是否发生变化,若前后有变化,则通过SPI接口发起一次DAC转换数据写入操作,建议该数据变化速率不要超过1.5KHz
    output reg dac_sync_n, //DAC帧同步信号,低电平有效
    output dac_sclk, //DAC串行时钟信号
    output reg dac_data //DAC串行数据信号
    );

    reg[4:0] cstate;
    reg[4:0] nstate;

    //-------------------------------------------------
    //DAC输出时钟产生,clk的4分频,即6.25MHz
    reg[1:0] div;
    always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
    div <= 2'd0;
    else
    div <= div+1'b1;//00 01 10 11
    end
    assign dac_sclk = ~div[1];

    wire div_en = (div == 2'd2);//在时钟sclk由高变低的瞬间置1

    //-------------------------------------------------
    //判断DAC输出数据是否变化,若变化则发起一次SPI数据写入操作
    reg[7:0] dac_out_bus_r; //dac_data缓存寄存器
    reg dac_en; //DAC转换使能信号,高电平有效

    //锁存前一拍的DAC值
    always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
    dac_out_bus_r <= 8'd0;
    else if(div_en)
    dac_out_bus_r <= dac_out_bus;
    end

    //判断是否需要写入新的DAC值,赋值DAC转换使能信号
    always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
    dac_en <= 1'b0;
    else if(div_en) begin
    if(dac_out_bus_r != dac_out_bus)
    dac_en <= 1'b1; //若DAC当前值与新的值不一致,则发起一次写DAC操作
    else
    dac_en <= 1'b0;
    end
    else ;
    end

    //-------------------------------------------------
    parameter DAC_BIT15_14 = 2'b00; //don't care
    parameter DAC_BIT13_12 = 2'b00; //2'b00--normal operation; 2'b01--1kohm to gnd; 2'b10--100kohm to gnd; 2'b11--high impedance
    parameter DAC_BIT3_0 = 4'b0000; //don't care

    wire[15:0] dac_parallel_data = {DAC_BIT15_14,DAC_BIT13_12,dac_out_bus_r,DAC_BIT3_0}; //写入DAC的16位数据

    //-------------------------------------------------
    //DAC芯片SPI时序状态机
    parameter STATE_IDLE = 5'd0;
    parameter STATE_DB15 = 5'd1;
    parameter STATE_DB14 = 5'd2;
    parameter STATE_DB13 = 5'd3;
    parameter STATE_DB12 = 5'd4;
    parameter STATE_DB11 = 5'd5;
    parameter STATE_DB10 = 5'd6;
    parameter STATE_DB09 = 5'd7;
    parameter STATE_DB08 = 5'd8;
    parameter STATE_DB07 = 5'd9;
    parameter STATE_DB06 = 5'd10;
    parameter STATE_DB05 = 5'd11;
    parameter STATE_DB04 = 5'd12;
    parameter STATE_DB03 = 5'd13;
    parameter STATE_DB02 = 5'd14;
    parameter STATE_DB01 = 5'd15;
    parameter STATE_DB00 = 5'd16;
    parameter STATE_DONE = 5'd17;

    //时序逻辑,状态切换
    always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
    cstate <= STATE_IDLE;
    else
    cstate <= nstate;
    end

    //切换下一个状态
    always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
    nstate <= STATE_IDLE;
    else if(div_en) begin
    case(cstate)
    STATE_IDLE: begin
    if(dac_en)
    nstate <= STATE_DB15; //DAC芯片写操作
    else
    nstate <= STATE_IDLE;
    end
    STATE_DB15: nstate <= STATE_DB14;
    STATE_DB14: nstate <= STATE_DB13;
    STATE_DB13: nstate <= STATE_DB12;
    STATE_DB12: nstate <= STATE_DB11;
    STATE_DB11: nstate <= STATE_DB10;
    STATE_DB10: nstate <= STATE_DB09;
    STATE_DB09: nstate <= STATE_DB08;
    STATE_DB08: nstate <= STATE_DB07;
    STATE_DB07: nstate <= STATE_DB06;
    STATE_DB06: nstate <= STATE_DB05;
    STATE_DB05: nstate <= STATE_DB04;
    STATE_DB04: nstate <= STATE_DB03;
    STATE_DB03: nstate <= STATE_DB02;
    STATE_DB02: nstate <= STATE_DB01;
    STATE_DB01: nstate <= STATE_DB00;
    STATE_DB00: nstate <= STATE_DONE;
    STATE_DONE: nstate <= STATE_IDLE;
    default: nstate <= STATE_IDLE;
    endcase
    end
    else ;
    end

    //DAC帧同步信号赋值
    always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
    dac_sync_n <= 1'b1;
    else if((nstate == STATE_IDLE) && div_en && dac_en)
    dac_sync_n <= 1'b0; //开始DAC操作时拉低帧同步信号
    else if((nstate == STATE_DB00) && div_en)
    dac_sync_n <= 1'b1; //结束DAC操作时拉高帧同步信号
    else if((nstate == STATE_IDLE) || (nstate == STATE_DONE))
    dac_sync_n <= 1'b1; //其他状态下,帧同步信号拉高
    else
    dac_sync_n <= 1'b0;
    end

    //并串转换,送16位DAC数据
    always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
    dac_data <= 1'b0;
    else begin
    case(nstate)
    STATE_IDLE: dac_data <= 1'b0;
    STATE_DB15: dac_data <= dac_parallel_data[15];
    STATE_DB14: dac_data <= dac_parallel_data[14];
    STATE_DB13: dac_data <= dac_parallel_data[13];
    STATE_DB12: dac_data <= dac_parallel_data[12];
    STATE_DB11: dac_data <= dac_parallel_data[11];
    STATE_DB10: dac_data <= dac_parallel_data[10];
    STATE_DB09: dac_data <= dac_parallel_data[9];
    STATE_DB08: dac_data <= dac_parallel_data[8];
    STATE_DB07: dac_data <= dac_parallel_data[7];
    STATE_DB06: dac_data <= dac_parallel_data[6];
    STATE_DB05: dac_data <= dac_parallel_data[5];
    STATE_DB04: dac_data <= dac_parallel_data[4];
    STATE_DB03: dac_data <= dac_parallel_data[3];
    STATE_DB02: dac_data <= dac_parallel_data[2];
    STATE_DB01: dac_data <= dac_parallel_data[1];
    STATE_DB00: dac_data <= dac_parallel_data[0];
    STATE_DONE: dac_data <= 1'b0;
    default: dac_data <= 1'b0;
    endcase
    end
    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
`timescale 1ns / 1ps

module SPI_DAC_tb;
reg clk; //外部输入50MHz时钟信号
reg rst_n; //外部输入复位信号,低电平有效
wire dac_sync_n; //DAC帧同步信号,低电平有效
wire dac_sclk; //DAC串行时钟信号
wire dac_data; //DAC串行数据信号

SPI_DAC_design uut_SPI_DAC_design(
.sys_clk_i(clk), //外部输入50MHz时钟信号
.ext_rst_n(rst_n), //外部输入复位信号,低电平有效
.dac_sync_n(dac_sync_n), //DAC帧同步信号,低电平有效
.dac_sclk(dac_sclk), //DAC串行时钟信号
.dac_data(dac_data) //DAC串行数据信号
);

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

always #10 clk = ~clk;

endmodule

4.仿真结果

  • RTL视图

    image-20230213232842741

  • 仿真波形

    image-20230213224507939


IIC接口RTC时间显示控制

  • 功能描述:本实例通过IIC接口定时读取RTC中的时、分、秒寄存器,同时将时、 分、秒数据通过UART发送到PC上的串口调试助手进行实时的显示;此外,PC上的串口调试助手也可以发送“0xaa+time+minute+second+0x55”这样的16进制字符串来重设RTC的时、 分、秒寄存器,重设后立即生效

1.思路

  • IIC协议:I2C通信中只涉及两条信号线,即时钟信号SCL和数据信号SDA。时钟信号为高电平时均可锁存数据(即时钟信号上升沿到下降沿之间)。当时钟信号SCL为高电平时,如果把数据信号SDA从高电平拉到低电平,则表示通信开始;如果把数据信号SDA从低电平拉到高电平,则表示通信结束。

    image-20230214225300750

  • 器件地址(DEVICE ADDRESS):最低位R/W表示读或者写状态,1表示读,0表示写;A0一般是由芯片的引脚决定的,PCF8563芯片的A0引脚和INT引脚公用,电路中做了上拉,因此为1。其它几位的取值由芯片定义好,查阅芯片手册可以得到。

    image-20230214225608426

2.源代码

Reference

欢迎来到ssy的世界