由于项目原因,需要接触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 哇,看起来很高端的样子!之前没想到去参赛啥的,可以看一下~
团长现在研究生啥方向啊
@匿名 动力机械及工程,做控制