因为大多数FPGA内部的触发器数目相当多,又加上独热码状态机( one hot state machine)的译码逻辑最为简单, 所以在设计采用FPGA实现的状态机时往往采用独热码状态机(即每个状态只有一个寄存器置位的状态机)。
建议采用case,casex,或casez语句来建立状态机的模型, 因为这些语句表达清晰明了,可以方便地从当前状态分支转向下一个状态并设置输出。不要忘记写上case语句的最后一个分支default,并将状态变量设为’bx,这就等于告知综合器:case语句已经指定了所有的状态,这样综合器就可以删除不需要
的译码电路,使生成的电路简洁,并与设计要求一致。
如果将缺省状态设置为某一确定的状态(例如:设置default:state = state1)行不行呢?回答是这样做有一个问题需要注意。因为尽管综合器产生的逻辑和设置default:state='bx时相同,但是状态机的Verilog HDL模型综合前和综合后的仿真结果会不一致。为什么会是这样呢?因为启动仿真器时,状态机所有的输入都不确定,因此立即进入default状态,这样的设置便会将状态变量设为state1,但是实际硬件电路的状态机在通电之后,进入的状态是不确定的,很可能不是state1的状态,因此还是设置default: state='bx与实际情况相一致。但在有多余状态的情况下还是应将缺省状态设置为某一确定的有效状态,因为这样做能使状态机若偶然进入多余状态后任能在下一时钟跳变沿时返回正常工作状态,否则会引起死锁。
状态机应该有一个异步或同步复位端,以便在通电时将硬件电路复位到有效状态,也可以在操作中将硬件电路复位(大多数FPGA结构都允许使用异步复位端)。
目前大多数综合器往往不支持在一个always块中由多个事件触发的状态机(即隐含状态机,implicitstate machines), 为了能综合出有效的电路,用Verilog HDL描述的状态机应明确地由唯一时钟触发。 目前大多数综合器不能综合采用Verilog HDL描述的异步状态机。异步状态机是没有确定时钟的状
态机,它的状态转移不是由唯一的时钟跳变沿所触发。
千万不要使用综合工具来设计异步状态机。 因为目前大多数综合工具在对异步状态机进行逻辑优化时会胡乱地简化逻辑,使综合后的异步状态机不能正常工作。 如果一定要设计异步状态机,我们建议采用电路图输入的方法,而不要用Verilog HDL输入的方法。
Verilog HDL中,状态必须明确赋值,通常使用参数(parameters)或宏定义(define)语句加上赋值语句来实现。使用参数(parameters)语句赋状态值见下例:
parameter state1 = 2 'h1, state2 = 2 'h2;
…
current_state = state2; //把current state设置成 2’h2
…
使用宏定义(define)语句赋状态值见下例:
` define state1 2 'h1
`define state2 2 'h2
…
current_state = `state2; //把current state设置成 2 'h2
典型的状态实例:
例1宇宙飞船控制器的状态机
module statmch1( launch_shuttle, land_shuttle, start_countdown,
start_trip_meter, clk, all_systems_go,
just_launched, is_landed, cnt, abort_mission);
output launch_shuttle, land_shuttle, start_countdown,
start_trip_meter;
input clk, just_launched, is_landed, abort_mission,
all_systems_go;
input [3:0] cnt;
reg launch_shuttle, land_shuttle, start_countdown,
start_trip_meter;
//设置独热码状态的参数
parameter HOLD=5'h1, SEQUENCE=5'h2, LAUNCH=5'h4;
parameter ON_MISSION=5'h8, LAND=5'h10;
reg [4:0] present_state, next_state;
always @(negedge clk or posedge abort_mission)
begin
/****把输出设置成某个缺省值,在下面的case语句中
就不必再设置输出的缺省值*******/
{launch_shuttle, land_shuttle, start_trip_meter, start_countdown} =4'b0;
/*检查异步reset的值即abort_mission的值*/
if(abort_mission)
next_state=LAND;
else
begin // if-else-begin
/*如果reset为零,把next_state赋值为present_state*/
next_state = present_state;
/*根据 present_state 和输入信号,设置 next_state
和输出output*/
case ( present_state )
HOLD: if(all_systems_go)
begin
next_state = SEQUENCE;
start_countdown = 1;
end
SEQUENCE: if(cnt==0)
next_state = LAUNCH;
LAUNCH:
begin
next_state = ON_MISSION;
launch_shuttle = 1;
end
ON_MISSION:
//取消使命前,一直留在使命状态
if(just_launched)
start_trip_meter = 1;
LAND: if(is_landed)
next_state = HOLD;
else land_shuttle = 1;
/*把缺省状态设置为'bx(无关)或某种已知状态,使其
在做仿真时,在复位前就与实际情况相一致*/
default: next_state = 'bx;
endcase
end // end of if-else
/*把当前状态变量设置为下一状态,待下一有效时钟沿来到
时当前状态变量已设置了正确的状态值*/
present_state = next_state;
end //end of always
endmodule
综合的一般原则:
1) 综合之前一定要进行仿真,这是因为仿真会暴露逻辑错误,所以建议大家这样做。如果不做仿真,没有发现的逻辑错误会进入综合器,使综合的结果产生同样的逻辑错误。
2) 每一次布局布线之后都要进行仿真,在器件编程或流片之前要做最后的仿真。
3) 用Verilog HDL描述的异步状态机是不能综合的,因此应该避免用综合器来设计,如果一定要设计异步状态机则可用电路图输入的方法来设计。
4) 如果要为电平敏感的锁存器建模,使用连续赋值语句是最简单的方法。
语言指导原则
always块:
上面这一段不太好理解,让我们再解释一下,这也就是说,用always块设计纯组合逻辑电路时, 在生成组合逻辑的always块中参与赋值的所有信号都必需有明确的值[即在赋值表达式右端参与赋值的信号都必需在always @(敏感电平列表)中列出],如果在赋值表达式右端引用了敏感电平列表中没有列出的信号,那么在综合时, 将会为该没有列出信号隐含地产生一个透明锁存器,这是因
为该信号的变化不会立刻引起所赋值的变化,而必须等到敏感电平列表中某一个信号变化时,它的作用才显现出来,也就是相当于存在着一个透明锁存器把该信号的变化暂存起来,待敏感电平列表中某一个信号变化时再起作用, 纯组合逻辑电路不可能做到这一点。这样,综合后所得电路已经不是纯组合逻辑电路了,这时综合器会发出警告提示设计中插入了锁存器。见下例。
input a,b,c;
reg e,d;
always @(a or b or c)
begin
e =d & a & b;
/* 因为d没有在敏感电平列表中,所以d变化时,
e不能立刻变化,要等到a或b或c变化时才体现出来,
这就是说实际上相当于存在一个电平敏感的透
明锁存器在起作用, 把d信号的变化锁存其中 */
d =e | c;
end
赋值: