最近在搞一个课程设计,题目很简单,用FPGA做一个密码锁。开发过程中遇到了一系列的错误,现在被一一解决。就通过写几篇博文来记录一下开发过程吧
首先在构思框架时目标就很明确。整个系统至少要有这么几个模块:密码锁驱动、键盘驱动、数码管驱动;这是最最最基本的,在完成这几个模块后可以进行功能和外设的扩展。
这一篇就记录密码锁的开发。
首先,构建密码锁框架时,考虑到这么几个需求:
1、密码锁需要与键盘构建接口,则需要有4位的数据接口和一个触发接口
2、密码锁需要与数码管构建接口,这个设计默认密码位数是6位的,所以需要6个4位的数据接口
有这么个思路就很好构建代码框架了
module lock_dri(
//================System Signal================
input clk ,
input rst_n ,
//================Interface====================
output reg [3:0] num0 ,
output reg [3:0] num1 ,
output reg [3:0] num2 ,
output reg [3:0] num3 ,
output reg [3:0] num4 ,
output reg [3:0] num5 ,
input key_trig ,
input [3:0] num ,
output flag_lock ,
);
接下来,分析如下需求:
1、密码是6位10进制数,则输密码部分就需要对矩阵键盘进行6次采样,然后在进入输入模式前还需要一个Enter命令;再加上密码判断,成功解锁和解锁失败这几种状态,就需要设计一个至少10个状态的状态机
下面是状态定义,定义在全局文件里了
`define S_LOCK 10'b00_0000_0001
`define S_NUM0_INPUT 10'b00_0000_0010
`define S_NUM1_INPUT 10'b00_0000_0100
`define S_NUM2_INPUT 10'b00_0000_1000
`define S_NUM3_INPUT 10'b00_0001_0000
`define S_NUM4_INPUT 10'b00_0010_0000
`define S_NUM5_INPUT 10'b00_0100_0000
`define S_CHECK 10'b00_1000_0000
`define S_SUCESS 10'b01_0000_0000
`define S_FAIL 10'b10_0000_0000
接下来,先考虑输密码的行为
这个行为实际上就是键盘的按键行为触发密码锁模块对键盘输出数据的采样并锁存,最终和设定密码进行比较的行为,因此首先需要考虑对键触发的捕获,方法如下:
我们是将键盘和密码锁模块以4位数据线和一个触发脉冲作为接口
也就是这两个信号,再用一个寄存器锁存num
input key_trig ,
input [3:0] num ,
捕获方法很简单
reg key_trig_t0 ;
reg key_trig_t1 ;
reg key_trig_t2 ;
reg key_trig_t3 ;
wire trig_key_trig ;
wire trig_key_trig_t1 ;
//key_trig
always @(posedge clk)begin
key_trig_t0 <= key_trig;
key_trig_t1 <= key_trig_t0;
key_trig_t2 <= key_trig_t1;
key_trig_t3 <= key_trig_t2;
end
assign trig_key_trig = key_trig_t1&(~key_trig_t2) ;
assign trig_key_trig_t1 = key_trig_t2&(~key_trig_t3);
将脉冲信号打3拍,然后通过这种写法可以得到触发的上升沿。只要在触发上升沿到来时采样4位数据就可以,而这里为了数据稳定,num信号的采样提前于状态机的状态转移,因此在trig_key_trig高时采样
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)
num_reg <= 'd0;
else if(trig_key_trig==1'b1)
num_reg <= num;
end
接下来就可以考虑写状态机了,我们采用比较规范的三段式状态机,我们首先声明一下几个宏定义,可以少些一些代码
//指令集
`define ENTER 4'ha
`define CLEAR 4'hb
`define LOCK 4'hc
`define BACK 4'hd
`define PASSWORD_SET 4'he
localparam CMD_ENTER = (trig_key_trig_t1==1'b1&&num_reg==`ENTER);
三段式状态机代码
//第一段
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)
cur_state <= `S_LOCK;
else
cur_state <= next_state;
end
//第二段
always @(*)begin
next_state = `S_LOCK;
case(cur_state)
`S_LOCK:begin
if(CMD_ENTER)
next_state = `S_NUM0_INPUT;
else
next_state = `S_LOCK;
end
`S_NUM0_INPUT:begin
if(trig_key_trig_t1==1'b1)
next_state = `S_NUM1_INPUT;
else
next_state = `S_NUM0_INPUT;
end
`S_NUM1_INPUT:begin
if(trig_key_trig_t1==1'b1)
next_state = `S_NUM2_INPUT;
else
next_state = `S_NUM1_INPUT;
end
`S_NUM2_INPUT:begin
if(trig_key_trig_t1==1'b1)
next_state = `S_NUM3_INPUT;
else
next_state = `S_NUM2_INPUT;
end
`S_NUM3_INPUT:begin
if(trig_key_trig_t1==1'b1)
next_state = `S_NUM4_INPUT;
else
next_state = `S_NUM3_INPUT;
end
`S_NUM4_INPUT:begin
if(trig_key_trig_t1==1'b1)
next_state = `S_NUM5_INPUT;
else
next_state = `S_NUM4_INPUT;
end
`S_NUM5_INPUT:begin
if(trig_key_trig_t1==1'b1)
next_state = `S_CHECK;
else
next_state = `S_NUM5_INPUT;
end
/*
`S_CHECK:begin
end
`S_SUCCESS:begin
end
`S_FAIL:begin
end
`S_PASSWORD_SET:begin
end
*/
default:next_state = `S_LOCK;
endcase
end
//第三段
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
end
case(cur_state)
`S_LOCK:begin
end
`S_NUM0_INPUT:begin
end
`S_NUM1_INPUT:begin
end
`S_NUM2_INPUT:begin
end
`S_NUM3_INPUT:begin
end
`S_NUM4_INPUT:begin
end
`S_NUM5_INPUT:begin
end
/*
`S_CHECK:begin
end
`S_SUCCESS:begin
end
`S_FAIL:begin
end
`S_PASSWORD_SET:begin
end
*/
endcase
default:begin
end
end
这样状态机框架就搭好了
接下来要往状态机里加条件了,首先完善密码判断状态。考虑如下:在密码输入过程中通过移位操作将6位10进制数锁存下来,因此需要定义一个24位的密码寄存器。由于num_reg寄存器在trig_key_trig条件下跳变,因此移位操作也在这个条件进行
//password_reg
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)
password_reg <= 'd0;
else case(cur_state)begin
`S_NUM0_INPUT,`S_NUM1_INPUT,`S_NUM2_INPUT,`S_NUM3_INPUT,`S_NUM4_INPUT,`S_NUM5_INPUT:begin
if(trig_key_trig==1'b1)
password_reg <= {password_reg[19:0],num_reg};
end
`S_LOCK:password_reg <= 'd0;
default:;
end
end
那么CHECK状态下的转移条件就可以写出来了
`S_CHECK:begin
if(password_reg==`PASSWORD)
next_state = `S_SUCCESS
else
next_state = `S_FAIL;
end
现在考虑一下输入过程中的条件转移细节:如果在输入过程中一段时间无操作,状态就应该跳回空闲状态。所以设定在输入密码过程中无操作满10s就回到LOCK·状态。这样就需要定义两个计数器,一个位1s的计时器,一个是计满10的计数器,这样就能解决10s的计时
reg[25:0] cnt_1s;
reg[3:0] cnt_10times;
localparam CNT_1S = 49_999_999;
//cnt_1s
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)
cnt_1s <= 'd0;
else case(cur_state)
`S_NUM0_INPUT,`S_NUM1_INPUT,`S_NUM2_INPUT,`S_NUM3_INPUT,`S_NUM4_INPUT,`S_NUM5_INPUT:begin
if(trig_key_trig_t1==1'b1)//有操作即清零
cnt_1s <= 'd0;
else if(cnt_1s==CNT_1S)//计满清零
cnt_1s <= 'd0;
else
cnt_1s <= cnt_1s + 1'b1;
end
default:cnt_1s <= 'd0;
endcase
end
//cnt_10times
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)
cnt_10times <= 'd0;
else case(cur_state)
`S_NUM0_INPUT,`S_NUM1_INPUT,`S_NUM2_INPUT,`S_NUM3_INPUT,`S_NUM4_INPUT,`S_NUM5_INPUT:begin
if(trig_key_trig_t1==1'b1)
cnt_10times <= 'd0;
else if(cnt_1s==CNT_1S&&cnt_10times==9)
cnt_10times <= 'd0;
else if(cnt_1s==CNT_1S)
cnt_10times <= cnt_10times + 1'b1;
end
default:cnt_10times <= 'd0;
endcase
end
这样以后,状态转移继续完善
`S_NUM0_INPUT:begin
if(trig_key_trig_t1==1'b1)
next_state = `S_NUM1_INPUT;
else if(cnt_1s==CNT_1S&&cnt_10times==9)
next_state = `S_LOCK;
else
next_state = `S_NUM0_INPUT;
end
`S_NUM1_INPUT:begin
if(trig_key_trig_t1==1'b1)
next_state = `S_NUM2_INPUT;
else if(cnt_1s==CNT_1S&&cnt_10times==9)
next_state = `S_LOCK;
else
next_state = `S_NUM1_INPUT;
end
`S_NUM2_INPUT:begin
if(trig_key_trig_t1==1'b1)
next_state = `S_NUM3_INPUT;
else if(cnt_1s==CNT_1S&&cnt_10times==9)
next_state = `S_LOCK;
else
next_state = `S_NUM2_INPUT;
end
`S_NUM3_INPUT:begin
if(trig_key_trig_t1==1'b1)
next_state = `S_NUM4_INPUT;
else if(cnt_1s==CNT_1S&&cnt_10times==9)
next_state = `S_LOCK;
else
next_state = `S_NUM3_INPUT;
end
`S_NUM4_INPUT:begin
if(trig_key_trig_t1==1'b1)
next_state = `S_NUM5_INPUT;
else if(cnt_1s==CNT_1S&&cnt_10times==9)
next_state = `S_LOCK;
else
next_state = `S_NUM4_INPUT;
end
`S_NUM5_INPUT:begin
if(trig_key_trig_t1==1'b1)
next_state = `S_CHECK;
else if(cnt_1s==CNT_1S&&cnt_10times==9)
next_state = `S_LOCK;
else
next_state = `S_NUM5_INPUT;
end
接下来还需处理num0-5这6个与数码管显示模块连线的接口和flag_lock这一锁住标志,处理方法很简单,num0-5在状态机中处理。我们认定num5是最左边的数字
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
{num5,num4,num3,num2,num1,num0} <= 24'h000_000;
end
case(cur_state)
`S_LOCK:begin
{num5,num4,num3,num2,num1,num0} <= 24'h000_000;
end
`S_NUM0_INPUT:begin
if(trig_key_trig_t1==1'b1)
num5 <= num_reg;
end
`S_NUM1_INPUT:begin
if(trig_key_trig_t1==1'b1)
num4 <= num_reg;
end
`S_NUM2_INPUT:begin
if(trig_key_trig_t1==1'b1)
num3 <= num_reg;
end
`S_NUM3_INPUT:begin
if(trig_key_trig_t1==1'b1)
num2 <= num_reg;
end
`S_NUM4_INPUT:begin
if(trig_key_trig_t1==1'b1)
num1 <= num_reg;
end
`S_NUM5_INPUT:begin
if(trig_key_trig_t1==1'b1)
num0 <= num_reg;
end
`S_CHECK:begin
end
`S_SUCCESS:begin
end
`S_FAIL:begin
end
/*`S_PASSWORD_SET:begin
end*/
endcase
default:begin
end
end
flag_lock就很简单,只要不是成功解锁即SUCCESS状态就都是锁定的,用assign写即可
assign flag_lock = ~(cur_state==`S_SUCCESS);
这样一来,具有基本功能的密码锁驱动就完成了
//==================defines=====================
`define SIM
module lock_dri2(
//================System Signal================
input clk ,
input rst_n ,
//================Interface====================
input [3:0] num ,
output reg [3:0] num0 ,
output reg [3:0] num1 ,
output reg [3:0] num2 ,
output reg [3:0] num3 ,
output reg [3:0] num4 ,
output reg [3:0] num5 ,
output flag_lock
);
//================parameters===================
`ifndef SIM
localparam CNT_1S = 49_999_999;
`else
localparam CNT_1S = 49;
`endif
localparam CMD_ENTER = (trig_key_trig_t1==1'b1&&num_reg==`ENTER);
//================System regs==================
reg[9:0] cur_state;
reg[9:0] next_state;
reg[3:0] num_reg;
wire trig_key_trig;
wire trig_key_trig_t1;
reg[23:0] password_reg;
reg[25:0] cnt_1s;
reg[3:0] cnt_10times;
//================Main Codes===================
//num_reg
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)
num_reg <= 'd0;
else if(trig_key_trig==1'b1)
num_reg <= num;
end
//**************state machine one******************
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)
cur_state <= `S_LOCK;
else
cur_state <= next_state;
end
//**************state machine two******************
always @(*)begin
next_state = `S_LOCK;
case(cur_state)
`S_LOCK:begin
if(CMD_ENTER)
next_state = `S_NUM0_INPUT;
else
next_state = `S_LOCK;
end
`S_NUM0_INPUT:begin
if(trig_key_trig_t1==1'b1)
next_state = `S_NUM1_INPUT;
else if(cnt_1s==CNT_1S&&cnt_10times==9)
next_state = `S_LOCK;
else
next_state = `S_NUM0_INPUT;
end
`S_NUM1_INPUT:begin
if(trig_key_trig_t1==1'b1)
next_state = `S_NUM2_INPUT;
else if(cnt_1s==CNT_1S&&cnt_10times==9)
next_state = `S_LOCK;
else
next_state = `S_NUM1_INPUT;
end
`S_NUM2_INPUT:begin
if(trig_key_trig_t1==1'b1)
next_state = `S_NUM3_INPUT;
else if(cnt_1s==CNT_1S&&cnt_10times==9)
next_state = `S_LOCK;
else
next_state = `S_NUM2_INPUT;
end
`S_NUM3_INPUT:begin
if(trig_key_trig_t1==1'b1)
next_state = `S_NUM4_INPUT;
else if(cnt_1s==CNT_1S&&cnt_10times==9)
next_state = `S_LOCK;
else
next_state = `S_NUM3_INPUT;
end
`S_NUM4_INPUT:begin
if(trig_key_trig_t1==1'b1)
next_state = `S_NUM5_INPUT;
else if(cnt_1s==CNT_1S&&cnt_10times==9)
next_state = `S_LOCK;
else
next_state = `S_NUM4_INPUT;
end
`S_NUM5_INPUT:begin
if(trig_key_trig_t1==1'b1)
next_state = `S_CHECK;
else if(cnt_1s==CNT_1S&&cnt_10times==9)
next_state = `S_LOCK;
else
next_state = `S_NUM5_INPUT;
end
`S_CHECK:begin
if(password_reg==`PASSWORD)
next_state = `S_SUCCESS
else
next_state = `S_FAIL;
end
/*
`S_SUCCESS:begin
end
`S_FAIL:begin
end
`S_PASSWORD_SET:begin
end
*/
default:next_state = `S_LOCK;
endcase
end
//
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
{num5,num4,num3,num2,num1,num0} <= 24'h000_000;
end
case(cur_state)
`S_LOCK:begin
{num5,num4,num3,num2,num1,num0} <= 24'h000_000;
end
`S_NUM0_INPUT:begin
if(trig_key_trig_t1==1'b1)
num5 <= num_reg;
end
`S_NUM1_INPUT:begin
if(trig_key_trig_t1==1'b1)
num4 <= num_reg;
end
`S_NUM2_INPUT:begin
if(trig_key_trig_t1==1'b1)
num3 <= num_reg;
end
`S_NUM3_INPUT:begin
if(trig_key_trig_t1==1'b1)
num2 <= num_reg;
end
`S_NUM4_INPUT:begin
if(trig_key_trig_t1==1'b1)
num1 <= num_reg;
end
`S_NUM5_INPUT:begin
if(trig_key_trig_t1==1'b1)
num0 <= num_reg;
end
`S_CHECK:begin
end
`S_SUCCESS:begin
end
`S_FAIL:begin
end
/*`S_PASSWORD_SET:begin
end*/
endcase
default:begin
end
end
//password_reg
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)
password_reg <= 'd0;
else case(cur_state)begin
`S_NUM0_INPUT,`S_NUM1_INPUT,`S_NUM2_INPUT,`S_NUM3_INPUT,`S_NUM4_INPUT,`S_NUM5_INPUT:begin
if(trig_key_trig==1'b1)
password_reg <= {password_reg[19:0],num_reg};
end
`S_LOCK:password_reg <= 'd0;
default:;
end
end
//cnt_1s
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)
cnt_1s <= 'd0;
else case(cur_state)
`S_NUM0_INPUT,`S_NUM1_INPUT,`S_NUM2_INPUT,`S_NUM3_INPUT,`S_NUM4_INPUT,`S_NUM5_INPUT:begin
if(trig_key_trig_t1==1'b1)//有操作即清零
cnt_1s <= 'd0;
else if(cnt_1s==CNT_1S)//计满清零
cnt_1s <= 'd0;
else
cnt_1s <= cnt_1s + 1'b1;
end
default:cnt_1s <= 'd0;
endcase
end
//cnt_10times
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)
cnt_10times <= 'd0;
else case(cur_state)
`S_NUM0_INPUT,`S_NUM1_INPUT,`S_NUM2_INPUT,`S_NUM3_INPUT,`S_NUM4_INPUT,`S_NUM5_INPUT:begin
if(trig_key_trig_t1==1'b1)
cnt_10times <= 'd0;
else if(cnt_1s==CNT_1S&&cnt_10times==9)
cnt_10times <= 'd0;
else if(cnt_1s==CNT_1S)
cnt_10times <= cnt_10times + 1'b1;
end
default:cnt_10times <= 'd0;
endcase
end
assign flag_lock = ~(cur_state==`S_SUCCESS);
endmodule