【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱: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开发板子上面,这样常常会有事半功倍的效果。