FPGA是并行执行的,如果想要处理具有前后顺序的事件就要使用状态机
状态机的每一个状态代表一个事件,从执行当前事件到执行另一事件我们称之为状态的跳转或状态的转移,我们需要做的就是执行该事件然后跳转到一下时间,这样我们的系统就“活”了,状态机特别适合描述那些发生有先后顺序或时序规律的事情,在数字电路系统中小到计数器大到微处理器都可以用状态机来进行描述。其实状态机也是一种函数关系,如上图所示,一个计数器其实就可以看作是一个最简单的状态机,输入是时钟信号,状态是计数的值,输出是计数的值,我们可以列出一个时间和输出的函数关系,函数表达式为 q = counter(t),坐标关系如图所示,在有限的时间内,我们都可以根据具体的时间来算出当前输出的值是多少。
可乐机每次只能投入 1 枚 1 元硬币,且每瓶可乐卖 3 元钱,即投入 3 个硬币就可以让可乐机出可乐,如果投币不够 3 元想放弃投币需要按复位键,否则之前投入的钱不能退回。
(资料图片仅供参考)
完整的状态转移图需要知道以下三个要素:1、输入:根据输入可以确定是否需要进行状态跳转以及输出,是影响状态机系统执行过程的重要驱动力;2、输出:根据当前时刻的状态以及输入,是状态机系统最终要执行的动作;3、状态:根据输入和上一状态决定当前时刻所处的状态,是状态机系统执行的一个稳定的过程。
1、输入:投入 1 元硬币;2、输出:出可乐、不出可乐;3、状态:可乐机中有 0 元、可乐机中有 1 元、可乐机中有 2 元、可乐机中有 3 元。
状态转移图画出了之后可以不用画波形图
信号:时钟和复位信号,还有输入信号(可能有多个输入信号)、输出信号
状态参数(通过参数定义中间状态方便阅读及修改):多个状态需要定义多个状态参数
状态参数编码:多个状态可以使用独热码的编码方式或者其他的编码方式(二进制码、格雷码)
状态变量 - 使用独热码的话,有多少个中间状态,中间变量就有多少位
中间变量(计数器等)
对状态变量和中间变量进行赋值
对输出信号进行赋值
第一段状态机写状态的跳转,第二段状态机写数据的输出
module simple_fsm(input wire sys_clk,input wire sys_rst_n,input wire pi_money,output reg po_cola);parameter IDLE = 3"b001; ONE = 3"b010; TWO = 3"b100;reg [2:0] state;// 第一段状态机描述当前状态如何跳转到下一个状态always @ (posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1"b0)state <= IDLE;else case(state)IDLE : if(pi_money == 1"b1)state <= ONE;else state <= IDLE;ONE : if(pi_money == 1"b1)state <= TWO;else state <= ONE;TWO : if(pi_money == 1"b1)state <= IDLE;else state <= TWO;default :state <= IDLE;endcase//第二段状态机,描述当前状态 state 和输入 pi_money 如何影响 po_cola 输出always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1"b0)po_cola <= 1"b0;else if((state == TWO) && (pi_money == 1"b1))po_cola <= 1"b1;elsepo_cola <= 1"b0;endmodule
module simple_fsm(input wire sys_clk,input wire sys_rst_n,input wire pi_money,output reg po_cola);parameter IDLE = 3"b001; ONE = 3"b010; TWO = 3"b100;reg [2:0] cur_state;reg [2:0] next_state;// 第一段状态机描述当前状态always @(posedge sys_clk or sys_rst_n)if(sys_rst_n == 1"b0)cur_state <= IDLE;else cur_state <= next_state;// 第二段描述如何跳转到下一个状态always @ (*)case(state) IDLE : if(pi_money == 1"b1)state <= ONE;else state <= IDLE;ONE : if(pi_money == 1"b1)state <= TWO;else state <= ONE;TWO : if(pi_money == 1"b1)state <= IDLE;else state <= TWO;default :state <= IDLE;endcase//第三段状态机,描述当前状态 state 和输入 pi_money 如何影响 po_cola 输出always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1"b0)po_cola <= 1"b0;else if((state == TWO) && (pi_money == 1"b1))po_cola <= 1"b1;elsepo_cola <= 1"b0;// 第三段assign po_cola = state == TWO && pi_money == 1"b1 ? 1"b1 : 1"b0;endmodule
状态机代码写法有一段式、二段式、三段式
`timescale 1ns/1nsmodule tb_simple_fsm();//reg definereg sys_clk ;reg sys_rst_n ;reg pi_money ; //wire definewire po_cola; //初始化系统时钟、全局复位initial beginsys_clk = 1"b1;sys_rst_n <= 1"b0;#20sys_rst_n <= 1"b1;end //sys_clk:模拟系统时钟,每 10ns 电平翻转一次,周期为 20ns,频率为 50MHzalways #10 sys_clk = ~sys_clk; //pi_money:产生输入随机数,模拟投币 1 元的情况always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1"b0)pi_money <= 1"b0;elsepi_money <= {$random} % 2; //取模求余数,产生非负随机数 0、1 //将 RTL 模块中的内部信号引入到 Testbench 模块中进行观察,使用wire类型变量wire [2:0] state = simple_fsm_inst.state; initial begin$timeformat(-9, 0, "ns", 6);$monitor("@time %t: pi_money=%b state=%b po_cola=%b", $time, pi_money, state, po_cola);end //------------------------simple_fsm_inst------------------------simple_fsm simple_fsm_inst(.sys_clk (sys_clk ), //input sys_clk.sys_rst_n (sys_rst_n ), //input sys_rst_n.pi_money (pi_money ), //input pi_money .po_cola (po_cola ) //output po_cola); endmodule
标签: