1、实验目的
上位机通过串口发送8位密码给逻辑派Z1开发板、开发板使用8个拨码开关输入密码,并使用一个按键进行验证。若输入密码与上位机发送的密码相同,则点亮8个led灯。若不同则不点亮led灯,同时支持上位机再次更改密码。
2、实验原理
上位机通过串口发送密码给FPGA。FPGA将密码解析并校验,如果接收成功则将8位密码寄存,在按键信号有效时将其与拨码开关8位数据进行比对。若比对成功则点亮led灯,若比对不成功则不点亮led灯。
3、代码设计
串口密码机top_uart_locker顶层代码如下:
module top_uart_locker(
input sys_clk ,//系统时钟,50MHZ
input sys_rst_n ,//系统复位,低电平有效
input key_in ,//按键用来触发密码验证
input [ 7: 0] switch ,//八个开关用于输入密码
output [ 7: 0] led ,//密码验证通过点亮呼吸灯
input uart_rxd ,//串口接收的数据
output uart_txd //串口发送的数据
);
//parameter define
parameter PACKET_HEAD = 8'h55 ; //定义数据包头
parameter ERR_HEAD = 8'hE0 ; //包头检测错误
parameter ERR_CHECKSUM = 8'hE1 ; //校验错误
parameter PARSE_RIGHT = 8'h00 ; //数据包解析正确
//wire define
wire [ 7: 0] uart_rx_data ;//UART接收端接收的数据
wire rx_finish ;//UART接收一字节数据完成标志
wire parse_done ;//解包完成标志
wire [ 7: 0] parse_result ;//解包后的结果,8'h00:解析正确 8'hEx:解析错误
wire uart_tx_en ;//UART发送端使能信号
wire [ 7: 0] uart_tx_data ;//UART发送端的数据
wire uart_tx_busy ;//UART发送端忙率标志信号
wire packet_tx_done ;//一包数据全部发送完成标志
wire key_flag ;//消抖后的按键信号
wire [ 7: 0] packet_data ;//密码数据
uart_rx u_uart_rx(
.clk (sys_clk ),
.rst_n (sys_rst_n ),
.uart_rxd (uart_rxd ),// UART接收端口
.uart_rx_finish (rx_finish ),// 一帧数据接收完成
.uart_rx_data (uart_rx_data ) // 解析后的数据
);
//例化数据包解析模块
packet_decode #(
.PACKET_HEAD (PACKET_HEAD ),
.ERR_HEAD (ERR_HEAD ),
.ERR_CHECKSUM (ERR_CHECKSUM ),
.PARSE_RIGHT (PARSE_RIGHT ) )
u_packet_decode(
.clk (sys_clk ),
.rst_n (sys_rst_n ),
.uart_rx_data (uart_rx_data ),
.uart_rx_done (rx_finish ),
.packet_tx_done (packet_tx_done ),
.parse_done (parse_done ),
.parse_result (parse_result ),
.packet_data (packet_data ) //密码数据
);
locker u_locker(
.clk (sys_clk ),
.rst_n (sys_rst_n ),
.key (key_flag ),
.switch (switch ),
.packet_data (packet_data ),
.led (led )
);
//例化数据包打包模块
packet_code #(
.PACKET_HEAD (PACKET_HEAD ),
.PARSE_RIGHT (PARSE_RIGHT ) )
u_packet_code(
.clk (sys_clk ),
.rst_n (sys_rst_n ),
.uart_tx_busy (uart_tx_busy ),
.parse_done (parse_done ),
.parse_result (parse_result ),
.packet_tx_done (packet_tx_done ),
.uart_tx_req (uart_tx_en ),
.uart_tx_data (uart_tx_data )
);
uart_tx u_uart_tx(
.clk (sys_clk ),
.rst_n (sys_rst_n ),
.uart_tx_req (uart_tx_en ),// 发送使能请求
.uart_tx_data (uart_tx_data ),// UART要发送的数据
.uart_txd (uart_txd ),// UART发送端口
.uart_tx_busy (uart_tx_busy ) // 发送忙
);
key_filter u_key_filter(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.key_in (key_in ),
.key_flag (key_flag )
);
endmodule
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
顶层代码例化了串口收发模块(uart_rx/uart_tx)、按键消抖模块(key_filter)、密码机模块(locker)、串口数据包解析模块(packet_decode)、串口数据包打包模块(packet_code)。具体模块的功能在下文进行介绍。
串口数据包解析模块(packet_decode):
module packet_decode(
input clk ,//时钟
input rst_n ,//复位,低电平有效
input [ 7: 0] uart_rx_data ,//UART接收数据完成信号
input uart_rx_done ,//UART接收的数据
input packet_tx_done ,//一包数据全部发送完成标志
output reg parse_done ,//解包完成标志
output reg [ 7: 0] parse_result ,//解包后的结果,8'h00:解析正确 8'hEx:解析错误
output reg [ 7: 0] packet_data
);
//parameter define
parameter PACKET_HEAD = 8'h55 ; //定义数据包头
parameter ERR_HEAD = 8'hE0 ; //包头检测错误
parameter ERR_CHECKSUM = 8'hE1 ; //校验错误
parameter PARSE_RIGHT = 8'h00 ; //数据包解析正确
localparam rec_head = 6'b00_0001; //解析包头
localparam rec_data = 6'b00_1000; //解析数据
localparam rec_check_sum = 6'b01_0000; //校验
localparam rec_end = 6'b10_0000; //结束状态
reg [ 5: 0] cur_state ;//状态机的现态
reg [ 5: 0] next_state ;//状态机的次态
reg [ 7: 0] rec_data_c ;//接收到的数据
reg [ 7: 0] rec_data_t ;//接收的数据寄存一拍
reg [ 7: 0] checksum ;//包的累加校验和
reg skip_en ;//控制状态跳转使能信号
//同步时序模块
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cur_state <= rec_head;
else
cur_state <= next_state;
end
//转态转换条件
always@(*)begin
next_state = rec_head;
case(cur_state)
rec_head:begin
if(skip_en)
next_state = rec_data;
else if(parse_result == ERR_HEAD)
next_state = rec_end;
else
next_state = rec_head;
end
rec_data:begin
if(skip_en)
next_state = rec_check_sum;
else
next_state = rec_data;
end
rec_check_sum:begin
if(skip_en)
next_state = rec_end;
else if( parse_result == ERR_CHECKSUM)
next_state = rec_end;
else
next_state = rec_check_sum;
end
rec_end :begin
if(packet_tx_done)
next_state = rec_head;
else
next_state = rec_end;
end
default:
next_state = rec_head;
endcase
end
//时序电路描述状态输出
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
parse_result <= 8'b0;
parse_done <= 1'b0;
skip_en <= 1'b0;
rec_data_c <= 8'b0;
rec_data_t <= 8'b0;
checksum <= 8'b0;
end
else begin
skip_en <= 1'b0;
parse_done <= 1'b0;
case(cur_state)
rec_head: begin //检测包头
if(uart_rx_done)
if(uart_rx_data == PACKET_HEAD)begin
skip_en <= 1'b1;
checksum <= PACKET_HEAD;
end
else begin
parse_result <= ERR_HEAD; //包头检测错误
parse_done <= 1'b1;
end
end
rec_data: begin //接收有效数据与累加
if(uart_rx_done)begin
rec_data_c <= uart_rx_data;
rec_data_t <= rec_data;
checksum <= uart_rx_data + checksum ;
skip_en <= 1'b1;
end
else
checksum <= checksum;
end
rec_check_sum:begin //校验
if(uart_rx_done)begin
if(checksum == uart_rx_data)begin
skip_en <= 1'b1;
parse_done <= 1'b1; //接收完成
parse_result <= PARSE_RIGHT; //接收成功
end
else begin
parse_result <= ERR_CHECKSUM; //校验错误
parse_done <= 1'b1;
end
end
end
rec_end:begin
if(packet_tx_done)begin
skip_en <= 1'b0 ;
parse_done <= 1'b0 ;
parse_result <= 8'b0 ;
checksum <= 8'd0 ;
end
end
default: ;
endcase
end
end
//接收完成后处理数据
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
packet_data <= 8'b0;
end
else if((parse_result == PARSE_RIGHT) && (parse_done == 1'b1)) begin
packet_data <= rec_data_c;
end
end
endmodule
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
为了保证串口数据发送可靠,我们使用了一个简单的数据包。其格式包括包头、密码、校验三部分。其中包头固定为16进制的55、密码也为16进制的XX。校验为包头与密码的和取最后八位,例如如果密码是16进制的FF,那么校验位则为54。因为16进制的55+FF=154,取最后八位即为54。我们还引入了校验错误的机制,如果上位机发送的数据包头不为55,则返回错误值E0。如果检测到校验位错误,则返回错误值E1。若接收成功则返回校验值00。这些信息会在串口数据打包模块打包发送回上位机,反馈解包结果。
串口数据包打包模块(packet_code):
module packet_code(
input clk ,
input rst_n ,
input uart_tx_busy ,//发串口发送模块发送忙信号
input parse_done ,//包解析完成标志
input [ 7: 0] parse_result ,//解包后的结果,8'h00:解析正确 8'hEx:解析错误
output reg packet_tx_done ,//数据全部发送完成标志
output reg uart_tx_req ,//串口发送使能
output reg [ 7: 0] uart_tx_data //串口发送数据
);
//parameter define
parameter PACKET_HEAD = 8'h55 ; //定义数据包头
parameter PARSE_RIGHT = 8'h00 ; //数据包解析正确
parameter PACKET_LEN = 8'd3 ; //定义数据包长度
//获取下降沿
reg tx_busy_d0 ;
reg tx_busy_d1 ;
reg tx_req ;//发送使能信号
reg [ 7: 0] data_send_cnt ;//发送数据计数器
reg [ 39: 0] uart_packet_data ;//定义发送包数据长度
wire tx_busy_fall ;//uart发送忙信号的下降沿
//获取下降沿//也作为一个字节数据发送完成标志
assign tx_busy_fall = tx_busy_d1 & (~tx_busy_d0);
//采下降沿,寄存两拍数据
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_busy_d0 <= 1'b0;
tx_busy_d1 <= 1'b0;
end
else begin
tx_busy_d0 <= uart_tx_busy;
tx_busy_d1 <= tx_busy_d0;
end
end
//发送数据计数器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
data_send_cnt <= 8'b0;
else if(tx_busy_fall) begin
if (data_send_cnt == (PACKET_LEN - 8'd1))
data_send_cnt <= 8'b0;
else
data_send_cnt <= data_send_cnt + 8'b1;
end
end
// 当数据全部发送完成,packet_tx_done拉高
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
packet_tx_done <= 1'b0;
else if(data_send_cnt == (PACKET_LEN - 8'd1) && tx_busy_fall )
packet_tx_done <= 1'b1;
else
packet_tx_done <= 1'b0;
end
//字节数据发送完成拉高发送使能信号
always@(posedge clk or negedge rst_n )begin
if(!rst_n)
tx_req <= 1'b0 ;
else if(tx_busy_fall && (data_send_cnt < (PACKET_LEN - 8'd1)))
tx_req <= 1'b1;
else
tx_req <= 1'b0;
end
//判断向上位机发送数据信号,并在串口发送模块空闲时给出串口发送使能信号
always@(posedge clk or negedge rst_n )begin
if(!rst_n)
uart_tx_req <= 1'b0 ;
else if(parse_done || tx_req)
uart_tx_req <= 1'b1;
else
uart_tx_req <= 1'b0;
end
//打包发送给上位机的数据包
always @(posedge clk or negedge rst_n ) begin
if(!rst_n)
uart_packet_data <= {32'd0,PACKET_HEAD};
else if(parse_done)begin
uart_packet_data[15:8] <= parse_result;
uart_packet_data[23:16] <= PACKET_HEAD + parse_result;
end
end
//发送数据
always @(posedge clk or negedge rst_n ) begin
if(!rst_n)
uart_tx_data <= 8'b0;
else begin
case(data_send_cnt)
8'd0 : uart_tx_data <= uart_packet_data[7:0];
8'd1 : uart_tx_data <= uart_packet_data[15:8];
8'd2 : uart_tx_data <= uart_packet_data[23:16];
default : uart_tx_data <= 8'b0;
endcase
end
end
endmodule
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
串口数据包打包模块将数据解包信息反馈给上位机,通过uart_tx模块发送。如果密码解析正确,则发送16进制的550055。其格式与串口数据解包模块格式相同。55代表包头,其值固定为55。00代表密码解析正确,而最后一个55是校验,用来检验本次发送是否正确。举个例子,如果包头检测错误,则返回的信息为55E035。如果密码校验信息错误,则返回的信息为55E136。
密码锁(locker)模块如下:
module locker(
//input
input clk , //系统时钟,50MHZ
input rst_n , //系统复位,低电平有效
input key , //确认按钮
input [7:0] switch , //八个拨码开关
input [7:0] packet_data, //密码
//output
output reg [7:0] led //LED灯
);
reg [7:0] switch_r;
reg [7:0] packet_data_r;
wire [7:0] led_out;
reg led_en;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
led_en <= 1'b0;
end
else if(key)begin
if(switch_r == packet_data_r)
led_en <= 1'b1 ;
else
led_en <= 1'b0;
end
end
//数据寄存
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
switch_r <= 8'd0;
packet_data_r <= 8'd0;
end
else begin
switch_r <= switch ;
packet_data_r <= packet_data;
end
end
//按键确认密码,如果密码正确则点亮led灯
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
led <= 8'b0;
else if(led_en)
led <= led_out;
else
led <= 8'b0;
end
breath_led#(
.CNT_1US_MAX (6'd49 ),
.CNT_1MS_MAX (10'd999 ),
.CNT_1S_MAX (10'd999 )
)
u_breath_led(
.sys_clk (clk ), // 系统时钟50MHz
.sys_rst_n (rst_n ), // 全局复位
.led_out (led_out ) // 输出信号,控制LED灯
);
endmodule
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
其功能较为简单,利用消抖后的按键信号为触发条件,将8位拨码开关信号与串口数据包解包模块传出的密码数据packet_data进行比对。若比对成功则将呼吸灯信号接入8个led灯,将led灯点亮。
测试代码如下:
`timescale 1ns / 1ps
module tb_top_uart_locker();
//parameter define
parameter CLK_PERIOD = 20 ;//时钟周期为20ns
// reg define
reg sys_clk ;
reg sys_rst_n ;
reg [7:0] uart_rx_data;
reg uart_rx_done;
//wire define
//初始化设置
initial begin
sys_clk <= 1'b0 ;
sys_rst_n <= 1'b0 ;
uart_rx_data <= 8'h00;
uart_rx_done <= 1'b0 ;
#20
sys_rst_n <= 1'b1 ;
//测试
#150000
uart_rx_data <= 8'h55;
uart_rx_done <= 1'b1 ;
#20
uart_rx_done <= 1'b0;
#150000
uart_rx_data <= 8'h56;
uart_rx_done <= 1'b1 ;
#20
uart_rx_done <= 1'b0 ;
#150000
uart_rx_data <= 8'hAB;
uart_rx_done <= 1'b1 ;
#20
uart_rx_done <= 1'b0 ;
#150000
//测试
#150000
uart_rx_data <= 8'h55;
uart_rx_done <= 1'b1 ;
#20
uart_rx_done <= 1'b0;
#150000
uart_rx_data <= 8'h57;
uart_rx_done <= 1'b1 ;
#20
uart_rx_done <= 1'b0 ;
#150000
uart_rx_data <= 8'hAC;
uart_rx_done <= 1'b1 ;
#20
uart_rx_done <= 1'b0 ;
#150000
//测试
#150000
uart_rx_data <= 8'h50;
uart_rx_done <= 1'b1 ;
#20
uart_rx_done <= 1'b0;
#150000
uart_rx_data <= 8'h57;
uart_rx_done <= 1'b1 ;
#20
uart_rx_done <= 1'b0 ;
#150000
uart_rx_data <= 8'hAC;
uart_rx_done <= 1'b1 ;
#20
uart_rx_done <= 1'b0 ;
#150000
//测试
#150000
uart_rx_data <= 8'h55;
uart_rx_done <= 1'b1 ;
#20
uart_rx_done <= 1'b0;
#150000
uart_rx_data <= 8'h57;
uart_rx_done <= 1'b1 ;
#20
uart_rx_done <= 1'b0 ;
#150000
uart_rx_data <= 8'hAB;
uart_rx_done <= 1'b1 ;
#20
uart_rx_done <= 1'b0 ;
end
//50Mhz的时钟,周期则为1/50Mhz=20ns,所以每10ns,电平取反一次
always #(CLK_PERIOD/2) sys_clk = ~sys_clk;
//紫光全局复位原语,仿真需要添加
GTP_GRS GRS_INST(
.GRS_N(1'b1)
);
top_uart_locker u_top_uart_locker(
//input
.sys_clk (sys_clk ), //外部50Mhn时钟
.sys_rst_n (sys_rst_n ), //外部复位信号,低有效
.uart_rxd (uart_txd ), //UART接收端口
//output
.uart_txd () //UART发送端口
);
uart_tx u_uart_tx(
.clk (sys_clk ),
.rst_n (sys_rst_n ),
.uart_tx_req (uart_rx_done ),// 发送使能请求
.uart_tx_data (uart_rx_data ),// UART要发送的数据
.uart_txd (uart_txd ),// UART发送端口
.uart_tx_busy ( ) // 发送忙
);
endmodule
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
代码中通过timescale 1ns / 1ns设置时间单位和精度为1纳秒。定义了串口收发信号、时钟、复位信号作为激励信号。使用initial语句模拟产生密码数据。例化串口发送模块uart_tx模块将数据包进行发送。同时例化顶层模块将数据包进行接收,我们主要观察串口解包模块与组包模块的功能是否正确。至于简单的模块我们直接上板验证即可。
4、实验现象
结合仿真波形分析,我们模拟发送了四组数据,分别是5556ab(正确的数据包)、5557ac(正确的数据包)、5057ac(包头错误)、5557ab(校验错误)。可以发现发送正确数据时packet_data解析出正确的密码数据56、57。但是发送错误的数据时parse_result输出错误信号E0、E1。同时在组包模块uart_packet_data发送了四次数据包,分别是550055、550055、35e055、36e155。发送550055代表密码解析正确,发送错误码e0代表包头检测错误,发送错误码e1代表校验错误。仿真图中因为串口模块从低位发送,所以这里反序输出了数据包,对功能没有影响。
我们将程序下载进入板卡,将板卡通过扩展io口连接上位机,打开串口调试助手,设置串口波特率为115200,16进制发送,16进制接收。发送数据55FF54。
可以看到板卡返回数据550055,代表密码解析成功。我们将板卡拨码开关全部打开(拨至led灯一侧)。然后按下按键key1进行校验,可以发现led被点亮。
我们继续通过串口发送信息50FF54(包头错误)、55FF55(校验错误)。发现板卡返回正确错误码,至此串口密码锁功能验证成功。
上位机验证实验结果图1
上位机验证实验结果图2