FPGA笔记(1)
——LED_RUN综合
紫龙
序言:
今天是我在西安ZXOPEN学习的第二十五天,对于一个毫无基础的学生而言,开始无疑是痛苦的,从刚开始创建文件还需要老师一步一步教着,步骤老记不住,打了BEGIN却忘记END等等,一步一步走到现在,确实很艰难。我不是一个很聪明的学生,却有着迷之自信,带着这份麻木的不知天高地厚的自信,我一个人笑着从小学走到现在。一个人的旅途确实很孤独,不过静享这份温柔的孤独,有时却是一份惬意与自得,至少,明天阳光很好...
回归主题:
咳咳•••说了太多无关紧要的东西,不要见怪! 很早就想发点东西来记录我的学习进程了,却一直忙于打基础(笔记班上的大神很多,像我们这种什么都不会的菜鸟,哎。。。),正好今天总结了一番,就从刚开始的LED_RUN开始吧。
正言:
LED_RUN,顾名思义,就是让LED灯“跑”起来,达到所谓的流水效果。
但是,通过FPGA并不是这么简单的说跑他就能跑。首先,我们得了解它“跑”的原理-----正如人跑一样,人跑步需要能量的提供,无能量,无驱动。哈哈,也算一个比方而已~
在我们ZX2开发板上,有四个LED灯,让他达到流水的效果,则需要LED灯依次点亮,当然这个点亮时间也有相应的要求,太快,LED闪烁太快,人眼分辨不出他流水的效果,于是这样就会出现大概三种情况:
第一种:频率太快,出现几个LED同时亮,但亮度却不饱满,这是由于LED点亮完全的时间是1MS到2MS,如果频率小于这个时间,第一个LED灯未点亮完全,第二个却开始点亮,同理第三四个。由于频率太快人眼分辨不出其闪烁,这时我们看到的现象就是LED亮度不饱满的效果。
第二种:频率刚好,达到LED点亮的饱和时。在1000HZ的时候,咱们人眼分辨不出其闪烁,看到的LED无闪烁,同时,(1*10^)/1000=1MS,其点亮时间达到1MS,也就意味着达到了LED点亮饱和时间,此时LED亮度最佳。于是我们看到的情况是四个LED灯全部点亮。
第三种:频率稍慢,这种情况,LED灯亮度达到饱和,而且,当第一个LED由亮到熄灭的时间,第二个LED灯能够及时反应过来,我们的人眼也能够清楚的分辨出来。此时LED灯将会依次点亮,达到流水的效果。
原理图如下:
今天,我将通过三种不同方式来实现流水效果,当然,我们也适量做做第二种情况。
FIRST:
传统的一段式的状态机的方法:
module led_run(
input clk, //input wire clk
input key,
output reg [3:0] pio_led
);
reg [2:0] state;
reg [31:0] counter;
always @ (posedge clk_5 or negedge key)
begin
if (key == 0) //按键按下,这为复位键,低电平有效
begin
pio_led <= 4'b1111; //4'd15 15 4'hf
state <= 0;
end
else
begin
case (state)
0 : begin
pio_led <= 4'b1110;
state <= 1;
end
1 : begin
pio_led <= 4'b1101;
state <= 2;
end
2 : begin
pio_led <= 4'b1011;
state <= 3;
end
3 : begin
pio_led <= 4'b0111;
state <= 0;
end
default : state <= 0;
endcase
end
end
// 分频模块
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
clk_5 <= 0;
counter <= 0;
end
else
begin
if (counter == 12500000 - 1)
begin
clk_5 <= ~clk_5;
counter <= 0;
end
else
begin
counter <= counter + 1;
end
end
end
endmodule
个人觉得这个方法很蠢啊, = = 别见怪,其实和这个差不多的程序还有一个,这下面,换汤不换药,大家就看看就好:
module led_run(
input clk,
input key,
output reg [3:0]poi_led
);
reg [1:0]state;
reg [31:0]counter;
parameter T=25000000-1;
always @ (posedge clk or negedge key)
begin
if(key==0)
begin
poi_led <= 4'b1111;
state <= 0;
counter <=0;
end
else
begin
case(state)
0:begin
if (counter<T)
begin
poi_led <= 4'b1110;
counter=counter+1;
state <= 0;
end
else
begin
counter<=0;
state <= 1;
end
end
1:begin
if (counter<T)
begin
poi_led <= 4'b1101;
counter=counter+1;
state <= 1;
end
else
begin
counter<=0;
state <=2;
end
end
2:begin
if (counter<T)
begin
poi_led <= 4'b1011;
counter=counter+1;
state <= 2;
end
else
begin
counter <= 0;
state <= 3;
end
end
3:begin
if (counter<T)
begin
poi_led <= 4'b0111;
counter=counter+1;
state <= 3;
end
else
begin
counter <= 0;
state <=0;
end
end
endcase
end
end
endmodule
哈哈,大家有没有发现其实这两个都一样,没错~ 后者只是省掉了一个分频模块,用一个计数器嵌套在一段式状态机中而已。 刚开始咱们都比较喜欢使用一段式状态机,但是从专业性角度来说,一段式状态机很多时候并不是适合用于大型工程中,这个问题,咱们下回再讨论。下面咱们用三段式状态机完成。
Second:
三段式状态机法:
module led_run(
input clk ,
input rst_n ,
output reg [3:0]poi_led
);
//------middle_signal-------
reg [31:0]counter ;
reg clk_out ;
reg [3:0]CS ;
reg [3:0]NS ;
parameter IDLE = 3'd0 ;
parameter S0 = 3'd1 ;
parameter S1 = 3'd2 ;
parameter S2 = 3'd3 ;
parameter S3 = 3'd4 ;
//-----counter--------------
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
counter <= 3'd0 ;
clk_out <=1 ;
end
else if(counter == 12500000-1)
begin
counter <= 3'd0 ;
clk_out <= ~clk_out ;
end
else
counter <= counter +1;
end
//-----FSM1-----------------
always @(posedge clk_out or negedge rst_n)
begin
if (!rst_n)
begin
CS <= IDLE ;
end
else
begin
CS <= NS ;
end
end
//----FSM2------------------
always @(*)
begin
if (!rst_n)
begin
NS = IDLE ;
end
else
case( CS)
IDLE: NS =S0 ; //NS 是CS的下一个状态
S0:
NS = S1 ;
S1:
NS = S2 ;
S2:
NS = S3 ;
S3:
NS = S0 ;
default NS = S0 ;
endcase
end
//-----FSM3-------------------
always @(posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
poi_led <= 4'b1111 ;
end
else
case (NS)
IDLE:
begin
poi_led <= 4'b1111 ;
end
S0:
begin
poi_led <= 4'b1110 ;
end
S1:
begin
poi_led <= 4'b1101 ;
end
S2:
begin
poi_led <= 4'b1011 ;
end
S3:
begin
poi_led <= 4'b0111 ;
end
default :poi_led <= 4'b1111;
endcase
end
endmodule
三段式的状态机,通俗点说:第一段,是对状态的初始赋值,第二段,是对状态的转移,第三段是对状态的描述。三段式咱们以后工作上用到得会很多,因此,尽管咱们不熟练,硬着头皮也多练。
Thired:
第三种,也是我认为最简便的一种,这是用线性序列机完成的。对于线性序列机,大致结构如下:
1:输入输出端口,这个基本上每个独立程序都有;
2:计数器count的定义;
3:case(count)。
由于我们使用线性序列机,在case(count)中,当我们count达到一个固定值,我们对输出赋值,但是,此后到第二个固定count值,其输出将会保持不变。
module lsm(
input clk ,
input rst_n ,
output reg [3:0]poi_led
);
//------middle_signal-------
reg [31:0]count;
//-------lsm1---------------
always @(posedge clk)
begin
if(!rst_n || count==80000000)
begin
count<=0 ;
end
else
count <= count+1 ;
end
//------lsm2------------------
always @(posedge clk)
begin
if(!rst_n)
begin
poi_led<=4'b1111 ;
end
else
case(count)
0:poi_led<=4'b1110 ;
20000000:poi_led<=4'b1101 ;
40000000:poi_led<=4'b1011 ;
60000000:poi_led<=4'b0111 ;
endcase
end
endmodule
是不是很简单呢?序列机方法和其他相比,既简单又易于整理改变。 三种方法咱们都可以通过改变程序中的数值如第一个中的T值,第二个中的counter值,第三个中count值,来实现全亮到led流水灯的转变,也可以改变led位选来实现各种操作,如警报,交互点亮等等,当然,这也是大家自己练习的小操作了。下次我整理一到三段式的区别以及用法,第一次发帖,水平不够,见谅哈~
|