第14章 状态机设计(State Machine Design)讲到VHDL设计而不讲state machine,感觉上就是不太完整,我们先来看看什么是state machine,它应该是一种流程控制的设计,在有限的状态中,根据判别信号的逻辑值决定后面要进入哪一个状态,这样的讲法似乎有些抽象,我们先来看看下面的状态图。
图14-1所显示的是一个十字路口的红绿灯控制设计,在一开始时信号Reset 会被设为逻辑’0’,此时state machine会在Reset状态,一直等到信号Reset变成逻辑’1’时,state machine才会进入真正的控制状态。
在之后的三个状态中,我们各定义了一个counter,当进入Red或是之后的Green及Yellow状态时,相对的counter值即会开始递减。
当counter值递减到0时,state machine即会改变到下一个状态。
当然state machine的执行就是依照这种方式进行,但是其中仍有许多的细节是设计者所要注意的,在接下来的章节中,我们会依据实际的例子来介绍state machine的设计方式。
14-1State Machine的建立在这一节我们所举的例子是一个类似检查密码的设计,在一般办公室的门口都会有门禁管制,进门前须先输入一组四个数字的密码,当密码确认无误后门才会打开,除此之外还有更改密码的功用。
我们先来看看其状态图。
在图14-2中一共有四个状态,一开始会维持在idle状态,当要更改或是第一次输入密码时,需要按下一个特殊的“密码更改”按键,此时InpinN信号会变成逻辑’0’的状态,状态机即会进入LoadPin的状态,接着再输入四个数字的密码,密码输入完毕按下“输入”按键,状态机即回到原先的idle状态。
在另一方面,当处于平时状态,有人进入门口要输入密码前,他也必需要按下另一个特殊键“密码输入”,表示之后输入的数字是待验证的密码,此时InData 信号会变成逻辑’0’,于是状态机进入InPin的状态。
在输入四个数字之后,一个内部计数器会计数到3,这时状态机会进入CheckPin的状态。
在CheckPin的状态下会将输入值与密码做一比较,当数值相符时会在按下“输入”按键后,信号nMatch会产生一短暂的逻辑’0’,这就能让门锁打开,若数值不符时,则信号nMatch会一直维持在逻辑’1’的状态。
14-1-1程序代码的撰写接着我们来看看设计的声明,首先是使用到的1ibrary和packages的声明。
library ieee;use ieee.std_logic_1164.all;use ieee.std_logic_arith.all;use ieee.std_logic_unsigned.all;前两行的声明我想已经不用多做解释了。
第三行声明的原因是在后面我们会使用到std_logic_arith package中的一个function----CONV_INTEGER,第四行声明的原因是后面要做输入pin数目的计算,而这种计算只会是正值,所以要声明使用ieee.std_logic_unsigned package。
在1ibrary的声明之后,接下来的是设计接口的声明,我们来看看这个设计的entity声明。
entity PinCheck isport(InD : in std_logic_vector(3 downto 0);InPinN : in std_logic;InData : in std_logic;nMatch : out std_logic;DEnter : in std_logic;Reset : in std_logic;Clk : in std_logic);end PinCheck;在接口的信号中,InD是一4-bit的信号,为输入密码和待测密码所使用,信号InPinN平时是在逻辑’1’的状态,当要更改设计内部的密码时,会先按下“密码更改”的按键,信号InPinN就会产生一逻辑’0’的脉冲,除了产生信号InPinN 外,在按下每一个按键时都会产生一个更短的clk脉冲,提供系统参考。
若是要输入待测的密码,必须先按下“密码输入”按键,此时信号InData会产生逻辑’0’的脉冲,不论是更改密码还是输入密码,在输入四个数值后都要按下“输入”按键,此时信号DEnter会产生逻辑’0’的脉冲,信号Reset是用来重置这个设计内部大部分的计数器,但原先已输入的密码并不会被重置,惟一的输出信号nMatch 会在输入待测密码与原先存储在内部的密码相符时,产生一逻辑’0’的脉冲。
接下来所要看的是architecture声明部分所声明的信号,在此我们先声明了两个数据类型:type MState is ( idle, LoadPin, InPin, CheckPin );type PinArray is array (0 to 3 ) of std_logic_vector ( InD’range );其中MState是以枚举方式将状态机的四种状态都列出来,而PinArray是一个向量型数据类型,可以用来存储一个4组4-bit宽的数值,使用到这两种数据类型的信号有下面三个:signal PinDigit : PinArray;signal InputDigit : PinArray;signal PresentState : MState;其中PinDigit是用来存储修改密码时输入的数值的,而InputDigit则是用来存储待测试的密码,至于PresentState则是用来存储状态机目前的状态值,除了这三个信号使用到自行定义的数据类型外,还有三个使用标准数据类型的信号,分别是:signal PinCnt : integer range 0 to 4;signal InputCnt : integer range 0 to 4;signal nMatchi : std_logic;信号PinCnt及InputCnt所计数的是在修改密码及输入待测密码时,输入数字的数目,信号nMatchi是在输入待测密码时,若是待测密码与实际密码相符,此信号会变成逻辑’0’,否则会一直维持在逻辑’1’。
当内部信号声明完毕后,接下来就是我们的设计内容了,首先要看的是状态机的控制,读者可在分析程序的同时参考图14-2。
process (Reset,Clk)beginif Reset = ‘0’ thenPresentState <= idle;elsif Clk = ‘1’ and Clk’event thenCase PresentState iswhen idle =>if InPinN = ‘0’ thenPresentState <= LoadPin;elsif InData = ‘0’ thenPresentState <= InPin;end if;when LoadPin =>if DEnter = ‘0’ thenPresentState <= idle;end if;when InPin =>if InputCnt = 3 thenPresentState <= CheckPin;end if;when CheckPin =>if DEnter = ‘0’ thenPresentState <= idle;end if;when others =>PresentState <= idle;end case;end if;end process;在上面的程序中,所有的流程受两个信号控制,分别是Reset及Clk。
其中Reset信号有较高的优先权,只要当其active(逻辑’0’)之后,状态机会立刻回到idle状态,所以这是一个异步Reset的设计,当Reset信号为逻辑’1’时,状态机便会等待C1k信号的上升沿出现,我们前面曾经谈过,当每次按下一个按键时会产生两种信号。
第一种是Clk信号,其会在按下任何一个按键时,出现的一个逻辑’0’的脉冲,但其脉冲宽度会比较短。
另一个信号则是按键上所标示的功能,比如说是“密码更改”或是输入的数值,其脉冲宽度会较C1k信号的脉冲宽度来得宽,如此才能使得C1k信号上升沿所读到的一定是正确的逻辑值。
当要更改密码时,必须先要按下“输入密码”的按键,此时除了产生C1k信号的逻辑’0’脉冲外,还会产生一InPinN的信号,当状态机在Idle状态时检测到此信号,便会进入LoadPin状态。
在LoadPin状态时,要等到输入一组四个数字的密码,并且按下“输入”按键后才会再回到Idle状态。
当要输入待测的密码时,要先按下“输入密码”的按键,此时信号InData会产生一逻辑’0’的脉冲。
当状态机检测到这个信号时,会由Idle状态进入InPin状态,在这个状态下,一个内部计数器开始激活,在每次输入数值时计数器会加l,当输入4个数值后,也就是在Clk信号的上升沿检测到计数器的值为3时,状态机便会进入CheckPin状态,在checkPin状态下,状态机会等待“输入”按键被按下,按下后状态机会再回到Idle状态。
接下来我们看看两个计数器的工作方式。
process (Reset,PresentState,Clk)beginif Reset = ‘0’ or PresentState = idle thenPinCnt <= 0;InputCnt <= 0;Elsif Clk = ‘1’ and Clk’event thenif (PresentState = LoadPin and PinCnt < 4) thenPinCnt <= PinCnt+1;PinDigit(CONV_INTEGER(PinCnt)) <= InD;end if;if (PresentState = InPin and InputCnt < 4) thenInputCnt <= InputCnt+1;InputDigit(CONV_INTEGER(InputCnt) <= InD;end if;end if;end process;在Reset信号为逻辑’0’或是PresentState为Idle时,内部计数器PinCnt及InputCnt的值都会被设为0,否则在Clk信号的上升沿时,就会检测PresentState 的状态值,在上面的if语句中,若状态为LoadPin,且PinCnt的值小于4时,表示输入密码的数字还没有达到4个。
此时只要输入一个数字,PinCnt的数值便会加1,并会把输入的数字存到PinDigit数组中,由于成立的条件是计数器PinCnt的值小于4,因此当输入4个数字后,计数器PinCnt的值就已经加到4了,此时即使是再输入其他数字也不会存入PinDigit的内部缓存器中。