FPGA之道(55)状态机的HDL模板

举报
李锐博恩 发表于 2021/07/15 01:14:07 2021/07/15
【摘要】 文章目录 前言状态机的HDL模板状态集合的HDL定义概念层级的定义实现层级的定义 if or case?各自特点分析状态选择次态及输出选择 状态的HDL描述方式次态和次中间变量的描述方式Moore 1型输出的描述方式Moore 2型输出的描述方式Mealy 1型输出的描述方式Mealy 2型输出的描述方式 状态机的HDL描述演化一段式状态机模板三段式状态机模板...

前言

上篇博客提供了状态机设计的一般思路,这篇博客仍然摘选自《FPGA之道》,一起来看下作者提供的状态机设计的HDL模板。

状态机的HDL模板

状态集合的HDL定义

状态集合的定义是使用HDL开始状态机描述的第一步,可是该给出怎样的定义才比较恰当呢?下面将给出比较详细的讨论。

概念层级的定义

绝大多数情况下,在设计状态机时,我们所抽象出来的状态应该仅仅是概念层级的,而状态载体的问题应该由编译器去解决。这样做的好处如下:

  • 首先,这样做可以集中项目精力,节省开发时间。因为这便将FPGA开发者从状态集合的具体实现工作中解脱出来,从而将更多的精力和时间用于那些更值得关心的问题。
  • 其次,这样做更有利于代码的阅读和理解。例如,定义状态集合为{idle, work},这样可以很容易从状态名看出其对应的功能,可若定义为{0, 1},可读性就差了很多。
  • 第三,这样做更利于状态机日后的维护与修改。因为一旦状态机的状态集合需要改动的时候,如果仅仅抽象出概念上的状态集合,那么我们只需要关心那些待添加、删除或改动的状态;但如果我们还给出了状态集合的具体实现,那么一个细微的修改就可能造成整个状态集合的重新实现。例如,状态集合为{S0、S1、S2、S3},如果需要添加一个状态,则最终结果可能为{ S0、S1、S2、S3、S4};但是若状态集合为{“00”、“01”、“10”、“11”},可见每个载体均为2比特的数字信号,此时若需要添加一个状态,由于状态个数增加为5个,而2比特的数字信号最多只能表示4个状态,那么不得不重新用3比特的数字信号重新为状态集合分配载体,最后结果类似如下{“000”、“001”、“010”、“011”、“100”},可见除了添加一个新的状态外,原先的4个状态也都不得不随着被修改了。
  • 最后,为状态寻找载体的过程其实就是状态编码,这点我们将在【状态机的编码方式】章节中详细进行介绍。状态编码工作应该是状态集合设计确定后才需要去完成的,而不应该与状态集合同时开始。客观上来说,当我们完成整个状态机的HDL描述之后,并已经确定不再修改的时候,才需要去考虑其状态编码的问题。可是在进行HDL描述最初,就需要我们先定义出状态机的状态集合,因为先声明再使用是绝大部分程序的语法要求,由此可见,从时机上来说,我们也应该仅抽象出概念上的状态。

经过上面的分析可以看出,在进行状态集合定义时,从概念层级出发有着诸多优势,下面就给出比较标准的状态集合定义方法:

-- VHDL format
type <type_name> is (<state0>, <state1>, ……);
signal <state_name> : <type_name>;

// Verilog format
parameter <state0> = 0, <state1> = 1, ……;
reg [x : 0] <state_name>; // x according to states’ number

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

由此可见,在VHDL语法中,采用枚举类型来定义状态集合非常符合概念层级的要求。但对于Verilog语法来说,由于没有枚举类型的变量,因此仅能通过参数语法来逐个给出各个状态的定义,不过此时可忽略赋给每个状态的具体数值,只需要保证任意两个状态参数的值不相等即可。另外,由于Verilog在状态集合定义时使用的是参数语法,因此在状态声明时,也需要根据状态数的多少来调整状态变量的位宽,当然了,这里的位宽并不是最终的状态寄存器位宽,只不过是为了不让HDL在代码编译的时候报错而已。下面,给出一个具体的例子作为参考:

-- VHDL example
type myStateType is (idle, readA, writeB, finish);
signal mystate : myStateType;

// Verilog example
parameter idle = 0, readA = 1, writeB = 0, finish = 1;
reg [1:0] mystate;	

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

关于概念层级的定义,需要牢记的一点是,正因为状态集合的定义仅仅是概念层级上的,所以在后续的HDL代码中,整个状态变量必须作为一个整体来通过比较的方式使用,而不可试图去用它参与运算或窥探其中的某一个具体状态寄存器的值,因为在编译器完成编译之前,状态到底会以几位宽的状态寄存器来承载,每个状态又是什么具体形态等都是不可确定的。仍参考上例,下列HDL描述方式是被禁止的:

-- VHDL example
a <= mystate + 1; -- error
b <= mystate(0); -- error

// Verilog example
assign a = mystate + 1’b1; // error
assign b = mystate[0]; // error

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

实现层级的定义

当然了,如果你非要采用实现层级的状态集合定义,当然也不绝能算错,因为你只是完成了原本由编译器完成的工作而已。不过如果非要自己定义的话,记得要修改编译器中状态编码的相关编译选项,或者为该状态机添加专门的综合约束。以下给出实现层级上的状态集合的一个例子:

-- VHDL example
constant idle : std_logic_vector(1 downto 0) := "00";
constant readA : std_logic_vector(1 downto 0) := "01";
constant writeB : std_logic_vector(1 downto 0) := "10";
constant finish : std_logic_vector(1 downto 0) := "11";
signal mystate : std_logic_vector(1 downto 0);

// Verilog example
parameter idle = "00", readA = "01", writeB = "10", finish = "11";
reg [1:0] mystate;

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

对比概念层级的示例,可以发现VHDL的定义中采用常量的形式给出了每一个状态的具体实现,不过Verilog的定义则几乎没有变化。这是因为,要想给状态赋予实现层级的定义,仅仅从代码上给出具体的定义是没有任何意义的,如果不配合恰当的编译选项或者综合约束的话,代码上的变化全是徒劳。
当然了,还有一种比较粗暴的方法,即不需要修改编译选项或者添加综合约束就可以赋予状态集合现实层级的定义,那就是在涉及到状态的HDL描述中直接使用常数。针对上例,即仅保留如下代码:

-- VHDL example
	signal mystate : std_logic_vector(1 downto 0);

	// Verilog example
	reg [1:0] mystate;

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

然后在具体的功能代码中,直接使用{“00”、“01”、“10”、“11”}这些常数来做描述。不过这种方法是非常不推荐的,因为当需要阅读或修改状态机代码时,这真是一个灾难!
最后,还有一种可以赋予状态集合现实层级定义的方法,只不过设计起来比较麻烦,因为其实这也是做了编译器的工作,详情可以参考【状态机的实现方式】章节中的讨论。

if or case?

在使用HDL对状态机进行描述的时候,无论是状态变迁、还是输出更新,都少不了对条件跳转语句的需求。在HDL语法当中,用于条件跳转的语法主要有if和case两种,那么当描述状态机的时候,该怎么选择才合适呢?

各自特点分析

首先来看一下if和case这两种条件跳转语法的特点吧。在【本篇->编程语法】的相关章节中,我们分别针对VHDL和Verilog介绍了if和case的特点与不同,那就是if是有优先级的,而case是无优先级的。由于有优先级和无优先级之间是可以相互转换的,因此if和case这两种语法并不对立(可参考【本篇->编程语法】的相关章节)。但是从HDL语法简洁性以及最终电路实现上面来看,if和case的效果的确可能会有不同。通常来说,if侧重于描述具有优先级概念的或分支较少的条件跳转,而case侧重于描述没有优先级概念的或者分支较多的条件跳转。因此,根据if和case侧重点的不同,再结合状态机本身的特点,下面给出推荐的使用方式。

状态选择

状态寄存器中存储着当前的状态信息,当前状态不同,状态机对外界输入的反应以及给外界的输出反馈都可能会不同。那么为了保证状态机的行为,必须以状态寄存器的内容为依据,通过条件跳转语句将状态机引导至相应状态的功能代码处。由于状态寄存器中存储的信息与各个状态是一一对应的(出现多余态时除外),所以状态的选择本身没有什么优先级关系,并且状态的数量通常并不少,因此case语法是状态选择时的最优方式。下面给出使用case进行状态选择时候的推荐语法:

-- VHDL format
case (<state_reg_name>) is 
-- <notes for state s0>
when <s0_name> =>
	<statements>;

-- <notes for state s1>
when <s1_name> => <statements>;

……

when others =>
	<statements>;
end case;

// Verilog format
case (<state_reg_name>)
// <notes for state s0>
<s0_name>: 
begin
	<statements>;
end

// <notes for state s1>
<s1_name>: 
begin
	<statements>;
end

……

default: 
begin
	<statements>;
end
endcase

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

关于上述HDL推荐描述语法,有两点需要注意:第一,为了能够便于代码的阅读和理解,在上面的推荐HDL描述中,还加入了必要的注释、空行和段落缩进等代码风格元素。第二,VHDL中的others状态和Verilog中的default状态是描述状态选择时必不可少的,它们的主要目的都是针对于处理多余态的。当然这并不绝对,有时候,如果状态寄存器在很多种取值下的行为都一样,那么也可以利用这两个状态来简化代码编写,不过这是十分不推荐的做法。因为,首先,一旦出现多余态,状态机也会采用相同的动作;其次,还要确保编译选项中的设置能够允许others状态和default状态产生作用才行,详细参见【状态的编码方式】章节。

次态及输出选择

具体进入到对应某个状态的功能代码后,面临的就是次态和输出的生成。
通常来说,每一个状态可选的次态数量都不会太多,并且根据输入情况的不同,各个次态之间可能会存在着优先级关系,例如对于SDRAM控制器来说,如果刷新请求和读请求同时发生,肯定是要先响应刷新请求,否则所有的存储信息都会丢失掉。因此,次态的选择语法以if为主,case为辅。
而对于输出来说,情况其实也是类似的,除非分支特别多,否则还是应该以if描述为主。
最后,需要说明一点,那就是后续关于状态机的描述介绍中,为了条理清晰起见,次态和输出的产生往往是分开描述的,但是通常来说,它们之间往往是存在着一定联系的,因此次态和输出的产生往往是在一个嵌套的if条件结构中同时描述的。

状态的HDL描述方式

在介绍整个状态机的HDL描述之前,先让我们看一下针对某个具体的状态,该如何去描述它的功能。结合【状态机的模型】章节的介绍,可以发现,无论是何种类型的状态机,每个状态的工作都可以划分为两大部分,即产生次态和产生输出,而产生次态和输出的依据便是现态和输入。不过,考虑到我们在设计状态机时其实针对的是抽象的状态而非真正的状态,因此需要对上述概念进行一些修改,即,每个状态的工作都可以划分为三大部分,即产生次态、次中间变量和输出,而产生这些的依据便是现态、中间变量和输入。

次态和次中间变量的描述方式

根据上述概念修改,可知无论是何种类型的状态机,其次态都是由输入、现态和中间变量决定的,而对于中间变量来说,由于它其实也是状态机的状态,所以次中间变量也是由输入、现态和中间变量共同决定的,因此次态和次中间变量的描述形式比较统一,大致如下:

	-- VHDL example
-- 状态内
	<次态> <= funcA(<现态>, <中间变量>, <输入>);
	<次中间变量> <= funcB(<现态>, <中间变量>, <输入>);
-- 状态外
	process(clk)
	begin
		if(clk'event and clk = '1')then <现态> <= <次态>; <中间变量> <= <次中间变量>;
		end if;	
	end process

	// Verilog example	
// 状态内
	<次态> = funcA(<现态>, <中间变量>, <输入>);
	<次中间变量> = funcB(<现态>, <中间变量>, <输入>);
// 状态外
	always@(posedge clk)
	begin
		<现态> <= <次态>;
		<中间变量> <= <次中间变量>;
	end

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

不过对于输出来说,则有着Moore类型与Mealy类型、组合输出形式与寄存输出形式等的不同,所以描述起来也会有所不同,接下来将会详细介绍。

Moore 1型输出的描述方式

Moore 1型的输出与输入无关,且为组合形式输出,描述方式如下:

-- VHDL example
<输出1> <= funcA(<现态>);
<输出2> <= funcB(<现态>, <中间变量>);

// Verilog example	
<输出1> = funcA(<现态>);
<输出2> = funcB(<现态>, <中间变量>);

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Moore 2型输出的描述方式

Moore 2型的输出与输入无关,但为寄存形式输出,描述方式如下:

-- VHDL example
-- 状态内
<次态> <= funcA(<现态>, <中间变量>, <输入>);
<次中间变量> <= funcB(<现态>, <中间变量>, <输入>);
<组合1> <= funcC(<次态>);
<组合2> <= funcD(<次态>, <次中间变量>);
-- 状态外
process(clk)
begin
	if(clk'event and clk = '1')then
		<输出1> <= <组合1>;
		<输出2> <= <组合2>;
	end if;	
end process

// Verilog example
// 状态内	
<次态> = funcA(<现态>, <中间变量>, <输入>);
<次中间变量> = funcB(<现态>, <中间变量>, <输入>);
<组合1> = funcC(<次态>);
<组合2> = funcD(<次态>, <次中间变量>);	
// 状态外
always@(posedge clk)
begin
	<输出1> <= <组合1>;
	<输出2> <= <组合2>;
end	

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

Mealy 1型输出的描述方式

Mealy 1型的输出与输入有关,且为组合形式输出,描述方式如下:

-- VHDL example
<输出1> <= funcA(<现态>, <输入>);
<输出2> <= funcB(<现态>, <输入>, <中间变量>);

// Verilog example	
<输出1> = funcA(<现态>, <输入>);
<输出2> = funcB(<现态>, <输入>, <中间变量>);

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Mealy 2型输出的描述方式

Mealy 2型的输出与输入有关,且为寄存形式输出,描述方式如下:

-- VHDL example
	-- 状态内
	<次态> <= funcA(<现态>, <中间变量>, <输入>);
	<次中间变量> <= funcB(<现态>, <中间变量>, <输入>);
	<组合1> <= funcC(<次态>, <输入>);
	<组合2> <= funcD(<次态>, <输入>, <次中间变量>);
	-- 状态外
	process(clk)
	begin
		if(clk'event and clk = '1')then <输出1> <= <组合1>; <输出2> <= <组合2>;
		end if;	
	end process

	// Verilog example
	// 状态内	
	<次态> = funcA(<现态>, <中间变量>, <输入>);
	<次中间变量> = funcB(<现态>, <中间变量>, <输入>);
	<组合1> = funcC(<次态>, <输入>);
	<组合2> = funcD(<次态>, <输入>, <次中间变量>);	
	// 状态外
	always@(posedge clk)
	begin
		<输出1> <= <组合1>;
		<输出2> <= <组合2>;
	end	

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

状态机的HDL描述演化

本小节将以之前几个小节的讨论为基础,介绍一下如何使用HDL来完整的描述一个状态机。
状态机的HDL描述方法很多,也很灵活,但是出于代码风格、逻辑纯净等方面的考虑,两段式状态机模板是本书所极力推荐的,因此本小节将以两段式状态机为主,附加介绍一下与其相关的一段式状态机和三段式状态机的描述方法作为对比。
注意,段的概念并不等同于进程或程序块的概念,而是指一类具有共性的代码集合。

一段式状态机模板

对于初次尝试使用状态机的思路来进行HDL描述的FPGA开发者们,往往会采用一段式的编程思路,即将关于状态机的所有功能代码全部写在一起。这是非常不好的,因为一段式状态机的描述方式仅适用于一些功能非常简单且输出全部为寄存形式的状态机,而对于一些比较复杂的状态机,这样的描述方法就非常不利于代码的阅读和理解,并且如果需要组合形式输出的话,一段式状态机就显得无能为力了。
现假设抽象出来的状态机功能为:状态机初始处于空闲状态idle,当发现运行信号run有效后便进入工作状态work开始相应的工作,此后若发现工作完成信号done有效后,便再次进入空闲状态idle,等待下一次工作需求。除此以外,为了让外界知道状态机的工作情况,状态机还需输出一个状态信号status,这样外界才可以在状态机空闲的时候才发出工作请求信号run。而对于status信号来说,为0时,表示状态机当前处于空闲状态;为1时,则表示状态机正在工作,无法响应新的工作请求。
那么以下便是运用一段式状态机模板对其进行描述的例子:

-- VHDL example
process(clk)
begin
	if(clk'event and clk = '1')then
		if(rst = '1')then status <= '0'; state <= idle;
		else case (state) is when idle => status <= '0'; if(run = '1')then state <= work; status <= '1'; end if; when work => statue <= '1'; if(done = '1')then state <= idle; status <= '0'; end if; when others => status <= '0'; state <= idle; end case;
		end if;
	end if;
end process

// Verilog example
always@(posedge clk)
begin
	if(rst)
	begin
		status <= 1'b0;
		state <= idle;
	end
	else
	begin
		case (state)
		idle: begin status <= '0'; if(run == 1'b1) begin state <= work; status <= 1'b1; end
		end
		work: begin status <= 1'b1; if(done == 1'b1) begin state <= idle; status <= 1'b0; end
		end
		default: begin nextState <= idle; status <= 1'b0;
		end
		endcase
	end
end

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

通过上例可见,一段式状态机的状态跳转、输入、输出等相关功能全部集中在一起,因此它们都位于时序逻辑进程中,因此无法给出组合形式的输出。

三段式状态机模板

相对于一段式状态机,三段式状态机的描述方法使得整个状态机的结构显得非常清晰,因此三段式状态机也是目前比较受欢迎的一种状态机描述模板。三段式状态机的思想,其实就是将整个状态机拆成三大部分逐个进行描述,即状态跳转部分、次态生成部分、输出生成部分。其中,状态跳转部分仅仅完成将次态更新为现态的工作;次态生成部分其实描述的就是状态转移函数,它根据现态和输入计算出次态;输出生成部分仅仅集中于状态机的输出工作,它也是根据现态和输入计算得出的。以下便是一个运用三段式状态机模板对上一小节的例子进行重新描述的结果:

-- VHDL example
-- section 1, state change 
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
-- section 2, next state generate
process(state, run, done)
begin
	case (state) is 
	when idle =>
		if(run = '1')then nextState <= work;
		else nextState <= idle;
		end if;
	when work =>
		if(done = '1')then nextState <= idle;
		else nextState <= work;
		end if;
	when others =>
		nextState <= idle;
	end case;
end process
-- section 3, output generate
process(state)
begin
	case (state) is 
	when idle =>
		status <= '0';
	when work =>
		status <= '1';
	when others =>
		status <= '0';
	end case;
end process

// Verilog example
//section 1, state change
always@(posedge clk)
begin
	if(rst)
	begin
		state <= idle;
	end
	else
	begin
		state <= nextState;
	end
end
//section 2, next state generate
always@(state, run, done)
begin
	case (state)
	idle: 
	begin
		if(run == 1'b1)
		begin nextState = work;
		end
		else
		begin nextState = idle;
		end
	end
	work: 
	begin
		if(done == 1'b1)
		begin nextState = idle;
		end
		else
		begin nextState = work;
		end
	end
	default: 
	begin
		nextState = idle;
	end
	endcase
end
//section 3, output generate
always@(state)
begin
	case (state)
	idle: 
	begin
		status = 1'b0;
	end
	work: 
	begin
		status = 1'b1;
	end
	default: 
	begin1
		status = 1'b0;
	end
	endcase
end

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108

结合【状态机的模型】章节的介绍可以看出,三段式状态机的描述其实最适合纯Moore型状态机的描述(注,此处所说的纯Moore型不是真正的Moore型,而是抽象的moore型,因为输出与中间变量无关),但是如果用于其它类型的状态机描述,则存在一些小缺陷。
首先,如果状态机的输出不仅仅和状态相关,还和中间变量、甚至输入有关,那么就会造成第二段次态生成部分与第三段输出生成部分的代码重复性非常之大。
其次,由于第二段次态生成部分和第三段输出生成部分都和现态相关紧密,因此一旦状态机的状态集合由于修改发生变化,那么这两段代码也必须同时进行修改,故修改的重复性工作较多。
综上所述,我们通常更推荐两段式状态机的描述方法,详见下一小节。

两段式状态机模板

两段式状态机的描述方法适用于所有类型的状态机,相对于三段式状态机,两段式状态机更易于修改和扩充,且同样具有较高的代码可读性。两段式状态机的思想,其实就是将整个状态机拆成两大部分逐个进行描述,即纯净的时序部分与纯净的组合部分,这种划分思路主要是基于【本篇->编程思路->编写纯净的组合或时序逻辑】章节的分析得来的。其中,纯净的时序部分主要负责完成状态的跳转、中间变量的更新以及输出的寄存工作;而纯净的组合部分主要完成状态转移函数的实现、次态的生成、组合输出的生成工作。仍以【一段式状态机模板】小节的状态机需求为例,运用两段式状态机模板对其进行重新描述的结果如下:

-- VHDL example
-- section 1, sequential logic 
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
-- section 2, combinational logic
process(state, run, done)
begin
	case (state) is 
	when idle =>
		status <= '0';
		if(run = '1')then nextState <= work;
		else nextState <= idle;
		end if;
	when work =>
		status <= '1';
		if(done = '1')then nextState <= idle;
		else nextState <= work;
		end if;
	when others =>
		status <= '0';
		nextState <= idle;
	end case;
end process

// Verilog example
//section 1, sequential logic
always@(posedge clk)
begin
	if(rst)
	begin
		state <= idle;
	end
	else
	begin
		state <= nextState;
	end
end
//section 2, combinational logic
always@(state, run, done)
begin
	case (state)
	idle: 
	begin
		status = 1'b0;
		if(run == 1'b1)
		begin nextState = work;
		end
		else
		begin nextState = idle;
		end
	end
	work: 
	begin
		status = 1'b1;
		if(done == 1'b1)
		begin nextState = idle;
		end
		else
		begin nextState = work;
		end
	end
	default: 
	begin
		status = 1'b0;
		nextState = idle;
	end
	endcase
end

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84

对比这三个小节的HDL范例,可以发现两段式的代码量几乎和一段式相同,但其代码却比三段式更加具有可读性(因为仅用查看一处便可知道当前状态的工作行为,而三段式需要查看两处),并且更加易于修改和扩展,因此是本书最为推荐的状态机HDL描述模板。

具有使能的状态机HDL描述

具有使能控制的状态机是状态机中的一种特例,它是指状态机中的某一个输入,对状态机的工作行为有着决定性的使能和冻结作用。在这种情况下,通过将该输入信号看做整个状态机的使能,便可以不对使能无效的情况抽象任何状态或者在使能无效的时候不改变状态,这样往往能够设计出结构更为简洁、逻辑更为清晰的状态机模型。
之所以在这里单独利用一个章节来对这种具有使能的状态机的HDL描述方法进行介绍,是因为这种情况确实比较常见,主要原因是由于FPGA中的数据传输往往都具有一定的离散性或间歇性,数据信号需要配合使能信号一起传递:当使能信号有效时,才表示当前数据也是有效的;否则表示当前数据为无效数据,不必处理。那么针对类似这种的情况,在抽象状态机时,比较常用的描述方法有两种——次态冻结式描述和现态冻结式描述,分别介绍如下:

次态冻结式状态机描述

次态冻结式状态机,顾名思义就是在产生次态的时候考虑到该使能输入信号,即当使能无效时,状态机的次态始终为当前状态,就好像次态被冻结了一样,顾得此名。次态冻结式状态机的描述方式主要针对两段式状态机的组合逻辑部分进行一些更为具体的描述,参考如下:

-- VHDL format
	case (<现态>) is 
	-- <notes for state s0>
	when <s0_name> =>
		if (<enable> = '1') then <次态> <= funcA(<现态>, <中间变量>, <输入>);
		else <次态> <= <s0_name>;
		end if;
		<statements for output>;

	-- <notes for state s1>
	when <s1_name> =>
		if (<enable> = '1') then <次态> <= funcB(<现态>, <中间变量>, <输入>);
		else <次态> <= <s1_name>;
		end if;
		<statements for output>;

	……

	when others =>
		<statements>;
	end case;

	// Verilog format
	case (<现态>)
	// <notes for state s0>
	<s0_name>: 
	begin
		if (<enable> == 1'b1)
		begin <次态> = funcA(<现态>, <中间变量>, <输入>);
		end
		else
		begin <次态> = <s0_name>;
		end
		<statements for output>;
	end

	// <notes for state s1>
	<s1_name>: 
	begin
		if (<enable> == 1'b1)
		begin <次态> = funcB(<现态>, <中间变量>, <输入>);
		end
		else
		begin <次态> = <s1_name>;
		end
		<statements for output>;
	end

	……

	default: 
	begin
		<statements>;
	end
	endcase

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

从上述描述中,可以看出这其实这就是普通的状态机描述方式,只不过在抽象状态的时候兼顾考虑到了使能信号的特性。由于这样的描述方式对使能输入不做特殊处理,因此几乎状态机的每个状态中都需要添加关于该使能信号的判断逻辑,而这无形中增加了这些状态的逻辑复杂度,同时也会使HDL代码的描述显得较为繁琐和啰嗦。

现态冻结式状态机描述

与次态冻结式的原理不同,现态冻结式状态机对于状态机的次态不做任何限制,而是利用使能输入信号作为状态寄存器更新的使能标志。这样一来,当使能信号有效时,状态机寄存器(包括中间变量寄存器)才会发生更新,而当使能信号无效时,无论输入导致次态如何变化,这种变化都无法导致状态机的现态发生跳转,就好像现态被冻结了一样,顾得此名。鉴于此,现态冻结式状态机主要修改两段式状态机的纯时序部分,其描述方式参考如下:

-- VHDL example
-- section 1, sequential logic 
process(clk)
begin
	if(clk'event and clk = '1')then
		if(rst = '1')then <现态> <= <初态>;
		else if (<enable> = '1') then <现态> <= <次态>; end if;
		end if;
	end if;
end process
-- section 2, combinational logic
process(<现态>, <中间变量>, <输入>)
begin
	case (<现态>) is 
	-- <notes for state s0>
	when <s0_name> =>
		<次态> <= funcA(<现态>, <中间变量>, <输入>);
		<statements for output>;

	-- <notes for state s1>
	when <s1_name> =>
		<次态> <= funcB(<现态>, <中间变量>, <输入>);
		<statements for output>;

	……

	when others =>
		<statements>;
	end case;
end process

// Verilog example
//section 1, sequential logic
always@(posedge clk)
begin
	if(rst)
	begin
		<现态> <= <初态>;
	end
	else
	begin
		if (<enable> == 1'b1) begin <现态> <= <次态>;
		end
	end
end
//section 2, combinational logic
always@(<现态>, <中间变量>, <输入>)
begin
	case (<现态>)
	// <notes for state s0>
	<s0_name>: 
	begin
		<次态> = funcA(<现态>, <中间变量>, <输入>); <statements for output>;
	end

	// <notes for state s1>
	<s1_name>: 
	begin
		<次态> = funcB(<现态>, <中间变量>, <输入>); <statements for output>;
	end

	……

	default: 
	begin
		<statements>;
	end
	endcase
end

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

请注意上述描述中,状态机每个状态中的部分。如果产生状态机输出的组合逻辑可以设计成与使能输入无关,或者仅有少数几个状态的输出与使能输入有关,那么采用现态冻结式的描述方法能够令代码的描述更加简洁,也会降低每个状态的逻辑复杂度,因为次态冻结需要使用使能信号对每个状态进行修改,而现态冻结仅需对状态寄存器一处使用使能信号进行更新控制即可。但是,当状态机中大部分状态的输出都跟该使能输入紧密相关时,那么大部分的中就会加入涉及到使能输入的if条件结构,结合【if or case?->次态及输出选择】小节中的描述,可知此时中的if条件结构也可以用来产生次态,因此,此时更适合使用次态冻结式状态机的描述方式。

文章来源: reborn.blog.csdn.net,作者:李锐博恩,版权归原作者所有,如需转载,请联系作者。

原文链接:reborn.blog.csdn.net/article/details/104385818

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。