LinCoding

【读书笔记】详细解析基于三段式状态机的流水灯

0
阅读(5396)

【主题】:详细解析基于三段式状态机的流水灯

【作者】:LinCoding

【时间】:2016.11.16

       对于学习Verilog的同学来说,状态机并不陌生,状态机分为一段式、两段式和三段式,具体优缺点请大家百度。网上一大把,本文笔者将以一个基于三段式流水灯为例,详细解析下三段式状态机,一方面,加深自己的理解,另一方面,如有见解有误,请大家指出。 

(源码出自CrazyBingo,尊重版权)

`timescale 1ns/1ns
module led_water
#(
	parameter LED_WIDTH = 8
)
(
	input					clk,		//global clock
	input					rst_n,		//global reset
	
	output	reg	[LED_WIDTH-1'b1:0]	led_data
);

第一部分是输入输出定义,没什么好说的。

//-----------------------------------
//200ms counter
localparam	DELAY_TOP = 24'd10_000_000;
//localparam	DELAY_TOP	= 24'd16;		//just for simulation
reg	[23:0]	delay_cnt;
always @ ( posedge clk or negedge rst_n )
begin
	if ( ! rst_n )
		delay_cnt	<= 24'd0;
	else if ( delay_cnt < DELAY_TOP )
		delay_cnt	<= delay_cnt + 1'b1;
	else
		delay_cnt	<= 24'd1;
end
wire	delay_done 	= ( delay_cnt == DELAY_TOP ) ? 1'b1 : 1'b0;

第二部分是一个200ms的计数器,没什么好说的。

运用了上篇文章的一个模式:即,计数器的输出用组合逻辑

//-----------------------------------
//FSM: part 1
reg	[3:0]	current_state;
reg	[3:0]	next_state;
always @ ( posedge clk or negedge rst_n )
begin
	if ( ! rst_n )
		current_state	<= 4'd0;
	else if ( delay_done )
		current_state	<= next_state;
	else
		current_state	<= current_state;
end

 重点来了,三段式有限状态机第一段,很简单,先声明当前态下一态,然后,在计数计到200ms时候,将下一态的值赋予当前态,否则,保持在当前态不变。

也就是说,第一段状态机描述了状态变迁的条件

       有一点需要注意,这里复位时,只将current_state清零,不清零next_state

//-----------------------------------
//FSM: part 2
always @ ( * )
begin
	next_state	= 4'd0;
	case ( current_state )
		4'd0:	next_state	= 4'd1;
		4'd1:	next_state	= 4'd2;
		4'd2:	next_state	= 4'd3;
		4'd3:	next_state	= 4'd4;
		4'd4:	next_state	= 4'd5;
		4'd5:	next_state	= 4'd6;
		4'd6:	next_state	= 4'd7;
		4'd7:	next_state	= 4'd8;
		4'd8:	next_state	= 4'd9;
		4'd9:   next_state	= 4'd10;
		4'd10:  next_state	= 4'd11;
		4'd11:  next_state	= 4'd12;
		4'd12:  next_state	= 4'd13;
		4'd13:  next_state	= 4'd0;
		default:next_state	= 4'd0;
	endcase
end

三段式FSM第二段,有四点需要注意。

1、本段是组合逻辑块,因此always的敏感列表写的是( * ),并且,用的是阻塞赋值语句

2、case的条件是current_state,也就是说,本段是根据当前态来找到下一态,可以说是描述状态变迁的内容(变迁到哪一个态)

3、上电后,由于组合逻辑,而current_state的复位值为0,那么,next_state的值会是1。如下图所示。

blob.png

4、next_state= 4'd0;  这一句,写不写无所谓,看个人喜好

//-----------------------------------
//FSM: part 3
always @ ( posedge clk or negedge rst_n )
begin
	if ( ! rst_n )
		led_data	<= 8'b0000_0001;
	else if ( delay_done )
		case ( next_state )
			4'd0:	led_data	<= 8'b0000_0001;
			4'd1:	led_data	<= 8'b0000_0010;
			4'd2:	led_data	<= 8'b0000_0100;
			4'd3:	led_data	<= 8'b0000_1000;
			4'd4:	led_data	<= 8'b0001_0000;
			4'd5:	led_data	<= 8'b0010_0000;
			4'd6:	led_data	<= 8'b0100_0000;
			4'd7:	led_data	<= 8'b1000_0000;
			4'd8:	led_data	<= 8'b0100_0000;
			4'd9:	led_data	<= 8'b0010_0000;
			4'd10:	led_data	<= 8'b0001_0000;
			4'd11:	led_data	<= 8'b0000_1000;	
			4'd12:	led_data	<= 8'b0000_0100;
			4'd13:	led_data	<= 8'b0000_0010;				
			default:led_data	<= 8'b0000_0001;
		endcase
	else
		led_data	<= led_data;

三段式FSM最后一段,注意三点:

1、case中为next_state,也就是根据next_state,来确定输出。可以说第三段描述的是状态变迁的输出

2、复位时,复位第一态,即:led_data <= 8'b0000_0001;

3、为什么要有delay_done的条件,直接写如下代码不可以吗?

else 
	case ( next_state )
		4'd0:	led_data	<= 8'b0000_0001;
		4'd1:	led_data	<= 8'b0000_0010;
		.......................................

 不可以的,

 (1)如果没有delay_done的限制,那么在复位结束后,led_data会直接根据next_state的值,也就是1,来进行输出,这时,输出的值就是8'b0000_0010,而8'b0000_0001停留的时间几乎没有。见下图。

blob.png

 (2)由于led_data的输出只与next_state有关,导致了边沿不能对齐。见下图

blob.png

 很明显,led_data的输出晚了一个CLK,原因就是由于刚复位结束后,led_data直接输出了next_state等于1的状态,导致后面都延迟了一个状态,唉,这有点说不清楚的感觉,大家还是最好自己仿真一下,看下结果就知道了。

加上delay_done以后,一切都变得很和谐。

blob.png

总结:虽然一个小小的三段式状态机,可是要注意的问题还是很多的

模式总结:

1、FSM第一段:描述状态变迁的条件,只对current_state进行复位清零

2、FSM第二段:描述状态变迁的内容,通通为组合逻辑,case中为current_state,根据当前态来确定下一态的内容。

3、FSM第三段:描述状态变迁的输出,首先复位时,要复位第一态,其次,要有一个使能条件(delay_done),最后case中放的是next_state,是根据next_state来确定输出。


掌握了这几点,状态机真是一点也不难掌握。


以上内容仅为笔者个人见解,如有不同观点,欢迎交流!