目录
状态机的概念与分类
时序电路在时钟脉冲的作用下,在有限个状态之间进行转换,以完成某种特定的功能,所以将时序电路又称为有限机状态机(Finite State Machine),简称为状态机,用FSM表示。
但广义地讲,状态机不单单是指时序逻辑电路,而是指任何有逻辑顺序和时序规律的事件。
状态机有三个基本要素:状态、输出和输入。
(1) 状态: 用于划分逻辑顺序和时序规律。(2) 输出: 在某个状态下发生的特定事件。(3) 输入:状态机中进入每个状态的条件。
根据状态机的输出是否与输入有关,将状态机分为摩尔(Moore)型状态机和米里(Mealy)型状态机两大类。
一般地,Moore型状态机也可能有输入,只是输出不直接与输入信号相关,因此,在数字电路中,Moore型状态机表示为图6-3所示的结构形式,其中输出z(t)=f[s(t)],而与输入x(t)无关。
Mealy型状态机
Mealy型状态机的输出不仅依赖于当前状态,而且与输入有关。
Mealy型状态机的输出既与状态有关,也与输入有关,因此,在数字电路中,Mealy型状态机表示结构形式,其中输出z(t)=f[x(t),s(t)]。
状态机的描述方法
时序逻辑电路常用的功能描述有状态转换表、状态转换图和波形图三种方法。
以表格的形式描述时序电路的次态Q*以及外部输出信号Y与外部输入信号X和现态Q之间的关系。
状态转换图
以图形的方式描述时序电路的状态Q和输出Y在输入信号X的作用下随时钟CLK的变化关系。
用随时间变化的波形来描述在输入信号的作用下,输出Y及状态Q与时钟脉冲CLK之间的对应关系。
状态机有三种常用的表示方法:状态转换图、状态转换表和HDL描述。
状态机的HDL描述方法结构清晰,灵活规范,而且安全、高效和易于维护,因此是本章重点的讲述内容。
状态机设计的基本步骤是: (1) 定义状态; (2) 建立状态转换图; (3) 完成状态编码; (4) 应用HDL描述状态转换关系以及输出。
- 定义状态
由于检测器用于检测"1111"序列,所以电路需要识别和记忆连续输入1的个数,因此,预定义电路内部有S0、S1、S2、S3和S4共五个状态,其中S0表示当前还没有接收到一个1,S1表示已经接收到一个1,S2表示已经接收到两个1,S3表示已经接收到三个1,而S4表示已经接收到四个1。
- 建立状态转换图
状态定义完成之后,需要分析在时钟脉冲的作用下,状态转移的方向以及输出,画出状态转换图。通常从系统的初始状态、复位状态或者空闲状态开始分析,标出每个状态的转换方向、转换条件以及输出。
- 状态编码
在状态机设计中,建议使用参数定义语句parameter/localparam来定义状态编码,以增强代码的可阅读性。
无效状态的处理大致有如下三种方式:(1)转入空闲状态,等待下一个任务的到来(2)转入指定的状态,去执行特定任务;(3)转入预先定义的专门处理错误的状态,如预警状态等。
- 状态机的描述
Verilog HDL用过程语句描述状态机。由于时序电路的次态是现态以及输入的逻辑函数,因此需要将现态和输入信号作为过程的敏感事件或者边沿触发事件,结合case和if等高级程序语句等实现逻辑功能。
有限状态机的描述可采用一段式、两段式和三段式三种描述方式。
三段式状态机的描述模板如下:
// 第一个过程语句,时序逻辑,描述状态转换关系
always @ ( posedge clk or negedge rst_n )
if ( !rst_n ) // 复位信号有效时
current_state <= IDLE;
else current_state <= next_state; // 状态转换
// 第二个过程语句,组合逻辑,描述次态
always @ ( current_state,input_signals ) // 电平敏感条件
case ( current_state )
S1: if (...) next_state = ...; // 阻塞赋值
else next_state = ...;
S2: if (...) next_state = ...;
else next_state = ...;
......
default: ...;
endcase
// 第三个过程语句描述输出(1),应用组合逻辑,阻塞赋值
always @ ( current_state,
input_signals )
case ( current_state )
S1: if ( cond_expression )
out = ...;
else out = ...;
S2: if ( cond_expression )
out = ...;
else out = ...;
......
default: ...;
endcase
// 第三个过程语句描述输出(2),
应用时序逻辑,非阻塞赋值
always @ ( posedge clk or negedge rst_n )
if ( !rst_n )
out <= ...;
else
case ( current_state )
S1: if (cond_expression ) out <= ...;
else out <= ...;
S2: if ( cond_expression ) out <= ...;
else out <= ...;
......
default: ...;
endcase
设计例1的"1111"序列检测器,应用三段式状态机Verilog描述代码参考如下:
module serial_detor ( input clk, // 检测器时钟
input rst_n, // 复位信号
input x, // 串行数据输入
output wire y ); // 检测结果输出
localparam S0 = 5'b00001, // 状态定义及编码
S1 = 5'b00010,
S2 = 5'b00100,
S3 = 5'b01000,
S4 = 5'b10000;
// 内部状态变量定义
reg [4:0] current_state,next_state; // 现态和次态
// 组合逻辑,定义输出
assign y = ( current_state == S4 );
// 时序逻辑过程,描述状态转换
always @( posedge clk or negedge rst_n)
if ( !rst_n ) current_state <= S0;
else current_state <= next_state;
// 组合逻辑过程,确定次态
always @( current_state,x )
case ( current_state )
S0 : if (x) next_state = S1; else next_state = S0;
S1 : if (x) next_state = S2; else next_state = S0;
S2 : if (x) next_state = S3; else next_state = S0;
S3 : if (x) next_state = S4; else next_state = S0;
S4 : if (x) next_state = S4; else next_state = S0;
default : next_state = S0;
endcase
endmodule
将上述代码经过编译与综合后,建立向量波形文件进行功能仿真,得到的时序波形如图所示。
基于状态机设计按键消抖电路时,需要将按键的一次动作分解为:按下前、按下时、稳定期和释放时4个状态,分别用 KEY_IDLE、KEY_PRESSED、KEY_ACTIVE和 KEY_RELEASE表示。设按键输入用key_in表示,低电平有效,设计消抖时间为20ms,则按键消抖电路的状态转换关系如图所示。
应用系统函数$random产生随机整数的语法格式为: num = random%b // num为-(b-1) \~ (b-1)范围内的随机整数 num ={random}%b // num为0 ~ (b-1)范围内的随机整数
交通灯控制器的设计
交通灯控制器用于控制十字路口交通信号灯的状态,引导车辆和行人通行。交通灯控制器是时序逻辑电路,可以应用MCU/状态机设计方法实现。
设计过程:交通灯控制器应由状态控制电路和计时电路两部分构成。控制电路用于切换主、支干道绿灯、黄灯和红灯的状态,计时电路用于控制通行时间。
主支干道的绿、黄、红灯正常工作时共有四种组合,分别用S0、S1、S2和S3表示,状态的具体含义如表所示。
根据状态转换图设计交通灯控制电路的Verilog代码参考如下:
module traffic_controller( clk,rst_n,t45,t5,t25,traffic_state,mG,mY,mR,sg,sy,sr );
input clk,rst_n;
input t45,t5,t25;
output wire [3:0] traffic_state; // 需要显示计时时间时,状态需要输出
output reg mG,mY,mR; // 主干道绿、黄、红灯
output reg sg,sy,sr; // 支干道绿、黄、红灯
// 状态及编码定义,一位热码方式
localparam S0=4'b0001, S1=4'b0010, S2=4'b0100, S3=4'b1000;
// 内部变量定义
reg [3:0] current_state,next_state; // 现态和次态
// 状态输出描述
assign traffic_state = current_state;
// 时序逻辑过程,描述状态转换
always @(posedge clk or negedge rst_n)
if ( !rst_n ) // 异步复位
current_state <= S0;
else // 状态切换
current_state <= next_state;
// 组合逻辑过程,确定次态
always @(current_state,t45,t25,t5)
case (current_state)
S0 : if (t45) next_state = S1; else next_state = S0;
S1 : if ( t5) next_state = S2; else next_state = S1;
S2 : if (t25) next_state = S3; else next_state = S2;
S3 : if ( t5) next_state = S0; else next_state = S3;
default : next_state = S0;
endcase
// 组合逻辑过程,描述输出
always @( current_state )
case ( current_state )
S0: begin
mG = 1; mY = 0; mR = 0; // 主干道绿灯
sg = 0; sy = 0; sr = 1; end // 支干道红灯
S1: begin
mG = 0; mY = 1; mR = 0; // 主干道黄灯
sg = 0; sy = 0; sr = 1; end // 支干道红灯
S2: begin
mG = 0; mY = 0; mR = 1; // 主干道红灯
sg = 1; sy = 0; sr = 0; end // 支干道绿灯
S3: begin
mG = 0; mY = 0; mR = 1; // 主干道红灯
sg = 0; sy = 1; sr = 0; end // 支干道黄灯
default: begin // 其它取值时
mG = 1; mY = 0; mR = 0;
sg = 0; sy = 0; sr = 1; end
endcase
endmodule
如果不要求显示计时时间,则计时电路的设计比较简单。
取计时电路的时钟周期为5秒时,则45秒、5秒、25秒和5秒计时共需要9+1+5+1=16个时钟周期。
设计一个16进制加法计数器,状态编码为0~15。当状态为8时令t45=1,状态为9时令t5=1,状态为14时令t25=1,状态为15时令t5=1。因此,计时电路的Verilog描述参考如下:
module traffic_timer(clk,rst_n,t45,t5,t25);
input clk,rst_n;
output wire t45,t5,t25;
// 定义内部计数器
reg [3:0] timer;
// 描述16进制计数器
always @(posedge clk or negedge rst_n)
if (!rst_n)
timer <= 4'd0;
else
timer <= timer+1'd1;
// 描述输出
assign t45 = ( timer == 4'd8 ) ;
assign t5 = ( timer == 4'd9 ) || ( timer == 4'd15 ) ;
assign t25 = ( timer == 4'd14 ) ;
endmodule
将交通灯控制模块traffic_controller和计时模块traffic_timer连成图所示的顶层设计电路即可实现简单的交通信号灯控制器,并将时钟脉冲clk和复位信号rst_n连接在一起以控制两个子模块之间的同步。
若需要显示当前状态的剩余时间,则需要重新设计计时电路,而且主支干道计时时间(main_timer,sub_timer)的显示与状态有关。
以倒计时方式分别显示主干道和支干道状态的剩余时间时,则计时电路设计的Verilog 描述代码参考如下:
module traffic_timer (clk,rst_n,state,t45,t5,t25,main_timer,sub_timer);
input clk;
input rst_n;
input [3:0] state; // 状态信息,来自于traffic_controller
output wire t45,t5,t25; // 计时到输出信号,用于控制状态切换
output reg [5:0] main_timer,sub_timer; // 主干道和支干道计时信息,用于显示
// 状态定义及编码,独热码方式
localparam S0=4'b0001, S1=4'b0010, S2=4'b0100, S3=4'b1000;
// 组合逻辑,计时时间到逻辑
assign t45 = (state==S0) && (main_timer == 0);
assign t5 = (main_timer == 0) && (sub_timer==0);
assign t25 = (state==S2) && (sub_timer==0 );
// 计时过程
always @(posedge clk or negedge rst_n)
if ( !rst_n ) begin // 复位有效时
main_timer <= 45; // 从主干道通行开始
sub_timer <= 50; end
else // 否则,在时钟作用下 case ( state ) // 分情况讨论
S0: if ( t45 ) begin // 主干道通行时间到
main_timer <= 4;
sub_timer <= sub_timer - 1; end
else begin
main_timer <= main_timer - 1;
sub_timer <= sub_timer - 1; end
S1: if ( t5 ) begin // 主干道停车时间到
main_timer <= 29;
sub_timer <= 24; end
else begin
main_timer <= main_timer - 1;
sub_timer <= sub_timer - 1; end
S2: if ( t25 ) begin // 支干道通行时间到
main_timer <= main_timer - 1;
sub_timer <= 4; end
else begin
main_timer <= main_timer - 1;
sub_timer <= sub_timer - 1; end
S3: if ( t5 ) begin // 支干道停车时间到
main_timer <= 44;
sub_timer <= 49; end
else begin
main_timer <= main_timer - 1;
sub_timer <= sub_timer - 1; end
default: begin main_timer <= 45;
sub_timer <= 50; end
endcase
endmodule
由于计时时间是以二进制方式计数的,因此需要显示主、支干道计时时间时,还需要设计6位二进制数到两组BCD码的转换电路,才能驱动数码管进行显示。 带计时显示的交通灯控制器顶层测试电路如图所示。由按键产生脉冲,经过模块KEY_debounce消抖后作为交通灯计时电路的时钟。在计时电路traffic_timer的控制下,模块traffic_controller驱动交通灯显示状态,模块BinarytoSEG驱动数码管显示主、支干道状态的剩余时间。