赞
踩
该项目来自野火的状态机拓展提高训练
读完这道题,我认为在该项目中我们需要三个模块。分别是,按键消抖模块,可乐状态机模块,LED控制模块。
模块框图如下
思路:当判断到按键按下,例如低电平触发后,开启一个计数器,计时20ms(可调整)在20ms区间或超过20ms的区间里,如果持续为低电平,那么我们认为按键真的触发了,而不是因为电平抖动。拉高一个变量作为按键的输出即可。
代码如下 较为简单不做详细解释
module key_filter #( parameter CNT_MAX = 20'd999_999 //50Mhz的时钟 计20ms ) ( input wire clk , //系统时钟50Mh input wire rst , //全局复位 input wire key_in , output reg key_out ); reg [20:0] cnt ; //计数器 always@(posedge clk or negedge rst) if(!rst) cnt <= 20'b0; else if(key_in == 1'b1) cnt <= 20'b0; //如果在该按键触发期间,计数超过了20ms,不清零,保持原状即可 else if(cnt == CNT_MAX && key_in == 1'b0) cnt <= CNT_MAX; else cnt <= cnt + 1'b1; always@(posedge clk or negedge rst) if(!rst) key_out <= 1'b0; //在CNT_MAX时拉高标志,需要-1是因为时序电路 else if(cnt == CNT_MAX-1'b1) key_out = 1'b1; else key_out = 1'b0; endmodule
这应该是这题里面最重要的部分。状态机的编程思想具体大家可以查阅相关文章,这里只做大概解释。
所谓状态机,我认为指的是让程序根据输入和输出,在指定的有限个状态之间进行轮转的模式。
举个例子
如果是电梯的话,每1秒运行一层楼,都进行一次判断,例如现在在5楼停止,处于关门状态。
有人按下了1楼,下一个时刻,状态机检测到了1楼的信号,和进行比对,5>1,于是把状态改为下楼
1S后下到了4楼,此时,有人按下了3楼。又过了1S,电梯运行到了3楼,状态机发现当前楼层是需要停止的楼层,于是把状态改为开门,开门一段时间后再把状态改回关门。
关门后,发现1楼还没到,那么状态改为下楼,继续走到1楼,到了1楼之后,还是进行开门,关门的状态切换,再次判断发现没有要走的楼层了,状态就停留在了关门。
伪代码例子如图
case(state) 关门: if(目标楼层 > 当前楼层) state <= 上升 if(目标楼层 < 当前楼层) state <= 下降 if(目标楼层 == 当前楼层 && 无人按下) state <= 关门 if(目标楼层 == 当前楼层 && 有人按下) state <= 开门 开门: if(延时时间到达) state <= 关门 else state <= 开门 上升: if(目标楼层 > 当前楼层) state <= 上升 if(目标楼层 == 当前楼层) state <= 开门 下降: if(目标楼层 < 当前楼层) state <= 下降 if(目标楼层 == 当前楼层) state <= 开门 endcase
如果能理解这个代码的话,那么本题的可乐状态机其实是更加简单的。
这里我们定义按键0是0.5元,按键1是1元,根据题目要求,核心部分状态机如下。KEY0投币的话 对应状态+0.5元,KEY1加一元,超过10S没投币的话 状态回到空闲态。2.5和3元的状态下,当计数到达10S后回到空闲态
//投币状态机 always@(posedge clk or negedge rst) if(!rst) state <= IDLE; else case(state) IDLE: //空闲 if(key_out[0]) state <= HALF; else if(key_out[1]) state <= ONE; HALF: //0.5 if(key_out[0]) state <= ONE; else if(key_out[1]) state <= ONE_HALF; else if(key_flag) state <= IDLE; //这里是10s后没按键按下的标志位 ONE: //1 if(key_out[0]) state <= ONE_HALF; else if(key_out[1]) state <= TWO; else if(key_flag) state <= IDLE; ONE_HALF: //1.5 if(key_out[0]) state <= TWO; else if(key_out[1]) state <= TWO_HALF; else if(key_flag) state <= IDLE; TWO: //2 if(key_out[0]) state <= TWO_HALF; else if(key_out[1]) state <= THREE; else if(key_flag) state <= IDLE; TWO_HALF: //2.5 if(cnt_10s==CNT_10S) state <= IDLE; //这里是流水的10s的标志位 THREE: //3 if(cnt_10s==CNT_10S) state <= IDLE;//这里是双向流水的10s的标志位 default: state <= IDLE; endcase
根据题目要求还需要有10s内按键没按下后清零状态的功能,那么这个功能靠一个定时器实现即可,不过这里不推荐直接直接计算10S(一个大佬和我说那样直接算那么长不太好)。
因为我们的时钟是50Mhz,也就是20ns。那么我选择计数500ms,20个500ms就是1S
//计数500ms 除非按键按下或者计满 always@(posedge clk or negedge rst) if(!rst) key_cnt <= 25'b0; else if(key_cnt == CNT_500MS_MAX || key_out[0] || key_out[1]) //按键按下时清空计数 key_cnt <= 25'b0; else key_cnt <= key_cnt + 1'b1; //按键500ms标志 always@(posedge clk or negedge rst) if(!rst) key_500ms_flag <= 1'b0; else if(key_cnt == CNT_500MS_MAX -1 ) key_500ms_flag <= 1'b1; else key_500ms_flag <= 1'b0; //计数10s 除非按键按下 always@(posedge clk or negedge rst) if(!rst) key_10s <= 5'b0; else if( (key_10s == CNT_10S && key_500ms_flag) || key_out[0] || key_out[1]) //按键按下时清空计数 key_10s <= 5'b0; else if(key_500ms_flag) key_10s <= key_10s + 1'b1; //按键10s标志 always@(posedge clk or negedge rst) if(!rst) key_flag <= 1'd0; else if(key_10s == CNT_10S - 1 && key_500ms_flag) key_flag <= 1'd1; else key_flag <= 1'd0;
除了按键按下10s内清零外,还有流水灯10s后自动切换会初始状态的代码,那么我们就是在2.5元和3元的状态触发时,使能一个定时器,计到10s后让该状态跳转回IDLE的空闲状态即可。
//10S定时使能信号 always@(posedge clk or negedge rst) if(!rst) cnt_en <= 1'b0; else if(cnt_flag) cnt_en <= 1'b0; else if(state == TWO_HALF || state == THREE) cnt_en <= 1'b1; //500ms计数器 always@(posedge clk or negedge rst) if(!rst) cnt <= 25'd0; else if(cnt == CNT_500MS_MAX && cnt_en) cnt <= 25'd0; else if(cnt_en) //使能时才运行定时器 cnt <= cnt + 1'b1; //500ms计数标志位 always@(posedge clk or negedge rst) if(!rst) cnt_500ms_flag <= 1'b0; else if(cnt == CNT_500MS_MAX - 1) cnt_500ms_flag <= 1'b1; else //使能时才运行定时器 cnt_500ms_flag <= 1'b0; //计20个500ms always@(posedge clk or negedge rst) if(!rst) cnt_10s <= 5'b0; else if(cnt_10s == CNT_10S && cnt_500ms_flag) cnt_10s <= 5'b0; else if(cnt_500ms_flag) cnt_10s <= cnt_10s + 1'b1; //流水灯10S标志位 always@(posedge clk or negedge rst) if(!rst) cnt_flag <= 1'd0; else if(cnt_10s == CNT_10S - 1 && cnt_500ms_flag) cnt_flag <= 1'd1; else cnt_flag <= 1'd0;
module cola_fsm#( parameter CNT_500MS_MAX = 25'd24_999_999, parameter CNT_10S = 5'd19, parameter IDLE = 7'b000_0001, parameter HALF = 7'b000_0010, parameter ONE = 7'b000_0100, parameter ONE_HALF = 7'b000_1000, parameter TWO = 7'b001_0000, parameter TWO_HALF = 7'b010_0000, parameter THREE = 7'b100_0000 )( input wire clk, input wire rst, input wire[1:0] key_out, output wire[6:0] state_out ); reg [6:0] state; reg cnt_en; //10S计数器使能 reg [24:0] cnt; //500ms计数器 reg cnt_500ms_flag; reg [4:0]cnt_10s; //流水灯的10s计数器 reg cnt_flag; reg [24:0] key_cnt; //投币的500ms定时器 reg [4:0] key_10s; //投币的10s定时器 reg key_500ms_flag; reg key_flag; assign state_out = state; //这样的话RTL会识别出是一个状态机 但是如果不这样的话也没问题 //投币状态机 always@(posedge clk or negedge rst) if(!rst) state <= IDLE; else case(state) IDLE: if(key_out[0]) state <= HALF; else if(key_out[1]) state <= ONE; HALF: if(key_out[0]) state <= ONE; else if(key_out[1]) state <= ONE_HALF; else if(key_flag) state <= IDLE; ONE: if(key_out[0]) state <= ONE_HALF; else if(key_out[1]) state <= TWO; else if(key_flag) state <= IDLE; ONE_HALF: if(key_out[0]) state <= TWO; else if(key_out[1]) state <= TWO_HALF; else if(key_flag) state <= IDLE; TWO: if(key_out[0]) state <= TWO_HALF; else if(key_out[1]) state <= THREE; else if(key_flag) state <= IDLE; TWO_HALF: if(cnt_flag) state <= IDLE; THREE: if(cnt_flag) state <= IDLE; default: state <= IDLE; endcase //10S定时使能信号 always@(posedge clk or negedge rst) if(!rst) cnt_en <= 1'b0; else if(cnt_flag) cnt_en <= 1'b0; else if(state == TWO_HALF || state == THREE) cnt_en <= 1'b1; //500ms计数器 always@(posedge clk or negedge rst) if(!rst) cnt <= 25'd0; else if(cnt == CNT_500MS_MAX && cnt_en) cnt <= 25'd0; else if(cnt_en) //使能时才运行定时器 cnt <= cnt + 1'b1; //500ms计数器 always@(posedge clk or negedge rst) if(!rst) cnt_500ms_flag <= 1'b0; else if(cnt == CNT_500MS_MAX - 1) cnt_500ms_flag <= 1'b1; else if(cnt_en) //使能时才运行定时器 cnt_500ms_flag <= 1'b0; //计20个500ms always@(posedge clk or negedge rst) if(!rst) cnt_10s <= 5'b0; else if(cnt_10s == CNT_10S && cnt_500ms_flag) cnt_10s <= 5'b0; else if(cnt_500ms_flag) cnt_10s <= cnt_10s + 1'b1; //流水灯10S标志位 always@(posedge clk or negedge rst) if(!rst) cnt_flag <= 1'd0; else if(cnt_10s == CNT_10S - 1 && cnt_500ms_flag) cnt_flag <= 1'd1; else cnt_flag <= 1'd0; //计数500ms 除非按键按下或者计满 always@(posedge clk or negedge rst) if(!rst) key_cnt <= 25'b0; else if(key_cnt == CNT_500MS_MAX || key_out[0] || key_out[1]) //按键按下时清空计数 key_cnt <= 25'b0; else key_cnt <= key_cnt + 1'b1; //按键500ms标志 always@(posedge clk or negedge rst) if(!rst) key_500ms_flag <= 1'b0; else if(key_cnt == CNT_500MS_MAX -1 ) key_500ms_flag <= 1'b1; else key_500ms_flag <= 1'b0; //计数10s 除非按键按下 always@(posedge clk or negedge rst) if(!rst) key_10s <= 5'b0; else if( (key_10s == CNT_10S && key_500ms_flag) || key_out[0] || key_out[1]) //按键按下时清空计数 key_10s <= 5'b0; else if(key_500ms_flag) key_10s <= key_10s + 1'b1; //按键10s标志 always@(posedge clk or negedge rst) if(!rst) key_flag <= 1'd0; else if(key_10s == CNT_10S - 1 && key_500ms_flag) key_flag <= 1'd1; else key_flag <= 1'd0; endmodule
这里就比较简单了,只需要根据状态操作LED即可
module led_controller #( parameter CNT_500MS_MAX = 25'd24_999_999 ) ( input wire clk, input wire rst, input wire[6:0] state, output wire[3:0] led_out ); reg [24:0] cnt; reg[1:0] led_state; reg[3:0] led_out_reg; reg double_state; reg cnt_flag ; //led输出信号 assign led_out = ~led_out_reg; //500ms计数器 always@(posedge clk or negedge rst) if(!rst) cnt <= 25'b0; else if(cnt == CNT_500MS_MAX && (|led_state)) cnt <= 25'b0; else if((|led_state)) cnt <= cnt + 1'b1; else cnt <= cnt; //特殊情况 always@(posedge clk or negedge rst) if(!rst) led_state <= 2'b00; else if(state == 7'b0100000) //未找零 led_state <= 2'b10; else if(state == 7'b1000000) //找零 led_state <= 2'b01; else led_state <= 2'b00; //计数器满500ms标志位 always@(posedge clk or negedge rst) if(!rst) cnt_flag <= 1'b0; else if(cnt == CNT_500MS_MAX - 1 && (|led_state)) cnt_flag <= 1'b1; else cnt_flag <= 1'b0; //led寄存器信号 always@(posedge clk or negedge rst) if(!rst) led_out_reg <= 4'b0000; else if(state == 7'b000_0001) led_out_reg <= 4'b0000; else if(state == 7'b0000010) led_out_reg <= 4'b1000; else if(state == 7'b0000100) led_out_reg <= 4'b1100; else if(state == 7'b0001000) led_out_reg <= 4'b1110; else if(state == 7'b0010000) led_out_reg <= 4'b1111; //双向流水 else if(led_state == 2'b01) begin if(led_out_reg == 4'b0000 || led_out_reg == 4'b1111 || led_out_reg == 4'b1110) led_out_reg <= 4'b0001; else if(cnt_flag && double_state) led_out_reg <= led_out_reg << 1'b1; else if(cnt_flag) led_out_reg <= led_out_reg >> 1'b1; end //单向流水 else if(led_state == 2'b10) begin if(led_out_reg == 4'b0000 || led_out_reg == 4'b1111 || led_out_reg == 4'b1110) led_out_reg <= 4'b0001; else if(cnt_flag && led_out_reg == 4'b1000) led_out_reg <= 4'b0001; else if(cnt_flag) led_out_reg <= led_out_reg << 1'b1; end else led_out_reg <= 4'b0000; //double_state信号 always@(posedge clk or negedge rst) if(!rst) double_state <= 1'b0; else if(led_out_reg == 4'b0001 && led_state == 2'b01) double_state <= 1'b1; else if(led_out_reg == 4'b1000 && led_state == 2'b01) double_state <= 1'b0; else if(led_state == 2'b10) double_state <= 1'b0; else double_state <= double_state; endmodule
没什么好说的 例化各个模块就可以了
module cola_machine( input wire [1:0] key_in, input wire clk, input wire rst, output wire[3:0] led_out ); wire[1:0] key_out; wire [6:0] state; //按键消抖模块 //这边其实应该改在test_bench里的 这里懒得弄了 key_filter #(.CNT_MAX(20'd5))key1( .clk(clk), .rst(rst), .key_in(key_in[0]), .key_out(key_out[0]) ); key_filter #(.CNT_MAX(20'd5))key2( .clk(clk), .rst(rst), .key_in(key_in[1]), .key_out(key_out[1]) ); //led控制模块 led_controller led_ctrl( .clk(clk), .rst(rst), .state(state), .led_out(led_out) ); //状态机 cola_fsm fsm( .clk(clk), .rst(rst), .key_out(key_out), .state_out(state) ); endmodule
`timescale 1ns/1ns module tb_cola_machine(); //reg define reg clk; reg rst; reg [1:0]key_in; reg random; wire [3:0]led_out; initial begin clk = 1'b1; rst <= 1'b0; #20 rst <= 1'b1; end always #10 clk = ~clk; always@(posedge clk or negedge rst) if(!rst) random <= 1'b0; else random <= {$random} % 2; always@(posedge clk or negedge rst) if(!rst) key_in[0] <= 1'b0; else key_in[0] <= random; always@(posedge clk or negedge rst) if(!rst) key_in[1] <= 1'b0; else key_in[1] <= ~random; defparam cola_machine_inst.fsm.CNT_500MS_MAX = 25'd24; defparam cola_machine_inst.led_ctrl.CNT_500MS_MAX = 25'd24; cola_machine cola_machine_inst( .clk (clk ), .rst (rst ), .key_in(key_in), .led_out(led_out) ); endmodule
观察可以发现,在两个1元+一个0.5元按下后,状态来到了0100000也就是2.5元的模式,在此期间定时器使能,底下的led_out也有对应的20格输出,一格500ms,就是10秒了
3元的双向流水则更为明显,因为双向,所以存在左右的切换,四个led灯,所以跳转三次,切换一次左右,这里的double_state一格对应的就是三格led_out,一共有3*6 + 2格,也是20格,10S
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。