FPGA之道(57)状态机的实现方式

前言

本文摘自《FPGA之道》,这是这本书中描述状态机的最后一部分,一起来看看作者对于状态机的实现方式的见解。

状态机的实现方式

编译器中,和状态机实现相关的选项通常叫做fsm_style,根据该选项的设置不同,状态机实现时所基于的载体也不同。通常来说,我们在编写FPGA中的状态机代码时,不需要考虑这么具体和细节的东西,不过当涉及到一些高性能的状态机开发时,下面的内容还是会对我们有所帮助的。

基于LUT的实现方式浅析

基于LUT的实现方式是状态机最基本的实现方式,也是比较常用的一种实现方式。从【状态机的HDL模板->状态机的HDL描述演化->两段式状态机模板】小节中,我们可以看出状态机可以分成两大部分——组合逻辑和纯时序逻辑(事实上对于任何时序逻辑来说都是如此)。对于纯时序逻辑来说,仅仅完成现态、中间变量的更新以及某些输出的寄存,所以这部分通常在整个状态机结构中所占的比例较小,因此组合逻辑在状态机的结构中占据了较大部分比例。而根据【知己知彼篇->FPGA内部资源介绍->逻辑资源块】小节中的介绍,可以发现LUT是组合逻辑的最主要载体,因此,在默认情况下,状态机的实现方式都是基于LUT的。
使用LUT实现状态机,一个比较原始的思路如下:
在这里插入图片描述
上图中,我们用LUT实现每一个状态下的状态转移函数和输出函数,然后再使用LUT实现对现态的译码电路,从而控制MUX选择对应现态的那个状态逻辑的次态和输出。当然了,上述数字电路原理结构图中肯定存在着非常多的冗余,编译器在实现的时候肯定会做大量的优化,在这里我们主要关注用LUT实现状态机的一个基本思路,而不去讨论该如何利用LUT实现出没有冗余的状态机数字电路,因为这是编译器的工作。

基于RAM的实现方式浅析

在【共同语言篇->数字逻辑电路基础知识->数字逻辑功能单元->小规模集成组合逻辑单元->查找表】小节中,介绍过RAM其实就是一个LUT,因此既然状态机可以基于LUT实现,那么就一定可以基于RAM来实现。
在【本篇->编程思路->数据的存储->数据存储的载体->查找表】小节中,介绍过某些LUT其实就相当于小型的SRAM,因此如果基于RAM的实现方式指的是这些小RAM的话,那其实它与基于LUT的状态机实现思路几乎没有什么差别。不过在【知己知彼篇->FPGA内部资源介绍->BLOCK RAM】小节中,介绍了FPGA内部集成的一种称为BRAM的存储资源,如果利用该资源为基础来实现状态机的话,其思路就有所不同了,下面就来分析这种基于BRAM的状态机实现方式。

可行性分析

RAM其实就是一个LUT,那么BRAM不过是一个容量较大一些的LUT罢了,因此基于BRAM来实现状态机电路肯定是可行的。不过,如果参考【基于LUT的实现方式浅析】小节中的方式,用多个BRAM实现多个不同的状态转移函数等组合逻辑,实在是“杀鸡用了宰牛刀”,非常的浪费,因此需要寻求适合BRAM特征的状态机实现方式才行。

实现思路简介

无论处于哪个状态,状态机的工作都可以总结成这样一个组合逻辑——其输入为状态机的现态、中间变量和输入,输出为状态机的次态、次中间变量和输出。因此,如果我们将BRAM看做一个大的LUT,然后利用BRAM实现该组合逻辑的话,就可以这样做:

情况一:若该BRAM的读端口是同步的,即在时钟有效沿更新输出的话,那么
令BRAM的地址总线 = 现态+中间变量+输入;
令BRAM的存储内容 = 次态+次中间变量+次态对应输出;
此时,数据总线上的“次态+次中间变量”直接反馈到BRAM的地址端即可,不过该BRAM仅适合描述Moore类型的状态机。注意由于BRAM的输出仅在时钟有效边沿才会更新,因此这类状态机仅能具有同步复位信号。
情况二:若该BRAM的读端口是异步的,即跟随输入而变化,那么
令BRAM的地址总线 = 现态+中间变量+输入;
令BRAM的存储内容 = 次态+次中间变量+输出;
此时,数据总线上的“次态+次中间变量”必须通过外部寄存器后才反馈到BRAM的地址端,该BRAM适合于描述Mealy和Moore类型的状态机。注意,该类状态机可以具有同步复位,也可以具有异步复位,因为具有寄存器用来保存状态和中间变量。不过如果不想使用寄存器的话,那么也可将时钟信号作为地址总线中的一位,如果再将复位信号作为地址总线中的一位,那么仅用一块BRAM就可以实现具有异步复位功能的状态机了,不过此时的状态机对于时钟信号的毛刺会非常敏感,而且位宽多1bit,对RAM的存储容量要求就大一倍,因此不建议采用。

这样一来,只要参考上述两种情况,预先在BRAM中存储好相应的内容,那么BRAM此时就相当于一个状态机。现以【状态机的HDL模板->状态机的HDL描述演化->一段式状态机模板】小节中所描述的状态机为例,若采用基于BRAM的方式来实现,其设计思路如下:
首先,确定状态机的状态表示、中间变量和输入输出。假设采用binary的形式来对状态集合进行编码,那么状态state仅有1bit,两种取值情况对应idle = 0,work = 1;该状态机没有中间变量,故忽略该项;不考虑复位的前提下,状态机的输入有两个,分别是run信号和done信号,都只有1bit;状态机的输出为status,位宽仅为1bit,由于此处假设状态集合采用binary的形式编码,因此status可以和state合并。
其次,由于该例为Moore型的状态机,那么选择在读时钟上升沿有效时才更新输出端数据的BRAM来填写其“地址—内容”表格。填写方式为:先列举出所有的地址情况,然后对应状态机的功能更行为,得出每个地址应该对应的数据内容,填写完称的表格如下:
地址	内容Addr[0]	Addr[1]	Addr[2]	datastate	run	done	State/status0	0	0	00	0	1	00	1	0	10	1	1	11	0	0	11	0	1	01	1	0	11	1	1	0
最后,依据情况一中的讨论,利用上述BRAM绘制最终的状态机电路原理图如下:
在这里插入图片描述
注意,上述状态机中的同步复位方式,也可以采用将复位信号作为BRAM地址中的一位来实现,不过此时之前的“地址—内容”表格将会扩大一倍。

采用基于BRAM的状态机实现选项的好处
如果在编译器中,配置为采用基于BRAM的状态机实现方式,相比于普通的基于LUT的实现方式,是有很多好处的,列举如下:
1、更利于实现大型的状态机。因为BRAM的容量很大。
2、更利于使状态机具有较高的性能。这是由于BRAM质地紧密,由输入到输出的时间性能较好,因此无论状态机的功能多么复杂,其所能达到的时钟极限频率都是类似的。
3、更利于加强状态机的抗干扰能力。这是由BRAM的结构特性决定的。
4、更利于降低状态机的功耗。功耗主要源自数字信号0、1之间的切换,如果基于BRAM的话,除了地址和数据输出部分外,BRAM内部几乎没有数字信号切换,但如果使用LUT的话,那么整个状态机内部的逻辑将会有很多处都发生数字信号切换。

基于BRAM自行设计状态机的好处

如果不需要自行设计出基于BRAM的状态机的话,那么【状态机的实现方式】章节中的所有内容均了解即可,因为有编译器来帮我们做这些事情,否则的话,之前的分析就都可以派上用场。自行设计基于BRAM的状态机有以下两点好处:
首先,便于实现自定义编码、解码算法。例如可以在BRAM中存储具有纠错功能的编码,然后在状态反馈回路中添加解码功能即可。
其次,可以实现状态机的动态可配置功能。这是因为BRAM还提供了数据写入端口,无论是在任何时候,只要利用该端口修改BRAM中的数据内容,就可以动态的更改状态机的功能。因此,只要若干个状态机所抽象出来的BRAM地址总线和数据总线具有兼容性,那么是可以通过BRAM的数据写入端口在FPGA上电运行过程中,动态的让BRAM的功能行为在这些状态机间进行切换。

显式状态机与隐式状态机

概念简介

如果把组合逻辑看成只有一个状态的状态机,那么FPGA项目的设计其实就是状态机的设计,不过你也许会觉得奇怪,因为并不是所有描述FPGA项目的HDL功能代码段落中都包含有状态集合的定义以及两段式状态机的模板结构,甚至在有些时候,整个FPGA项目中都找不到与状态机概念相关的语法定义和结构,这又是为什么呢?
原因很简单,状态机的定义是输出不仅跟输入有关,还和电路目前的状态有关,因此,凡是符合上述定义的逻辑电路都是状态机。而状态集合的定义,两段式的状态机模板结构等,只不过是使用HDL来描述人脑所抽象出来的状态机的一种方法而已,只不过这种方法能够胜任所有的状态机描述,并且通常来说运用比较简单、方便,且通常能够将时序电路的功能描述的更加清晰、更加易懂。但是这些好处也并不是完全绝对的,对于有些结构不是特别复杂的状态机,往往采用更加直接的描述方式效果会更好。
因此,我们称具有状态集合定义以及使用了状态机HDL模板的状态机描述方式为显式的状态机,而称那些不具有显示状态机HDL描述特征的状态机描述方式为隐式状态机。
下面,就让我们通过一个例子来具体体会一下显式状态机与隐式状态机的描述区别。假设待描述的时序电路为:
时钟信号为clk;
输入为1bit宽的数据信号serialDataIn,与clk同步;
输出为1bit宽的指示信号pattenFound,也与clk同步;
该时序电路的功能为针对serialDataIn数据的模式检测,当且仅当发现连续出现3个数字信号1后,从pattenFound端口发出仅持续1个时钟周期的模式匹配成功指示信号。注意,连续‘1’的个数不能多,也不能少,必须正好为3个,例如4个或2个都不算匹配成功。 那么,下面两个小节就分别利用显式状态机和隐式状态机的描述方式来解决上述时序电路的HDL描述问题,请大家仔细进行对比。

显式状态机的描述示例

按照该时序电路功能的描述,可知必须顺序接收到01110这样的序列后,才算是检测到了相应的模式,因此很容易联想到用五个状态来分别代表目前接收到了序列0、01、011、0111、01110,那么经过简单的分析和抽象,可得该时序逻辑对应的状态转移图如下:
在这里插入图片描述
从上图可以看出该状态机的工作原理为:
初始时位于idle状态,该状态输出恒为0,当输入为1时,次态仍为idle;当输入为0时,表示匹配上序列01110中的第一个数,次态为match1。
Match1状态表示已经匹配了模式序列中的一个数,该状态输出恒为0,当输入为1时,次态为match2;当输入为0时,次态仍为match1,因为之前的匹配失败,但输入的0使得新一次的匹配还是匹配了一个数。
Match2状态表示已经匹配了模式序列中的两个数,该状态输出恒为0,当输入为1时,次态为match3;当输入为0时,次态为match1,因为之前的匹配失败,但输入的0使得新一次的匹配还是匹配了一个数。
Match3状态表示已经匹配了模式序列中的三个数,该状态输出恒为0,当输入为1时,次态为match4;当输入为0时,次态为match1,因为之前的匹配失败,但输入的0使得新一次的匹配还是匹配了一个数。
Match4状态表示已经匹配了模式序列中的四个数,当输入为0时,次态为match1,此时匹配成功,输出应该为1,并且由于输入的0使得下一次的匹配还是匹配了一个数;当输入为1时,次态为idle,因为匹配失败,所以输出为0,并且输入的1使得新一次的匹配重头开始。由于Match4状态的次态不会是其自身,因此一旦匹配,输出1仅会持续一个时钟周期,满足要求。
经过简单的分析,可知上述状态机中并不存在冗余,因此采用两段式的HDL模板对上述状态机描述的结果如下:

-- VHDL example
	type stateType is (idle, match1, match2, match3, match4);
	signal state, nextState : stateType;
	-- state register update
	process(clk)
	begin
		if(clk'event and clk = '1')then
			if(rst = '1')then
				state <= idle;
			else	
				state <= nextState;
			end if;
		end if;
	end process;
	-- next state and output update
	process(state, serialDataIn)
	begin
		pattenFound <= '0';
		case (state) is 
		-- no character matched
		when idle =>
			if(serialDataIn = '0')then
				nextState <= match1;
			else
				nextState <= idle;
			end if;
			
		-- 1 character matched
		when match1 =>
			if(serialDataIn = '1')then
				nextState <= match2;
			else
				nextState <= match1;
			end if;
			
		-- 2 character matched
		when match2 =>
			if(serialDataIn = '1')then
				nextState <= match3;
			else
				nextState <= match1;
			end if;
			
		-- 3 character matched
		when match3 =>
			if(serialDataIn = '1')then
				nextState <= match4;
			else
				nextState <= match1;
			end if;
			
		-- 4 character matched
		when match4 =>
			if(serialDataIn = '0')then
				nextState <= match1;
				pattenFound <= '1';
			else
				nextState <= idle;
			end if;
		
		when others =>
			nextState <= idle;
		end case;
	end process;

	// Verilog example
	parameter idle = 0, match1 = 1, match2 = 2, match3 = 3, match4 = 4;
	reg [2:0] state, nextState;
	// state register update
	always@(posedge clk)
	begin
		if(rst)
		begin
			state <= idle;
		end
		else
		begin
			state <= nextState;
		end
	end
	// next state and output update
	always@(state, serialDataIn)
	begin
		pattenFound = 1'b0;
		case (state)
		// no character matched
		idle: 
		begin
			if(serialDataIn == 1'b0)
			begin
				nextState = match1;
			end
			else
			begin
				nextState = idle;
			end
		end
		
		// 1 character matched
		match1: 
		begin
			if(serialDataIn == 1'b1)
			begin
				nextState = match2;
			end
			else
			begin
				nextState = match1;
			end
		end
		
		// 2 character matched
		match2: 
		begin
			if(serialDataIn == 1'b1)
			begin
				nextState = match3;
			end
			else
			begin
				nextState = match1;
			end
		end
		
		// 3 character matched
		match3: 
		begin
			if(serialDataIn == 1'b1)
			begin
				nextState = match4;
			end
			else
			begin
				nextState = match1;
			end
		end
		
		// 4 character matched
		match4: 
		begin			
			if(serialDataIn == 1'b0)
			begin
				nextState = match1;
				pattenFound = 1'b1;
			end
			else
			begin
				nextState = idle;
			end
		end
		
		default: 
		begin
			nextState = idle;
		end
		endcase
	end

隐式状态机的描述示例

描述时序逻辑的功能,并不是仅有显式状态机描述思路这一种方式。只要仔细分析该时序电路,可以发现其实它就是完成一个模式匹配的功能,即一旦发现序列中出现01110这样的连续结构就发出一个周期高电平的pattenFound信号作为指示,由于这样的模式不可能被连续匹配,那么我们只需要使用一个深度为5的移位寄存器来存储连续5个serialDataIn的输入值,进而再通过一个简单的判断逻辑即可输出匹配结果。那么现在上述时序逻辑的功能可以用HDL描述如下:

-- VHDL example
	signal shift : std_logic_vector(4 downto 0);
	process(clk)
	begin
		if(clk'event and clk = '1')then
			if(rst = '1')then
				shift <= (others => '0');
			else	
				shift <= shift(3 downto 0) & serialDataIn;
			end if;
		end if;
	end process;
	-- output update
	pattenFound <= '1' when shift = "01110" else
  '0';

	// Verilog example
	reg [4:0] shift;
	// shift register update
	always@(posedge clk)
	begin
		if(rst)
		begin
			shift <= 5'b0;
		end
		else
		begin
			shift <= {shift[3:0], serialDataIn};
		end
	end
	// output update
	assign pattenFound = (shift == 5'b01110) ? 1'b1 : 1'b0;

对比显式状态机的描述方法,可见对于本例,使用隐式状态机的方法进行描述,代码更加简练,同时思路也很清晰。因此,显式状态机的描述思路虽然很好,但也不要将思路仅仅局限于此,很多时候,对于描述一些逻辑关系较为简单的功能,我们完全可以放开思想,尝试使用没有固定形式、固定原则可遵循的隐式状态机思路去更加巧妙的解决它们。但是若描述一些逻辑关系较为复杂的功能,还是强烈建议使用显式状态机的描述思路的。

发布了806 篇原创文章 · 获赞 1541 · 访问量 151万+

猜你喜欢

转载自blog.csdn.net/Reborn_Lee/article/details/104396620
今日推荐