Steins;Lab

某团的自留研究所

小脚丫STEP FPGA开发板上手及初阶FPGA实验

由于项目原因,需要接触FPGA。入手了一块入门用的FPGA开发板,体会可编程逻辑器件的思想。

 

 

0 板卡信息

购买的开发板是STEP-MXO2-C

基本信息:

  1. 核心器件:Lattice LCMXO2-4000HC-4MG132
    • 132脚BGA封装,引脚间距0.5mm,芯片尺寸8mm x 8mm;
    • 上电瞬时启动,启动时间<1ms;
    • 4320个LUT资源, 96Kbit 用户闪存,92Kbit RAM;
    • 2+2路PLL+DLL;
    • 嵌入式功能块(硬核):一路SPI、一路定时器、2路I2C
    • 支持DDR/DDR2/LPDDR存储器;
    • 104个可热插拔I/O;
    • 内核电压2.5-3.3V;
  2. 板载资源:
    • 两位7段数码管;
    • 两个RGB三色LED;
    • 8路用户LED;
    • 4路拨码开关;
    • 4路按键;
  3. 36个用户可扩展I/O(其中包括一路SPI硬核接口和一路I2C硬核接口)
  4. 支持的开发工具Lattice Diamond
  5. 支持MICO32/8软核处理器
  6. 板上集成FPGA编程器
  7. 一路Micro USB接口
  8. 板卡尺寸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是可编程的硬件。

相关语法:

  1. begin end 将多条语句组成顺序块,类似c语言的{}
  2. 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相关语法:

  1. 用reg声明寄存器变量 reg [8:0] seg;
  2. 用reg生命存储器(寄存器组)reg [8:0] seg[15:0];
  3. 用存储器操作wire [8:0] a,b; assign a= seg[1]; assign b=seg[1][5:0];
  4. 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语法

  1. parameter定义参数,模块内范围有效,例化时可以通过参数传递更改参数
  2. localparam定义的参数不可传递更改
  3. 除法和取余数尽量少用,耗费资源且不稳定。
  4. 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语法:

  1. {}  表达式为{A,B}  将AB连接起来,产生更大的向量
  2. {{}}  表达式为{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。

 

 

0 0 vote
Article Rating
Subscribe
提醒
guest
9 评论
最新
最旧 得票最多
Inline Feedbacks
View all comments
树
2 年 之前

!!!

R
R
2 年 之前

博主在学习FPGA时,肯定也要学习VHDL。请问有什么优秀的VHDL的教程可以参考吗?

fandy
fandy
2 年 之前

催更

Re'e
Re'e
2 年 之前

博主这么喜欢Arduino,有没有考虑过参加泰雷兹杯Arduino竞赛?

匿名
匿名
2 年 之前

团长现在研究生啥方向啊

9
0
Would love your thoughts, please comment.x
()
x