实验七 串行密码锁实验
实验目的
- 学习使用状态机来控制电路工作,在不同的状态下完成相应的功能;
- 进一步掌握时序逻辑电路的基本分析和设计方法;
实验内容
设计一个四位 16 进制串行电子密码锁,其具体功能如下:
(1)设置密码:用户可串行设置四位 16 进制密码;
(2)验证密码:用户串行输入密码,如密码符合则点亮开锁灯,若不符合则点亮错误灯;
设计需求
- 设计一个四位的串行密码锁,通过模式开关来决定密码锁是设置密码还是验证密码,由密码输入开关输入密码,通过时钟上沿送入密码锁,这样串行设置或验证四位密码,复位按键下降沿可以随时复位密码锁状态。
- 使用时钟模块上的复位开关
CLK
作为手动时钟输入,复位开关RST
作为复位输入。 - 使用一个开关模块上的四个开关作密码锁的四位密码输入
Code[3:0]
;另一个开关模块上的一个开关作为模式选择Mode
。 - 使用开关模块上的发光二极管作为开锁指示灯
Unlock
和错误指示灯Err
。
设计框图
flowchart LR
CLK[复位开关CLK]:::periph -- CLK ---> lock;
RST[复位开关RST]:::periph -- RST ---> lock;
Code[拨动开关Code]:::periph -- Code ---> lock;
Mode[拨动开关Mode]:::periph -- Mode ---> lock;
subgraph FPGA
direction BT
lock[密码锁Lock]:::seq;
end
lock -- Unlock --> led1[发光二极管]:::periph;
lock -- Err --> led2[发光二极管]:::periph;
classDef comb fill:#e8f5e9,stroke:#43a047; %% green
classDef seq fill:#ffecb3,stroke:#ffa000; %% orange
classDef periph fill:#e3f2fd,stroke:#2196f3; %% blue
linkStyle default opacity:1.0
原理图
串行密码锁
本实验中的密码锁为串行密码锁,无论是密码的设置和验证都是采用串行的方式。验证密码时用户将密码一个个按顺序输入,如果密码符合则密码锁被打开,否则需要重新输入。 在基本功能要求下,密码锁有两种模式,一种为设置密码模式,另外一种为验证密码模式,这两种模式下密码锁的具体工作流程如下图所示:
从上面的流程图可以看出,每次进入设置或验证模式都需要进行一次复位,然后开始真正进入相应工作流程,每次输入一位密码后,都需要通过 CLK
来输入当前密码位。
在验证模式下,当四位密码都输入完成后再统一验证密码是否正确,在验证过程中,如果 RST
按下则重新开始验证。
在设计的过程中要充分考虑各种情况,画出相应的状态转移图,如密码验证过程中密码锁的状态机参考如下图所示:
flowchart LR
ANY(( 任意状态 )) -- RST ---> S0((    S0    ));
S0 -- 保存第1位密码 ---> S1((    S1    ));
S1 -- 保存第2位密码 ---> S2((    S2    ));
S2 -- 保存第3位密码 ---> S3((    S3    ));
S3 -- 保存第4位密码 ---> S4((    S4    ));
linkStyle default opacity:1.0
状态 S0:RST 后进入,CLK 保存第 1 位密码,转 S1
状态 S1:CLK 保存第 2 位密码,转 S2
状态 S2:CLK 保存第 3 位密码,转 S3
状态 S3:CLK 保存第 4 位密码,转 S4
状态 S4:检查密码是否正确
注意
本状态转移图与实验指导书并不一致,密码锁的工作流程并不局限于这个流程,可自由设计,自洽即可。
下面给出密码验证部分的代码,假定密码为 16'h1234,仅供参考
module Lock(
input wire[3:0] Code,
input wire Mode,
input wire CLK,
input wire RST,
output reg Unlock,
output reg Err
);
reg [15:0]token;
parameter S0=3'b000,S1=3'b001,S2=3'b011,S3=3'b111,S4=3'b110;
parameter password=16'h1234;
reg[2:0] state, next_state;
always_ff @(posedge CLK or posedge RST)
begin
if (RST)
state <= S0;
else begin
case(state)
S0: token[3:0] <= Code;
S1: token[7:4] <= Code;
S2: token[11:8] <= Code;
S3: token[15:12] <= Code;
default: ;
endcase
state<=next_state;
end
end
always_comb begin
case(state)
S0: next_state<=S1;
S1: next_state<=S2;
S2: next_state<=S3;
S3: next_state<=S4;
S4: next_state<=S4;
default: next_state<=S0;
endcase
end
always_comb begin
if (state == S4 ) begin
if (token == password) begin
Unlock = 1;
Err = 0;
end
else begin
Unlock = 0;
Err = 1;
end
end else begin
Unlock = 0;
Err = 0;
end
end
endmodule
管脚绑定
使用可编程模块上的接插孔连接开关和发光数码管,其中 CLK 作为时钟输入 CLK
,IO0 作为复位输入 RST
,IO1 ~ IO4 作为密码输入 Code[3:0]
,IO11 作为模式选择 Mode
,IO16 和 IO17 分别作为开锁指示 Unlock
和错误指示 Err
。
具体约束文件如下:
#CLK input
set_property -dict {PACKAGE_PIN J19 IOSTANDARD LVCMOS33} [get_ports CLK]; #CLK接插孔
#RST input
set_property -dict {PACKAGE_PIN K18 IOSTANDARD LVCMOS33} [get_ports RST]; #IO0接插孔
# Code input
set_property -dict {PACKAGE_PIN M21 IOSTANDARD LVCMOS33} [get_ports Code[3]]; #IO1接插孔
set_property -dict {PACKAGE_PIN N20 IOSTANDARD LVCMOS33} [get_ports Code[2]]; #IO2接插孔
set_property -dict {PACKAGE_PIN N22 IOSTANDARD LVCMOS33} [get_ports Code[1]]; #IO3接插孔
set_property -dict {PACKAGE_PIN P21 IOSTANDARD LVCMOS33} [get_ports Code[0]]; #IO4接插孔
# Mode input
set_property -dict {PACKAGE_PIN W21 IOSTANDARD LVCMOS33} [get_ports Mode]; #IO11接插孔
# Unlock output
set_property -dict {PACKAGE_PIN AA18 IOSTANDARD LVCMOS33} [get_ports Unlock]; #IO16接插孔
# Err output
set_property -dict {PACKAGE_PIN AB18 IOSTANDARD LVCMOS33} [get_ports Err]; #IO17接插孔
# required if touch button used as manual clock source
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets CLK_IBUF]
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
实验步骤
- 使用 vivado2019.2 新建一个工程,编写 system verilog 代码,并进行综合;
- 进行功能仿真;
- 使用时钟模块,开关模块和可编程模块,进行电路连接;
- 管脚绑定,编译并下载,观察实验结果。
提高要求
- 密码预置:为管理员创建万用密码以备管理。
- 系统报警:开锁三次失败后点亮报警灯,并锁定密码锁,只有输入管理员密码才可开锁,并解除报警。
思考题
说明
这个思考题是助教加的,主要目的是希望大家可以理解数字逻辑设计的一些基本内容。
不强制回答,但我相信这对大部分人来说有帮助。
-
System Verilog 中事件的发生顺序
请阅读下面这段 System Verilog 代码:
typedef enum {STATE_0, STATE_1, STATE_2, STATE_3} state_type; state_type state_r; state_type tmp_state_w, tmp_state_r; always_ff @(posedge clk) begin if(rst) begin state_r <= STATE_0; end else begin case(state_r) STATE_0: begin state_r <= STATE_1; end STATE_1: begin state_r <= STATE_2; end STATE_2: begin state_r <= STATE_3; end STATE_3: begin state_r <= STATE_0; end endcase end end always_ff @(posedge clk) begin if(rst) begin tmp_state_r <= STATE_0; end else begin tmp_state_r <= state_r; // TODO: state_r 是新的还是旧的? end end always_comb begin tmp_state_w = state_r; end
假定当前 clk 上升沿过后,state_r 为 STATE_1,tmp_state_r 为 STATE_0,rst 一直为低。
请画出这之后三个周期的波形。跟仿真结果作比较。
根据结果,对以下的事件进行排序:
clk 上升沿到来 -> (1) -> (2) -> (3) -> clk 上升沿到来
分别填入:组合逻辑计算完毕,寄存器输入准备完成,寄存器值切换。
简要画出上述 Verilog 代码的电路图,并对你填入的顺序进行解释。
-
手动 v.s. 自动
在本实验中,我们实现了手动控制的串行密码锁。在每次切换模式输入的时候,我们需要重新按下 RST 来保证逻辑正确。
那有没有一种方法能够让电路自己检测模式的变化,自动进行 RST 呢?
一种很朴素的想法是,在代码中直接响应各个模式开关的边沿。这意味着你不仅要向设计引入多个时钟,同时还要响应信号的双边沿,这肯定是不可行的。
那有没有什么信号是一直在变化的呢?对,我们可以将所有响应按钮上升沿的逻辑,全部转换为响应时钟,而将 clk 和 rst 当作两个电平信号进行使用。
这需要我们解决两个问题:
- clk 被按下后,会保持多个周期。而我们只希望响应 clk 的上升沿。如何处理?
- 如何通过时钟监测模式输入的变化?
请尝试解决这两个问题,给出简要的实现逻辑。
实验报告
- 整理出完整、清晰的代码,并详细添加注释说明电路的工作原理。
- 给出软件仿真的结果。
- 说明电路功能测试的结果,并与软件仿真的结果相比较。
- 总结调试中所遇到的问题及解决方法。