由于项目原因,需要接触FPGA。入手了一块入门用的FPGA开发板,体会可编程逻辑器件的思想。
Table of Contents
0 板卡信息
购买的开发板是STEP-MXO2-C
基本信息:
-
核心器件:Lattice LCMXO2-4000HC-4MG132
-
132脚BGA封装,引脚间距0.5mm,芯片尺寸8mm x 8mm;
-
上电瞬时启动,启动时间<1ms;
-
4320个LUT资源, 96Kbit 用户闪存,92Kbit RAM;
-
2+2路PLL+DLL;
-
支持DDR/DDR2/LPDDR存储器;
-
104个可热插拔I/O;
-
内核电压2.5-3.3V;
-
-
板载资源:
-
两位7段数码管;
-
两个RGB三色LED;
-
8路用户LED;
-
4路拨码开关;
-
4路按键;
-
-
36个用户可扩展I/O(其中包括一路SPI硬核接口和一路I2C硬核接口)
-
支持的开发工具Lattice Diamond
-
支持MICO32/8软核处理器
-
板上集成FPGA编程器
-
一路Micro USB接口
-
板卡尺寸52mm x 18mm
使用的开发工具为Lattice提供的 Diamond
http://www.latticesemi.com/en/Products/DesignSoftwareAndIP/FPGAandLDS/LatticeDiamond.aspx
1 FPGA预备
使用FPGA前,需要将它和之前一直使用的MCU对比一番。
FPGA(Field-Programmable Gate Array),即现场可编程门阵列
与单片机的简要区别
单片机硬件架构已经固定。CPU从时间上考虑,FPGA从空间上考虑,比如增加很多乘法器、加法器实现并行计算(比如不必等待同一个加法器多次执行)。IO口可自行配置。
FPGA特色汇总
- 并行性,可以同时处理不同运算数据或任务。
- 速度快,FPGA可对时钟倍频作系统时钟,加之并行性,可以达到较高的数据处理速度。
- 丰富的片上硬件资源(逻辑单元和I/O管脚资源)。
- 数字电路硬件开发无需投板,周期短,费用低,可重复编程。
- 可嵌入CPU核,实现CPU和FPGA联合设计,协同处理。
一般流程
加法器、乘法器、寄存器、IO等等。
可以实现“软核”,甚至在其中搭建单片机核。
相关厂商
2 实验
本笔记以实验的形式学习FPGA开发。主要以Verilog语言进行开发。
值得指出的是,实验的价值并不在于其实验本身,而是在于通过实验体会FPGA设计与MCU设计得不同之处。
2.1 组合逻辑
2.1.1 38译码器
组合逻辑电路是数字电路的重要部分,电路的输出只与输入的当前状态相关的逻辑电路,常见的有选择器、比较器、译码器、编码器、编码转换等等。在本实验里以最常见的3-8译码器为例说明如何用Verilog实现。3-8译码器的真值表如下:
EDA工具解释:目前进入我国并具有广泛影响的EDA软件是系统设计软件辅助类和可编程芯片辅助设计软件:Protel、PSPICE、multiSIM10(原EWB的最新版本)、OrCAD、PCAD、、LSIIogic、MicroSim,ISE,modelsim等等。这些工具都有较强的功能,一般可用于几个方面,例如很多软件都可以进行电路设计与仿真,同时还可以进行PCB自动布局布线,可输出多种网表文件与第三方软件接口。
本文中使用的EDA工具即为Lattice提供的 Diamond软件。
图:Lattice提供的 Diamond软件
关于Verilog:Verilog(国际标准为IEEE 1364), 是一种专用来模型化电子系统的硬件描述语言(HDL),它通常被用于在寄存器传输级(RTL)抽象层面进行数字电路的设计和验证,它也被用于模拟电路和混合信号电路的验证,以及通用电路的设计。
具体的Verilog我将通过专门的“FPGA开发床头必备”的Verilog书籍学习,本文仅对实验内容进行叙述。
我们来看一下38译码器使用Verilog实现的代码:
// Module Function:利用3路开关的状态作为输出,通过3-8译码实现控制LED灯的显示。 module decode38 (sw,led); input [2:0] sw; //开关输入信号,利用了其中3个开关作为3-8译码器的输入 output [7:0] led; //输出信号控制特定LED reg [7:0] led; //定义led为reg型变量,在always过程块中只能对reg型变量赋值 //always过程块,括号中sw为敏感变量,当sw变化一次执行一次always中所有语句,否则保持不变 always @ (sw) begin case(sw) //case语句,一定要跟default语句 3'b000: led=8'b0111_1111; //条件跳转,其中“_”下划线只是为了阅读方便,无实际意义 3'b001: led=8'b1011_1111; //位宽'进制+数值是Verilog里常数的表达方法,进制可以是b、o、d、h(二、八、十、十六进制) 3'b010: led=8'b1101_1111; 3'b011: led=8'b1110_1111; 3'b100: led=8'b1111_0111; 3'b101: led=8'b1111_1011; 3'b110: led=8'b1111_1101; 3'b111: led=8'b1111_1110; default: ; endcase end endmodule
该例程很好地体现了使用Verilog设计FPGA的思想:制定硬件的布线和逻辑策略,FPGA是可编程的硬件。
相关语法:
- begin end 将多条语句组成顺序块,类似c语言的{}
- case() endcae 是多分支选择语句,类似c语言的选择,但是每个分支各不相同,执行分支语句后即跳出
2.1.2 数码管
数码管是工程设计中使用很广的一种显示输出器件。一个7段数码管(如果包括右下的小点可以认为是8段)分别由a、b、c、d、e、f、g位段和表示小数点的dp位段组成。实际是由8个LED灯组成的,控制每个LED的点亮或熄灭实现数字显示。通常数码管分为共阳极数码管和共阴极数码管,结构如下图所示:
数码管所有的信号都连接到FPGA的管脚,作为输出信号控制。FPGA只要输出这些信号就能够控制数码管的那一段LED亮或者灭。这样我们可以通过开关来控制FPGA的输出。
事实上,操作数码管,就是要构建一个4-16译码器,真值表如图:
Verilog代码如下:
module LED (seg_data_1,seg_data_2,seg_led_1,seg_led_2); input [3:0] seg_data_1; //数码管需要显示0~9十个数字,所以最少需要4位输入做译码 input [3:0] seg_data_2; //小脚丫上第二个数码管 output [8:0] seg_led_1; //在小脚丫上控制一个数码管需要9个信号 MSB~LSB=DIG、DP、G、F、E、D、C、B、A output [8:0] seg_led_2; //在小脚丫上第二个数码管的控制信号 MSB~LSB=DIG、DP、G、F、E、D、C、B、A reg [8:0] seg [9:0]; //定义了一个reg型的数组变量,相当于一个10*9的存储器,存储器一共有10个数,每个数有9位宽 initial //在过程块中只能给reg型变量赋值,Verilog中有两种过程块always和initial //initial和always不同,其中语句只执行一次 begin seg[0] = 9'h3f; //对存储器中第一个数赋值9'b00_0011_1111,相当于共阴极接地,DP点变低不亮,7段显示数字 0 seg[1] = 9'h06; //7段显示数字 1 seg[2] = 9'h5b; //7段显示数字 2 seg[3] = 9'h4f; //7段显示数字 3 seg[4] = 9'h66; //7段显示数字 4 seg[5] = 9'h6d; //7段显示数字 5 seg[6] = 9'h7d; //7段显示数字 6 seg[7] = 9'h07; //7段显示数字 7 seg[8] = 9'h7f; //7段显示数字 8 seg[9] = 9'h6f; //7段显示数字 9 end assign seg_led_1 = seg[seg_data_1]; //连续赋值,这样输入不同四位数,就能输出对于译码的9位输出 assign seg_led_2 = seg[seg_data_2]; endmodule
思路很简单,就是用4路输入来根据情况选择输出的状态;其中输出状态通过存储器的形式预先设置好。
Verilog相关语法:
- 用reg声明寄存器变量 reg [8:0] seg;
- 用reg生命存储器(寄存器组)reg [8:0] seg[15:0];
- 用存储器操作wire [8:0] a,b; assign a= seg[1]; assign b=seg[1][5:0];
- initial关键字 用于给寄存器或存储器初始化数据
在EDA设计工具中将FPGA的引脚(对应板子上的资源引脚)与内部逻辑引脚连接起来,也就是所说的管脚约束。这个方面每一家FPGA公司提供的工具链都不一样,但是对于FPGA开发的流程是大致相同的(见上流程图),这里不再赘述。
在我的开发板上下载完成后,实验结果如下:
图:根据拨码开关不同改变的数码管显示值。
2.2 时序逻辑
2.2.1 分频器
废话不多说,我们来看一下分频器Verilog代码
module divide ( input clk, //clk连接到FPGA的C1脚,频率为12MHz input rst_n, //复位信号,低有效, output clkout //输出信号,可以连接到LED观察分频的时钟 ); //parameter是verilog里参数定义 parameter WIDTH = 3; //计数器的位数,计数的最大值为 2**(WIDTH-1) parameter N = 5; //分频系数,请确保 N<2**(WIDTH-1),否则计数会溢出 reg [WIDTH-1:0] cnt_p,cnt_n; //cnt_p为上升沿触发时的计数器,cnt_n为下降沿触发时的计数器 reg clk_p,clk_n; //clk_p为上升沿触发时分频时钟,clk_n为下降沿触发时分频时钟 /**************************************上升沿触发部分**************************************/ //上升沿触发时计数器的控制 always @(posedge clk or negedge rst_n) //posedge和negedge是verilog表示信号上升沿和下降沿 begin //当clk上升沿来临或者rst_n变低的时候执行一次always里的语句 if(!rst_n) cnt_p <= 1'b0; else if(cnt_p == (N-1)) cnt_p <= 1'b0; else cnt_p <= cnt_p + 1'b1; //计数器一直计数,当计数到N-1的时候清零,这是一个模N的计数器 end //上升沿触发的分频时钟输出,如果N为奇数得到的时钟占空比不是50%;如果N为偶数得到的时钟占空比为50% always @(posedge clk or negedge rst_n) begin if(!rst_n) clk_p <= 1'b0; else if(cnt_p < (N>>1)) //N>>1表示右移一位,相当于除以2取商 clk_p <= 1'b0; else clk_p <= 1'b1; //得到的分频时钟正周期比负周期多一个clk时钟 end /******************************************************************************************/ /**************************************下降沿触发部分**************************************/ //下降沿触发时计数器的控制 always @(negedge clk or negedge rst_n) begin if(!rst_n) cnt_n <= 1'b0; else if(cnt_n == (N-1)) cnt_n <= 1'b0; else cnt_n <= cnt_n + 1'b1; end //下降沿触发的分频时钟输出,和clk_p相差半个clk时钟 always @(negedge clk or negedge rst_n) begin if(!rst_n) clk_n <= 1'b0; else if(cnt_n < (N>>1)) clk_n <= 1'b0; else clk_n <= 1'b1; //得到的分频时钟正周期比负周期多一个clk时钟 end /******************************************************************************************/ wire clk1 = clk; //当N=1时,直接输出clk wire clk2 = clk_p; //当N为偶数也就是N的最低位为0,N[0]=0,输出clk_p wire clk3 = clk_p & clk_n; //当N为奇数也就是N最低位为1,N[0]=1,输出clk_p&clk_n。正周期多所以是相与 assign clkout = (N==1)? clk1:(N[0]? clk3:clk2); //条件判断表达式 endmodule
其中分频方法使用了“计数分频”,看以下代码:
always @(posedge clk or negedge rst_n) begin if(!rst_n) clk_p <= 1'b0; else if(cnt_p < (N>>1)) //N>>1表示右移一位,相当于除以2取商 clk_p <= 1'b0; else clk_p <= 1'b1; //得到的分频时钟正周期比负周期多一个clk时钟 end
这个和我们使用MCU时,根据时钟,使用计数法产生延迟或者生成PWM时是一个思想。
下图为模拟的信号
可以看到cnt_p和cnt_n差了半个时钟周期。可以将上升沿和下降沿激活的信号一起使用,产生50%占空比的“奇分频”方波(clk3)!
Verilog语法
- parameter定义参数,模块内范围有效,例化时可以通过参数传递更改参数
- localparam定义的参数不可传递更改
- 除法和取余数尽量少用,耗费资源且不稳定。
- Verilog中,左移相当于乘2,右移相当于除2取商(二进制乘除运算就是移位!)
关于赋值方式
阻塞赋值方式 =
在块内语句逐条进行赋值(理解成按照逻辑赋值),但是在时间上,这几个变量是同时改变的。
建议在组合逻辑中使用
e=a; c=e; e=b; d=e;
非阻塞赋值方式 <=
块内语句同时执行,建议在时序逻辑中使用
e<=a; c<=e; e<=b; d<=e;
2.2.2 流水灯设计
终于到了激动人心的流水灯环节了!流水灯简直是硬件开发的“Hello World!”
看一下我们设计流水灯的框架:
module flashled ( input clk, input rst_n, output reg [7:0] led ); wire clk1h; //例化分频器模块,产生一个1Hz时钟信号 divide # //#后面的()中为参数传递 ( .WIDTH(24), .N(12_000_000) //分频系数12_000_000,产生1Hz信号 ) u1 ( .clk (clk), .rst_n (rst_n), .clkout (clk1h) ); //使用1Hz时钟上升沿触发循环赋值 always@(posedge clk1h or negedge rst_n) begin if(!rst_n) led <= 8'b1111_1110; //"<="为非阻塞赋值 else //当时钟上升沿来一次,执行一次赋值 led <= {led[6:0],led[7]}; //"{}"为拼接符,将led[6:0]与led[7]重新拼接赋给led,循环左移 end endmodule
代码中,直接调用了之前的divide模块。可以理解成c语言中的调用函数。
首先使用分频器产生1Hz的信号,再以此信号当做我们本模块的“基准时钟”。在产生的1Hz的clk1h上升沿时出发逻辑,进行led移位。实验了循环移位功能
Verilog语法:
- {} 表达式为{A,B} 将AB连接起来,产生更大的向量
- {{}} 表达式为{B{A}} 将A重复B次
注意,这里也体现了思想,就是模块化可编程设计。divide分频器可以在以后的工程中直接拿过来用!
实验结果:
自此我们学会了使用板上的时钟产生分频的方法。也可以操作数码管自增。
2.3 按键消抖
相信所有实验过单片机学习的同学,都进行过按键处理的学习。
按键占用一个IO口,通过上拉/下拉与另一端相连。但是,物理世界是不稳定的,按键时会有无数的抖动,如果我们没有对抖动进行处理,那么将产生位置次数的按击效果,因此我们常常使用“延时消抖”和“周期消抖”的方法进行处理
2.3.1 延时消抖
延时消抖思路如下:
首先进行边沿检测,如果按下,立即触发计数器进行计数,一定时间后,还有下降,那么我们就认为按下了按键,进行信号输入。(相当于软件,这个模块进行检测,然后给予内部逻辑“是否按下”的逻辑信号)
module Debounce # ( parameter KEY_WIDTH = 1, parameter CNT_NUM = 19'd240000 ) ( input clk, //system clock input rst_n, //system reset input [KEY_WIDTH-1:0] key_n, //button input output reg [KEY_WIDTH-1:0] key_jit, //key jitter output output wire[KEY_WIDTH-1:0] key_pulse, //Debounce pulse output output reg [KEY_WIDTH-1:0] key_state, //Debounce state output output [10:0] empty ); assign empty = 11'h7ff; reg [KEY_WIDTH-1:0] key_n_r,key_n_r1; //Register key_n_r1, lock key_n_r to next clk //存储上一个按键状态,供对比 always @(posedge clk or negedge rst_n) if (!rst_n) begin key_n_r <= {KEY_WIDTH{1'b1}}; key_n_r1 <= {KEY_WIDTH{1'b1}}; end else begin key_n_r <= key_n; key_n_r1 <= key_n_r; end //Detect the edge of key_n //探测按键变化 wire key_an = (key_n_r == key_n_r1)? 1'b0:1'b1; reg [18:0] cnt; //Count when a edge of key_n is occured //开始计数 always @(posedge clk or negedge rst_n) if (!rst_n) cnt <= 19'd0; else if(key_an) cnt <=19'd0; else cnt <= cnt + 1'b1; //Sample key_jit when cnt count to CNT_NUM(20ms) always @(posedge clk or negedge rst_n) if (!rst_n) key_jit <= {KEY_WIDTH{1'b1}}; else if (cnt == CNT_NUM-1) key_jit <= key_n_r; reg [KEY_WIDTH-1:0] key_jit_r; //Register key_jit_r, lock key_jit to next clk always @(posedge clk or negedge rst_n) if (!rst_n) key_jit_r <= {KEY_WIDTH{1'b1}}; else key_jit_r <= key_jit; //wire [KEY_WIDTH-1:0] key_pulse; //Detect the negedge of key_jit, generate pulse assign key_pulse = key_jit_r & ( ~key_jit); //Detect the negedge of key_jit, generate state always @(posedge clk or negedge rst_n) if (!rst_n) key_state <= {KEY_WIDTH{1'b1}}; else if(key_pulse) key_state <= key_state ^ key_pulse; else key_state <= key_state; endmodule
2.3.2 周期消抖
周期消抖就是检测到变化后,隔一个周期(比如10ms)后再次检测,如果仍然是按下状态,则认为成功按下。
2.4 PWM与呼吸灯
PWM是我们非常熟悉的东西,FPGA产生PWM要怎么做?相信根据之前的时间,我们的思路已经有了。就是根据时钟进行计数、分频的同时,在指定时间进行输出的变化。但是,FPGA中有硬件的优势。下面看一下FPGA的PWM发生器方法:
解析如下:
需要一个三角波,一个比较器,即可产生PWM信号!
看一下PWM基本的实现:
module PWM # ( parameter WIDTH = 32 //ensure that 2**WIDTH > cycle ) ( input clk, input rst_n, input [WIDTH-1:0] cycle, //cycle > duty input [WIDTH-1:0] duty, //duty < cycle output reg pwm_out ); reg [WIDTH-1:0] cnt; //counter for cycle always @(posedge clk or negedge rst_n) if(!rst_n) cnt <= 1'b1; else if(cnt >= cycle) cnt <= 1'b1; else cnt <= cnt + 1'b1; //pulse with duty always @(posedge clk or negedge rst_n) if(!rst_n) pwm_out <= 1'b1; else if(cnt <= duty) pwm_out <= 1'b1; else pwm_out <= 1'b0; endmodule
就把我们MCU中实现的思路变成了一个硬件PWM模块!!!!
好,然后我们来讲PWM和LED灯组合产生呼吸灯:
module Breath_led # ( parameter CNT_NUM = 3464 //period = (3464**2)*2 = 24_000_000 = 2s ) ( input clk_in, //system clk input rst_n_in, //system reset output Breath_led1, //Breath led output output Breath_led2, //Breath led output output [11:0] empty ); assign empty = 12'hfff; reg [12:0] cnt1; //generate cnt1 signal always@(posedge clk_in or negedge rst_n_in) begin if(!rst_n_in) begin cnt1<=13'd0; end else begin if(cnt1>=CNT_NUM-1) cnt1<=1'b0; else cnt1<=cnt1+1'b1; end end reg flag; reg [12:0] cnt2; //generate cnt2 signal always@(posedge clk_in or negedge rst_n_in) begin if(!rst_n_in) begin cnt2<=13'd0; flag<=1'b0; end else begin if(cnt1==CNT_NUM-1) begin if(!flag) begin if(cnt2>=CNT_NUM-1) flag<=1'b1; else cnt2<=cnt2+1'b1; end else begin if(cnt2<=0) flag<=1'b0; else cnt2<=cnt2-1'b1; end end else cnt2<=cnt2; end end //Compare cnt1 and cnt2, generate PWM-Breath-led assign Breath_led1 = (cnt1<cnt2)?1'b0:1'b1; //Compare cnt1 and cnt2, generate PWM-Breath-led assign Breath_led2 = ((cnt1+(CNT_NUM>>2))<cnt2)?1'b0:1'b1; endmodule
代码中,是两个PWM发生器控制LED灯。综合后分配引脚,下载到FPGA中,实验结果如下:
2.5 状态机
状态机在数字电路设计中,是一个非常重要的组成部分,也是贯穿于整个设计最基本的世纪思想和设计方法。在现代数字电路设计中,状态机的实际对于系统的高速性、高可靠性、高稳定性都有决定性的作用。熟练掌握状态机的设计,是FPGA的Verilog设计的必备技能。
经过我在网络上的搜集,我认为,三段式写法模式很好理解,可以进行复用。三段式的状态机写法结构如下:
module stateflow( input clk, input rst_n, input [7:0] data, output reg led ); parameter checkh = 5'b0000_1, checke = 5'b0001_0, checkla = 5'b0010_0, checklb = 5'b0100_0, checko = 5'b1000_0; reg [4:0] cstate; reg [4:0] nstate; //第一段,同步时序的always模块,用于状态转移: //复位信号,clk的处理(主要是对初始状态进行赋值操作) always @(posedge clk or negedge rst_n) if(!rst_n) begin cstate <= checkh; end else cstate <= nstate; //第二段 组合逻辑的always模块,描述状态转移条件判断 //状态迁移的处理 always @(cstate or data) case (cstate) checkh: if(data == "h") nstate <= checke; else nstate <= checkh; checke: if(data == "e") nstate <= checkla; else nstate <= checkh; checkla: if(data == "l") nstate <= checklb; else nstate <= checkh; checklb: if(data == "l") nstate <= checko; else nstate <= checkh; checko: if(data == "o") begin nstate <= checkh; end else nstate <= checkh; default:nstate <= checkh; endcase //第三段,输出数据的处理 //输出数据的处理 always @(posedge clk or negedge rst_n) if(!rst_n) begin led <= 1'b1; end else case (cstate) checko: if(data == "o") led <= ~led; default; endcase endmodule
接下来我将使用板载资源进行实验。
我们打算做一个交通灯,对于本交通系统的状态机分解如下:
交通灯主路上绿灯持续22秒时间,黄灯4秒时间,红灯16秒时间;
交通灯支路上绿灯持续12秒时间,黄灯4秒时间,红灯26秒时间;
然后,我们利用板载资源对以上状态序列进行实验。
module Traffic_Light ( input clk_in, input rst_n_in, output reg [2:0] led_master, //R,G,Y output reg [2:0] led_slave, //R,G,Y output [8:0] segment_led_1, //MSB~LSB = SEG,DP,G,F,E,D,C,B,A output [8:0] segment_led_2 //MSB~LSB = SEG,DP,G,F,E,D,C,B,A ); localparam S1 = 2'b00, //master green slave red S2 = 2'b01, //master yellow slave red S3 = 2'b10, //master red slave green S4 = 2'b11; //master red slave green localparam RED = 3'b011, GREEN = 3'b101, YELLOW = 3'b110; reg clk_1Hz; reg [23:0] cnt; //Generate 1Hz signal always @(posedge clk_in or negedge rst_n_in) begin if(!rst_n_in) begin cnt <= 0; clk_1Hz <= 0; end else if(cnt == 24'd5_999_999) begin cnt<=0; clk_1Hz <= ~clk_1Hz; end else cnt<=cnt+1'b1; end reg [7:0] timecnt; reg [1:0] c_state,n_state; //同步状态转移 always @(posedge clk_1Hz or negedge rst_n_in) if(!rst_n_in) c_state <= S1; else c_state <= n_state; //判断转移条件 always @(c_state or timecnt) if(!rst_n_in)begin n_state = S1; end else begin case(c_state) S1: if(!timecnt) n_state = S2; else n_state = S1; S2: if(!timecnt) n_state = S3; else n_state = S2; S3: if(!timecnt) n_state = S4; else n_state = S3; S4: if(!timecnt) n_state = S1; else n_state = S4; default:n_state = S1; endcase end //同步逻辑输出 always @(posedge clk_1Hz or negedge rst_n_in) begin if(!rst_n_in)begin timecnt <= 8'h21; led_master <= GREEN; led_slave <= RED; end else begin case(n_state) S1: begin led_master <= GREEN; led_slave <= RED; if(timecnt==0) begin timecnt <= 8'h21; end else begin if(timecnt[3:0]==0) begin timecnt[7:4] <= timecnt[7:4] - 1'b1; timecnt[3:0] <= 4'd9; end else timecnt[3:0] <= timecnt[3:0] - 1'b1; end end S2: begin led_master <= YELLOW; led_slave <= RED; if(timecnt==0) begin timecnt <= 8'h03; end else begin if(timecnt[3:0]==0) begin timecnt[7:4] <= timecnt[7:4] - 1'b1; timecnt[3:0] <= 4'd9; end else timecnt[3:0] <= timecnt[3:0] - 1'b1; end end S3: begin led_master <= RED; led_slave <= GREEN; if(timecnt==0) begin timecnt <= 8'h15; end else begin if(timecnt[3:0]==0) begin timecnt[7:4] <= timecnt[7:4] - 1'b1; timecnt[3:0] <= 4'd9; end else timecnt[3:0] <= timecnt[3:0] - 1'b1; end end S4: begin led_master <= RED; led_slave <= YELLOW; if(timecnt==0) begin timecnt <= 8'h03; end else begin if(timecnt[3:0]==0) begin timecnt[7:4] <= timecnt[7:4] - 1'b1; timecnt[3:0] <= 4'd9; end else timecnt[3:0] <= timecnt[3:0] - 1'b1; end end default:; endcase end end //Segment led display Segment_led Segment_led_uut ( .seg_data_1(timecnt[7:4]), //seg_data input .seg_data_2(timecnt[3:0]), //seg_data input .segment_led_1(segment_led_1), //MSB~LSB = SEG,DP,G,F,E,D,C,B,A .segment_led_2(segment_led_2) //MSB~LSB = SEG,DP,G,F,E,D,C,B,A ); endmodule
在最后,使用segment模块进行对数码管的操作,体现了模块思想。附数码管控制模块代码(之前我们做数码管实验生成的。)
module Segment_led ( input [3:0] seg_data_1, //seg_data input input [3:0] seg_data_2, //seg_data input output [8:0] segment_led_1, //MSB~LSB = SEG,DP,G,F,E,D,C,B,A output [8:0] segment_led_2 //MSB~LSB = SEG,DP,G,F,E,D,C,B,A ); reg[8:0] seg [9:0]; initial begin seg[0] = 9'h3f; // 0 seg[1] = 9'h06; // 1 seg[2] = 9'h5b; // 2 seg[3] = 9'h4f; // 3 seg[4] = 9'h66; // 4 seg[5] = 9'h6d; // 5 seg[6] = 9'h7d; // 6 seg[7] = 9'h07; // 7 seg[8] = 9'h7f; // 8 seg[9] = 9'h6f; // 9 end assign segment_led_1 = seg[seg_data_1]; assign segment_led_2 = seg[seg_data_2]; endmodule
最后综合下载到板上,效果如下:
状态1 主绿灯 从红灯 倒计时
状态2 主黄灯 从红灯
状态3 主红灯 从绿灯
状态4 主红灯 从黄灯(图略)
2.6 IP核例化
注:IP核使用在各家(Xilinx、Altera、Lattice)提供的工具链中,具体操作不尽相同。本节中,使用的Lattice提供的Diamond开发工具(我购买的FPGA开发板是Lattice的)。之后很可能使用的是Xilinx或Altera的产品。因此我们将理解重点放在IP核使用的一般流程上。
IP核,全称知识产权核(英语:intellectual property core),是指某一方提供的、形式为逻辑单元、芯片设计的可重用模块。调用IP核能避免重复劳动,大大减轻工程师的负担。
IP核分为软核、硬核和固核。软核通常是与工艺无关、具有寄存器传输级硬件描述语言描述的设计代码,可以进行后续设计。
通俗地说,IP核就是在我们使用厂商提供的开发工具时能直接拿来利用的模块,往往配以图形化界面进行例化和配置。
常见的IP核类型:
PLL配置
RAM
IO相关
乘法器加法器等算法核、滤波器
在Diamond中,我们进入IP核例化界面,如下:
选择我们需要使用的IP核。我们选择PLL进行探索:
从此可以使用图形化界面对这块PLL进行设置,包括输入的设置,PLL分频即功能设置,多PLL输出倍频设置。
设置完毕后,我们输出该IP核。
IP核被添加进我们的工程中。
注:我们之前用Verilog进行编程时,每个部分都写作module。我们引用的PLL的IP核,就是一个module,用来减轻我们工作量的可靠的module。
!!!
博主在学习FPGA时,肯定也要学习VHDL。请问有什么优秀的VHDL的教程可以参考吗?
@R FPGA我在做完这个实验因为项目变动搁置了……我的经验肯定没有你深,教程啥的还得向你求教呢。
之前也调研了一些关于Verilog和VHDL的比较,貌似现在很多资料都是Verilog的,VHDL在大学教材里更多一些
催更
@fandy gengle.
博主这么喜欢Arduino,有没有考虑过参加泰雷兹杯Arduino竞赛?
@Re'e 哇,看起来很高端的样子!之前没想到去参赛啥的,可以看一下~
团长现在研究生啥方向啊
@匿名 动力机械及工程,做控制