对于FPGA的结构原理,先不进行全面的了解,先能根据教程程序看得懂,写得出来跑起来。慢慢的了解程序运行的原理,各种语法的使用。
今天对流水的程序有一个认识,熟悉软件的使用,语法规则,原理。以正点原子的例程为例,代码如下
// Created by: 正点原子 module flow_led(
input sys_clk , //系统时钟
input sys_rst_n, //系统复位,低电平有效 output reg [:] led //4个LED灯
); //reg define
reg [:] counter; // main code
//计数器对系统时钟计数,计时0.2秒
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
counter <= 'd0;
else if (counter < 'd1000_0000)
counter <= counter + 'b1;
else
counter <= 'd0;
end //通过移位寄存器控制IO口的高低电平,从而改变LED的显示状态
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
led <= 'b0001;
else if(counter == 'd1000_0000)
led[:] <= {led[:],led[]};
else
led <= led;
end endmodule
这个代码的功能是点亮流水灯。代码的内容是:用一个定时器计时,到了0.2s就自动清零,否则自动+1计数。另一方面,判断是否到了0.2s,到了就换另一个led亮。程序写好后,烧写在芯片里,会形成这样的电路,功能是并行的。不像是stm32需要用C语言写代码,生成指令,cpu取指执行。FPGA会快一些。
代码的整体结构,头和尾有 module 和 endmodule 这是模块的开始和结束。如果工程中有其他的功能代码,写在一个.v文件,也会有这个 module 。就是一个功能或者一个模块卸载一个.v 文件,这样便于管理。
input sys_clk , 可以理解为定义一个输入类型的变量。
output reg [3:0] led, 可以理解为,定义一个输出类型的变量,他占据了4个bit。
always @(posedge sys_clk or negedge sys_rst_n) begin 可理解为,复位了或者有新的始终进来就做下面的代码。
led <= 4'b0001; 这个是赋值,和C语言有些不一样。
4'b0001 是一个数,b:二进制表示,0001。 4' 是指这个数是4位的。
补充一下流水灯的形成 led[3:0] <= {led[2:0],led[3]}; 意思是:4位循环右移。即,3位放到0位,210位放到321位。led初始化0001,用形象的描述如下:
位 3210
初始值 0001
第一次 0010
第二次 0100
第三次 1000
....................
高电平点亮。形成流水灯。
然后这个程序实现了点灯。但是有一点,他是怎么控制的引脚呢,led是接在引脚少了,这里面也没涉及到引脚啊。在下面这里:
通过这个图可以看到,我们在工程里面的变量,需要“绑定”引脚。使使之一一对应,举个例子led变量是4个bit的,把0 1 2 3 分别对应板子上led连接的引脚上,操作led就相当于操作引脚了。
几天重新研究了一下这个流水灯的例程,觉得主要部分代码还要再仔细的分析一下,时序代码部分:
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
counter <= 'd0;
else if (counter < 'd1000_0000)
counter <= counter + 'b1;
else
counter <= 'd0;
end
其中关键字 always 是时序逻辑电路,在这个语句的下面,赋值的话用非阻塞赋值 “ <= ” ,非阻塞可以理解为:相互不干扰、不堵塞,是并行的。而阻塞赋值是串行的,相互之间有顺序干扰的,一个一个的。
第一行解释:如果有 sys_clk 时钟信号上升沿或者 sys_rst_n 下降沿的话,就开始执行下面的代码一直到 end 结束。
这是一个或的关系,两个条件满足一个即可。第一条 if 判断是时钟信号上升沿还是复位重启,如果是复位重启的话,就给 counter 计数器赋初值 0 。如果不是复位那就是时钟洗好上升沿,此时,是一个脉冲过来了,执行下一个(类似单片机的下一条指令),那么就看计数器是否达到最大,没达到的话,就 继续 +1 ,达到了最大的话,就置为0。以便于重新计数。
流水灯部分:
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
led <= 'b0001;
else if(counter == 'd1000_0000)
led[:] <= {led[:],led[]};
else
led <= led;
end
这段代码看着和上一段时序部分非常像。主要功能是,实现流水灯,根据上面的时序部分“定时器”,克制外部晶振50MHz,周期是 20ns。0.2s流动一次的话,计数器就要计数到 10^7 个,从0 到10^7-1,就是 24'd1000_0000。在流水灯部分,判断计数器是否为24'd1000_0000 是的话就是到了0.2s。流动一次。
总结:
1. 掌握verilog的语法:
1.1 always 和 assign的区别
1.2 阻塞赋值和非阻塞赋值的区别
1.3 数据格式
1.4 程序代码的框架