跳转至

实验七 串行密码锁实验

实验目的

  1. 学习使用状态机来控制电路工作,在不同的状态下完成相应的功能;
  2. 进一步掌握时序逻辑电路的基本分析和设计方法;

实验内容

设计一个四位 16 进制串行电子密码锁,其具体功能如下:

(1)设置密码:用户可串行设置四位 16 进制密码;

(2)验证密码:用户串行输入密码,如密码符合则点亮开锁灯,若不符合则点亮错误灯;

设计需求

  1. 设计一个四位的串行密码锁,通过模式开关来决定密码锁是设置密码还是验证密码,由密码输入开关输入密码,通过时钟上沿送入密码锁,这样串行设置或验证四位密码,复位按键下降沿可以随时复位密码锁状态。
  2. 使用时钟模块上的复位开关 CLK 作为手动时钟输入,复位开关 RST 作为复位输入。
  3. 使用一个开关模块上的四个开关作密码锁的四位密码输入 Code[3:0];另一个开关模块上的一个开关作为模式选择 Mode
  4. 使用开关模块上的发光二极管作为开锁指示灯 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((&nbsp &nbsp S0 &nbsp &nbsp));

  S0 -- 保存第1位密码 ---> S1((&nbsp &nbsp S1 &nbsp &nbsp));
  S1 -- 保存第2位密码 ---> S2((&nbsp &nbsp S2 &nbsp &nbsp));
  S2 -- 保存第3位密码 ---> S3((&nbsp &nbsp S3 &nbsp &nbsp));
  S3 -- 保存第4位密码 ---> S4((&nbsp &nbsp S4 &nbsp &nbsp));

  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]

实验步骤

  1. 使用 vivado2019.2 新建一个工程,编写 system verilog 代码,并进行综合;
  2. 进行功能仿真;
  3. 使用时钟模块,开关模块和可编程模块,进行电路连接;
  4. 管脚绑定,编译并下载,观察实验结果。

提高要求

  1. 密码预置:为管理员创建万用密码以备管理。
  2. 系统报警:开锁三次失败后点亮报警灯,并锁定密码锁,只有输入管理员密码才可开锁,并解除报警。

思考题

说明

这个思考题是助教加的,主要目的是希望大家可以理解数字逻辑设计的一些基本内容。

不强制回答,但我相信这对大部分人来说有帮助。

  1. 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 代码的电路图,并对你填入的顺序进行解释。

  2. 手动 v.s. 自动

    在本实验中,我们实现了手动控制的串行密码锁。在每次切换模式输入的时候,我们需要重新按下 RST 来保证逻辑正确。

    那有没有一种方法能够让电路自己检测模式的变化,自动进行 RST 呢?

    一种很朴素的想法是,在代码中直接响应各个模式开关的边沿。这意味着你不仅要向设计引入多个时钟,同时还要响应信号的双边沿,这肯定是不可行的。

    那有没有什么信号是一直在变化的呢?对,我们可以将所有响应按钮上升沿的逻辑,全部转换为响应时钟,而将 clk 和 rst 当作两个电平信号进行使用。

    这需要我们解决两个问题:

    1. clk 被按下后,会保持多个周期。而我们只希望响应 clk 的上升沿。如何处理?
    2. 如何通过时钟监测模式输入的变化?

    请尝试解决这两个问题,给出简要的实现逻辑。

实验报告

  1. 整理出完整、清晰的代码,并详细添加注释说明电路的工作原理。
  2. 给出软件仿真的结果。
  3. 说明电路功能测试的结果,并与软件仿真的结果相比较。
  4. 总结调试中所遇到的问题及解决方法。

最后更新: 2024年5月31日
作者:cuibst (29.07%), 李山山 (64.71%), Jiajie Chen (3.46%), liuh22 (2.77%)