Verilog手撕代码(3)状态机

状态机,全称是有限状态机( Finite State Machine,缩写为FSM),是一种在有限个状态之间按一定规律转换的时序电路,可以认为是组合逻辑和时序逻辑的一种组合。 状态机通过控制各个状态的跳转来控制流程,使得整个代码看上去更加清晰易懂,在控制复杂流程的时候,状态机优势明显,因此基本上都会用到状态机,如SDRAM控制器等。

状态机相当于一个控制器,它将一项功能的完成分解为若干步,每一步对应于二进制的一个状态,通过预先设计的顺序在各状态之间进行转换,状态转换的过程就是实现逻辑功能的过程。

根据状态机的输出是否与输入条件相关,可将状态机分为两大类,即**摩尔(Moore)型状态机和米勒(Mealy)**型状态机。

摩尔(Moore)型状态机

Moore状态机:组合逻辑的输出只取决于当前状态

在这里插入图片描述

米勒(Mealy)型状态机

Mealy状态机:组合逻辑的输出不仅取决于当前状态,还取决于输入状态。

在这里插入图片描述

状态机写法

根据状态机的实际写法,状态机还可以分为一段式、二段式和三段式状态机。

  1. 一段式:整个状态机写到一个always模块里面,在该模块中既描述状态转移,又描述状态的输入和输出。不推荐采用这种状态机,因为从代码风格方面来讲,一般都会要求把组合逻辑和时序逻辑分开;从代码维护和升级来说,组合逻辑和时序逻辑混合在一起不利于代码维护和修改,也不利于约束。
  2. 二段式:用两个always模块来描述状态机,其中一个always模块采用同步时序描述状态转移;另一个模块采用组合逻辑判断状态转移条件,描述状态转移规律以及输出。 不同于一段式状态机的是, 它需要定义两个状态, 现态和次态,然后通过现态和次态的转换来实现时序逻辑。
  3. 三段式:在两个always模块描述方法基础上,使用三个always模块,一个always模块采用同步时序描述状态转移,一个always采用组合逻辑判断状态转移条件,描述状态转移规律,另一个always模块描述状态输出(可以用组合电路输出,也可以时序电路输出)。

实际应用中三段式状态机使用最多,因为三段式状态机将组合逻辑和时序分开,有利于综合器分析优化以及程序的维护;并且三段式状态机将状态转移与状态输出分开,使代码看上去更加清晰易懂,提高了代码的可读性, 推荐大家使用三段式状态机, 本文也着重讲解三段式。
三段式状态机的基本格式是:

  • 第一个always语句实现同步状态跳转;
  • 第二个always语句采用组合逻辑判断状态转移条件;
  • 第三个always语句描述状态输出(可以用组合电路输出,也可以时序电路输出)。

实例分析

可乐售卖系统:可乐机每次只能投入 1 枚 1 元硬币,且每瓶可乐卖 3 元钱,即投入 3 个硬币就可以让可乐机出可乐,如果投币不够 3 元想放弃投币需要按复位键,否则之前投入的钱不能退回。

信号含义
clk(输入信号)同步状态机,那么时钟是肯定少不了的,这里设定时钟是50MHz;
rst_n(输入信号)系统复位,这里设定位低电平有效;
money(输入信号)系统肯定需要一个复位,这里设定位低电平有效;
cola(输入信号)系统肯定需要一个复位,这里设定位低电平有效;

Moore 状态机(输出和输入无关)的状态转移图:
1/0:前面的1代表输入,后面的0代表输出

在这里插入图片描述

Mealy 状态机(输出和输入无关)的状态转移图:
在这里插入图片描述
有两种状态机的状态转移图可知:

  • Mealy 状态机比Moore状态机的状态个数要少
  • Mealy 状态机比Moore状态机的输出要早一个时钟周期

代码

状态机的编码方式一般有三种,各有优劣,独热码算是用的比较多的:

  • 独热码
  • 格雷码
  • 二进制码

一段式和二段式在实际工程应用中相对较少,在此不再赘述,本文将详细介绍三段式状态机代码:

Moore型(摩尔型)三段式状态机

verilog代码:

module fsm_moore_3(
	input clk,
	input rst_n,
	input money,
	output reg cola
);

parameter IDLE = 4'b0001,
			 one  = 4'b0010,
			 two  = 4'b0100,
			 three= 4'b1000;
			 
reg [3:0]c_state;
reg [3:0]n_state;

//状态机第一段,时序逻辑描述状态转移
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)
		c_state <= 4'b0;
	else 
		c_state <= n_state;
end 

//状态机第二段,组合逻辑描述状态转移条件
always@(*)begin
	case(c_state)
		IDLE:n_state = money?one:IDLE;
		one :n_state = money?two:one;
		two :n_state = money?three:two;
		three:n_state = money?one:IDLE;
		default:n_state = IDLE;
	endcase
end

//状态机第三段,时序逻辑描述输出
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)
		cola <= 1'b0;
	else if(c_state == three)
		cola <= 1'b1;
	else	
		cola <= 1'b0;
end

endmodule

状态转移图:
在这里插入图片描述

Testbench:

`timescale 1ns/1ns
module fsm_moore_3_tb();
	reg clk;
	reg rst_n;
	reg money;
	wire cola;	
	
	fsm_moore_3 u0(
		.clk(clk),
		.rst_n(rst_n),
		.money(money),
		.cola(cola)
	);
	
	always #10 clk = ~clk;
	
	initial begin
		clk = 0;
		rst_n = 0;
		money = 0;
		#5
		rst_n = 1;
		#25
		money = 1;
		#40
		money = 0;
		#20
		money = 1;
		#80
		money = 0;
		#200
		$stop;
		
	end
	
	//状态机名称查看

	reg [39:0]	state_name_cur;			//每字符8位宽,这里最多5个字符40位宽
	reg [39:0]	state_name_next;		   //每字符8位宽,这里最多5个字符40位宽
	
	always@(*)begin
		case(u0.c_state)
			4'b0001:state_name_cur = "IDLE";
			4'b0010:state_name_cur = "one";
			4'b0100:state_name_cur = "two";
			4'b1000:state_name_cur = "three";
			default:state_name_cur = "IDLE";
		endcase
	end
	
	always@(*)begin
		case(u0.n_state)
			4'b0001:state_name_next = "IDLE";
			4'b0010:state_name_next = "one";
			4'b0100:state_name_next = "two";
			4'b1000:state_name_next = "three";
			default:state_name_next = "IDLE";
		endcase
	end
	
endmodule

仿真结果:

在这里插入图片描述

可发现:

  • 现态落后次态一个时钟周期,这是因为需要用次态去描述现态;
  • 可乐的输出会滞后一个时钟周期,这是因为采用了时序逻辑来描述输出;
  • **
    当第三段的输出判断条件变成使用n_state(次态)来判断的话,仿真图如下:输出结果相较于使用c_state(现态)判断早一个时钟周期,这是由于现态落后次态一个时钟周期

在这里插入图片描述
而当状态机第三段的输出使用组合逻辑输出时,即改成:

assign cola = (c_state == three)?1:0;

仿真结果如下,因为用判断条件时c_state(现态),相较于时序逻辑使用c_state判断的输出结果也要早一个时钟周期。
在这里插入图片描述
Mealy型(摩尔型)三段式状态机

verilog代码:

module fsm_mealy_3(
	input clk,
	input rst_n,
	input money,
	output reg cola
);

parameter IDLE = 3'b0001,
			 one  = 3'b0010,
			 two  = 3'b0100;

			 
reg [2:0]c_state;
reg [2:0]n_state;

//状态机第一段,时序逻辑描述状态转移
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)
		c_state <= 3'b0;
	else 
		c_state <= n_state;
end 

//状态机第二段,组合逻辑描述状态转移条件
always@(*)begin
	case(c_state)
		IDLE:n_state = money?one:IDLE;
		one :n_state = money?two:one;
		two :n_state = money?IDLE:two;
		default:n_state = IDLE;
	endcase
end

//状态机第三段,时序逻辑描述输出
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)
		cola <= 1'b0;
	else if(money&&(n_state == two))
		cola <= 1'b1;
	else	
		cola <= 1'b0;
end

endmodule

生成的状态转移图:
在这里插入图片描述
Testbench:

`timescale 1ns/1ns
module fsm_mealy_3_tb();
	reg clk;
	reg rst_n;
	reg money;
	wire cola;	
	
	fsm_mealy_3 u0(
		.clk(clk),
		.rst_n(rst_n),
		.money(money),
		.cola(cola)
	);
	
	always #10 clk = ~clk;
	
	initial begin
		clk = 0;
		rst_n = 0;
		money = 0;
		#5
		rst_n = 1;
		#25
		money = 1;
		#40
		money = 0;
		#20
		money = 1;
		#80
		money = 0;
		#200
		$stop;
		
	end
	
	//状态机名称查看

	reg [39:0]	state_name_cur;			//每字符8位宽,这里最多5个字符40位宽
	reg [39:0]	state_name_next;		   //每字符8位宽,这里最多5个字符40位宽
	
	always@(*)begin
		case(u0.c_state)
			4'b0001:state_name_cur = "IDLE";
			4'b0010:state_name_cur = "one";
			4'b0100:state_name_cur = "two";
			4'b1000:state_name_cur = "three";
			default:state_name_cur = "IDLE";
		endcase
	end
	
	always@(*)begin
		case(u0.n_state)
			4'b0001:state_name_next = "IDLE";
			4'b0010:state_name_next = "one";
			4'b0100:state_name_next = "two";
			4'b1000:state_name_next = "three";
			default:state_name_next = "IDLE";
		endcase
	end
	
endmodule

仿真结果:

在这里插入图片描述

通过以上,针对三段式状态机可以得出如下小结:

Moore型状态机输出滞后Mealy型状态机一个时钟周期 三段式状态机的输出使用时序逻辑输出,避免了二段式状态机使用组合逻辑输出从而无法避免的“毛刺”问题

moore型和mealy型状态机的区别

波形上

以一个序列检测器为例,检测到输入信号11时输出z为1,其他时候为0。用摩尔型FSM实现需要用到三个状态(A,B,C)。而用米利型FSM实现则需要两个状态(A,B)。摩尔型FSM输出函数的输入只由状态变量决定,要想输出z=1,必须C状态形成,即寄存器中的两个1都打进去后才可以。输出z=1会在下一个有效沿到来的时候被赋值。而米利型FSM输出函数是由输入和状态变量共同决定的。状态在B的时候如果输入为1,则直接以组合电路输出z=1,不需要等到下个有效沿到来。从而也就不需要第三个状态C。

优缺点:

1、moore型更安全。

输出在时钟边沿变化(总是在一个周期后)。在Mealy机器中,输入更改可能会在逻辑完成后立即导致输出更改, 当两台机器互连时出现大问题 - 如果不小心,可能会发生异步反馈。

2、mealy型反应更快

在相同的周期内反应 - 不需要等待时钟。在Moore机器中,可能需要更多逻辑来将状态解码为输出 - 在时钟边沿之后更多的门延迟。并非所有时序电路都可以使用Mealy模型实现。 一些时序电路只能作为摩尔机器实现

总结与思考

状态机的三种描述方法:

    一段式:整个状态机写到一个always模块里面,在该模块中既描述状态转移,又描述状态的输入和输出。

    二段式:用两个always模块来描述状态机,其中一个always模块采用同步时序描述状态转移;另一个模块采用组合逻辑判断状态转移条件,描述状态转移规律以及输出。

    三段式:在两个always模块描述方法基础上,使用三个always模块,一个always模块采用同步时序描述状态转移,一个always采用组合逻辑判断状态转移条件,描述状态转移规律,另一个always模块描述状态输出。

应该选择哪一种状态机 ?

    一段式状态机写法不够模块化 ,且过于臃肿不利于维护,及布局布线;

    二段式状态机将同步时序和组合逻辑分别放到不同的always模块中实现,这样做的好处不仅仅是便于阅读、理解、维护,更重要的是利于综合器优化代码,利于用户添加合适的时序约束条件,利于布局布线器实现设计。但是其当前状态的输出用组合逻辑实现,组合逻辑很容易产生毛刺,而且不利于约束,不利于综合器和布局布线器实现高性能的设计。

    三段式状态机与二段式状态机相比,关键在于根据状态转移规律,在上一状态根据输入条件判断出当前状态的输出,从而在不插入额外时钟节拍的前提下,实现了寄存器输出,解决了毛刺问题。实际应用中三段式状态机使用最多,因为三段式状态机将组合逻辑和时序分开,有利于综合器分析优 化以及程序的维护;并且三段式状态机将状态转移与状态输出分开,使代码看上去更加清晰易懂,提高了代码的可读性,推荐大家使用三段式状态机。              

三段式状态机的基本格式:

第一个 always 语句实现同步状态跳转;

第二个 always 语句采用组合逻辑判断状态转移条件;

第三个 always 语句采用时序逻辑描述状态输出。(状态机的第三段可以使用组合逻辑电路输出,也可以使用时序逻辑电路输出,一般推荐使用时序电路输出,因为状态机的设计和其它设计一样,最好使用同步时序方式设计,以提高设计的稳定性,消除毛刺)

状态机的编码方式:

独热码:和格雷码相比,虽然独热码多用了触发器,但所用组合电路可以省一些,因而使电路的速度和可靠性有显著提高,而总的单元数并无显著增加。因为独热码只有一位的变化,所以更适用于高速系统。

    格雷码:使用了更多的组合逻辑资源,但是比独热码能表示更多的状态。

    2进制:使用了更多的组合逻辑资源,但是比独热码能表示更多的状态,稳定性不如格雷码。

三段式状态机的第三段采用next_state还是cur_state:

第三段使用next_state和cur_state的区别在于,当状态跳转时,基于next_state的输出是立刻变化的,而基于cur_state输出会延迟一个周期,其他情况都一样,应该根据自己的时序要求选择。

modelsim显示状态机名称的方法:

    1.在Testbench中添加如下语句:实质上就是在测试文件里添加一个变量来代替你要观察的变量;

    2.在modelsim的波形仿真见面右击波形,选择Radix--ASSIC。
//------------------------------------------------
//--    状态机名称查看器
//------------------------------------------------
reg [39:0]	state_name_cur;			    //每字符8位宽,这里最多5个字符40位宽(THREE)
reg [39:0]	state_name_next;		    //每字符8位宽,这里最多5个字符40位宽(THREE)
 
always @(*) begin
    case(FSM_Mealy_3_inst.cur_state)    //这里写你例化的状态机模块里你想查看的参数
         4'b0001:    	state_name_cur = "IDLE";    //编码对应你的状态机的编码
         4'b0010:   	state_name_cur = "ONE";
         4'b0100:   	state_name_cur = "TWO";
         4'b1000:		state_name_cur = "THREE"; 
        default:		state_name_cur = "IDLE";
    endcase
end
 
always @(*) begin
    case(FSM_Mealy_3_inst.next_state)
         4'b0001:    	state_name_next = "IDLE";
         4'b0010:   	state_name_next = "ONE";
         4'b0100:   	state_name_next = "TWO";
         4'b1000:		state_name_next = "THREE"; 
		 default:		state_name_next = "IDLE";
    endcase
end

更多内容:FPGA三段式状态机的思维陷阱

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
这段代码是一个Verilog的函数,用于判断输入向量是否为onehot编码。引用中的SystemVerilog代码定义了一个自动的函数is_onehot,它接受一个输入向量sig,函数内部使用一个名为parity的逻辑向量来计算输入向量的奇偶校验位。函数中的for循环用于遍历输入向量的每一个位,并使用异或操作符^将每个位与前一个位的奇偶校验位进行异或运算。最后,函数使用逻辑与运算符&和逻辑或运算符|对奇偶校验位进行比较,以判断输入向量是否为onehot编码。如果所有条件都满足,则函数返回true,否则返回false。 引用中的代码也是一个Verilog的函数,名为is_onehot,它使用类似的方法来判断输入向量是否为onehot编码。函数内部的逻辑和运算符&&被用于检查奇偶校验位的条件。与中的代码相比,这段代码多了一个向量前导1检测器的注释,但实际上代码内容是相同的。 总结来说,这段Verilog代码定义了一个用于判断输入向量是否为onehot编码的函数,它使用奇偶校验位和逻辑运算符来实现。这种编码的一大特点是,在输入向量的每一位中,只有一个位被置为1,其他位均为0。这种编码方式在一些应用中具有重要意义,例如多路选择器、优先编码器等。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Verilog代码(11)1检测(统计个数、独热码检测、1的位置检测)](https://blog.csdn.net/m0_51965113/article/details/131374816)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [暑期实习准备——牛客Verilog刷题(完结)](https://blog.csdn.net/diamond_biu/article/details/129207228)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值