fpga实操训练(仿真和状态机)

Posted 嵌入式-老费

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了fpga实操训练(仿真和状态机)相关的知识,希望对你有一定的参考价值。

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】

        在进行fpga上板子实验之前,相信很多同学都是通过仿真的方式来实现verilog学习的。仿真比较容易,也不需要依赖物理硬件,所以一般是大家比较认可的学习方法。等接触了fpga开发板之后,很多同学认为,这样就不需要进行仿真测试了。其实这种想法就大错特错了,通过了仿真测试的电路不一定可以在fpga上面运行起来,但是没有通过仿真测试的电路是根本没有可能正常运行的。

        除此之外,相信经过这么几次上机测试,大家还发现了fpga实际运行的几个问题:1)编译、综合的速度其实非常慢,至少比自己之前编译软件的时间多多了;2)调试的手段不多。fpga调试一般会有这么几种方法,a、用led、uart输出有用的信息;b、用示波器、逻辑分析仪测量信号;c、用signal tap或者chipscope这样内置的逻辑分析仪+jtag进行调试。但是上面说到的三种调试方法,每一次都需要重新编译、综合版本,这又相当于回到了问题1,那就是重新综合花费的时间比较多、速度比较慢。

        所以为了解决fpga的问题,还是建议大家,在真正把verilog运行到fpga之前,先完成仿真的工作。仿真的速度非常快,而且很容易修改和验证。对于开发者来说,至少先保证仿真时没有逻辑的错误之后,再到fpga上面进行测试验证,这样要比直接在fpga上面开发效率要高很多。

        当然,今天除了仿真之外,另外一个要说的就是状态机。状态机在fpga上面用的非常多。从底层的硬件逻辑,到上层的模块逻辑、算法逻辑,用的都非常多。建议这部分,大家可以好好掌握一下。

        为了说明状态机怎么使用。我们可以通过一个示例来进行说明。之前,谈到过数据累加的程序。今天,可以把这个程序改一下。比如先从1累加到9,再从9递减到1。从1加到9,在从9减到1,这里面过程都是独立的。因此,可以把这两个过程看成是两个独立的状态,1加到9是状态1,9减到1是状态2,这么来分析的话,事情就变得简单了。


module state_machine(clk, rst, data);

localparam SECOND_INTERVAL=32'd50;

input clk;
input rst;
output [7:0] data;

wire clk;
wire rst;
reg[7:0] data;


reg[31:0] count;

reg[1:0] state;
reg[1:0] next_state;

// register count
always@(posedge clk or negedge rst)
	if(!rst)
		count <= 32'd0;
	else if(count == SECOND_INTERVAL)
		count <= 32'd0;
	else
		count <= count + 1;

// output data
always @(posedge clk or negedge rst)
	if(!rst)
		data <= 8'd0;
	else if(count == SECOND_INTERVAL && state == 2'b00)
		data <= data + 1;
	else if(count == SECOND_INTERVAL && state == 2'b01)
		data <= data - 1;

// state machine
always@(posedge clk or negedge rst)
	if(!rst)
		state <= 2'b00;
	else
		state <= next_state;
		
// next state
always@(*)
	if(!rst)
		next_state <= 2'b00;
	else
		case (state)
			2'b00:
				if(data == (9-1) && count == SECOND_INTERVAL)
					next_state <= 2'b01;
				else
					next_state <= 2'b00;
			
			2'b01:
				if(data == (1+1) && count == SECOND_INTERVAL)
					next_state <= 2'b00;
				else
					next_state <= 2'b01;
				
			default:
				next_state <= 2'b00;
		endcase

endmodule


        上述代码中的2'b00就是状态1,2'b01就是状态2,注意下两个状态的切换条件。只有当data等于8,并且count等于SECOND_INTERVAL的时候,next_state才会切换为2'b01。但是因为当前状态还是2'b00,所以data还会自增1,达到9。但是下一次的时候,data就开始自减了。有了state从2'b00切换为2'b01,那么它从2'b01切换为2'b00也是一样的道理。

`timescale 1ns/1ps
module show_seg_tb();
 
reg rst;
reg clk;
wire[7:0] data;
 
state_machine state_machine0(
	.rst(rst),
    .clk(clk),
    .data(data));
 
initial
    begin
        rst = 1;
        clk = 0;
        #12 rst = 0;
        #21 rst = 1;
        #100000 $finish;
    end
 
 
initial
begin
    while(1)
    clk = #5 !clk;
end
 
initial
begin
    $dumpfile("hello.vcd");
    $dumpvars(0, state_machine0);
end
 
endmodule

        为了验证我们编写的代码是否正确,有必要编写一个testbench代码。这里使用的工具还是iverilog+gtkwave来完成的。

         通过了testbench之后,这样才能保证代码的基本功能才是正确的。完成了仿真和test bench之后,下面就可以准备把这段代码port到fpga上面了,当前要做一点小的修改,主要是增加数码管显示的部分。

module seg_test(clk, rst, sel, seg_data);

localparam SECOND_INTERVAL=32'd4999_9999;

input clk;
input rst;
output [5:0] sel;
output [7:0] seg_data;

wire clk;
wire rst;
reg[7:0] data;
reg [5:0] sel;
reg [7:0] seg_data;


reg[31:0] count;

reg[1:0] state;
reg[1:0] next_state;

// output sel and out
always@(posedge clk or negedge rst)
	if(!rst)
		sel <= 6'b011111;
	else
		sel <= sel;


always@(posedge clk or negedge rst)
		if(!rst)
			seg_data <= 8'b1100_0000;
		else
			case (data)
				4'd0: 
					seg_data <= 8'b1100_0000;				
				4'd1: 
					seg_data <= 8'b1111_1001;
				4'd2: 
					seg_data <= 8'b1010_0100;
				4'd3: 
					seg_data <= 8'b1011_0000;
				4'd4: 
					seg_data <= 8'b1001_1001;
				4'd5: 
					seg_data <= 8'b1001_0010;
				4'd6:
					seg_data <= 8'b1000_0010;
				4'd7:
					seg_data <= 8'b1111_1000;
				4'd8:	
					seg_data <= 8'b1000_0000;
				4'd9:
					seg_data <= 8'b1001_0000;
					
				default: 
					seg_data <= 8'b1100_0000;
			endcase
		
// register count
always@(posedge clk or negedge rst)
	if(!rst)
		count <= 32'd0;
	else if(count == SECOND_INTERVAL)
		count <= 32'd0;
	else
		count <= count + 1;

// output data
always @(posedge clk or negedge rst)
	if(!rst)
		data <= 8'd0;
	else if(count == SECOND_INTERVAL && state == 2'b00)
		data <= data + 1;
	else if(count == SECOND_INTERVAL && state == 2'b01)
		data <= data - 1;

// state machine
always@(posedge clk or negedge rst)
	if(!rst)
		state <= 2'b00;
	else
		state <= next_state;
		
// next state
always@(*)
	if(!rst)
		next_state <= 2'b00;
	else
		case (state)
			2'b00:
				if(data == (9-1) && count == SECOND_INTERVAL)
					next_state <= 2'b01;
				else
					next_state <= 2'b00;
			
			2'b01:
				if(data == (1+1) && count == SECOND_INTERVAL)
					next_state <= 2'b00;
				else
					next_state <= 2'b01;
				
			default:
				next_state <= 2'b00;
		endcase

endmodule

        编译、综合没有问题后,就可以开始进行pin脚的bind,这部分和之前没有什么区别,

         烧入后,如果没有问题的话,就可以看到对应的测试内容了,即先增加到9,再递减到1,循环反复。

        所以说,通过今天的状态机实验告诉我们,虽然实际调试板子非常方便,但是效率不太高。如果想更有效率地实现fpga的开发工作,最好还是先编写仿真代码,然后再port到fpga开发板子上面,这样常常会有事半功倍的效果。 

以上是关于fpga实操训练(仿真和状态机)的主要内容,如果未能解决你的问题,请参考以下文章

fpga实操训练(基础)

fpga实操训练(基础)

fpga实操训练(系统开发和硬件接口)

fpga实操训练(系统开发和硬件接口)

fpga实操训练(ip ram和ip fifo)

fpga实操训练(锁相环pll)