FPGA:(Field Programmable Gate Array,现场可编程门阵列)
1.是基于查找表的CLB阵列。(Configurable Logic Block,可编程逻辑块,由 多个基本的LUT查找表、REG寄存器、MUX多路选择器组成)
2.基于SRAM运行。(Static Random-Access Memory,静态随机存储器)
3.FPGA的前身为PLD(可编程逻辑器件)和CPLD(复杂可编程逻辑器件-与或阵列可编程)。
FPGA的组成:
- 可编程输入/输出单元(IOB)
- 基本可编程逻辑单元(CLB)
- 完整的时钟管理单元(DCM、PLL)
- 嵌入式块状RAM
- 丰富的布线资源
- 内嵌的底层功能单元和内嵌专用硬件模块(DSP、Serdes)
PLL:(Phase Locked Loop) 为锁相回路或锁相环,用来统一整合时钟信号,使高频器件正常工作,如内存的存取资料等。PLL用于振荡器中的反馈技术。
DCM(digital clock manager):集成的专门用于时钟综合、消除时钟偏斜和进行时钟相位调整的固件资源,利用DCM完成时钟倍频、分频、相移
DSP(Digital Signal Processing)数字信号处理
ASIC (Application Specific Integrated Circuit)专用集成电路
RTL(Register Transfer Level) 寄存器传输级。
与单片机的区别:
系统结构:单片机—冯诺依曼,FPGA—查找表
执行方式:单片机—串行执行,FPGA—并行执行
语言类型:单片机—汇编语言,FPGA—Verilog HDL/VHDL
FPGA优缺点:
1.并行处理,处理速度快。FPGA不擅长顺序结构,更擅长并行结构。如果要使用FPGA处理顺序结构的算法,比较常用有限状态机模型(FSM)。
2.引脚多,即可编程逻辑器件门电路数更多,但也容易造成资源剩余。
3.可编程。FPGA可以反复使用,内部的逻辑可根据需求改变,减少开发成本。
4.低延迟。FPGA的延迟往往是确定的,通常专用性更强:无需依赖通用操作系统,也无需通过通用总线(例如USB或PCIe)通信。
5.可定制,功耗低。
FPGA开发流程:
//3-6步quartus帮我们做了
1.设计输入—Verilog HDL/HDL编码
2.RTL功能仿真—验证逻辑功能/数据流—modelsim工具
3.分析综合—将设计映射为器件模型,生成网表文件
4.布局布线—功能映射,指定布线资源
5.门级仿真—根据估计的布局布线延时进行时序仿真
6.时序分析—验证设计是否满足时序和性能要求—timing analyzer工具
7.板级验证—signal tap嵌入式逻辑分析工具
组合逻辑电路—特点是任意时刻的输出仅仅取决于该时刻的输入,与电路原来的状态无关。
时序逻辑电路—特点是任意时刻的输出不仅取决于当时的输入信号,而且还取决于电路原来的状态,或者说,还与以前的输入有关。时序逻辑的存储电路一般由锁存器,触发器和寄存器组成。利用触发器的记忆功能即存储一位二进制码,将n个触发器的时钟端口连接起来,构成存储n位二进制码的寄存器。寄存器和锁存器功能相同,区别在于寄存器是时钟控制,锁存器是电平信号控制。D寄存器和D锁存器的区别——一个是边沿触发,一个是电平触发。
两者的区别与联系:
时序逻辑相当于在组合逻辑的输入上加上一个反馈输入。组合逻辑电路没有存储记忆,时序逻辑电路却包含了存储记忆。电路中有一个存储电路,可以保持输出的状态。组合逻辑电路只是包含了电路,但是时序逻辑电路包含了组合逻辑电路+存储电路,输出状态必须反馈到组合电路的输入端,与输入信号共同决定组合逻辑的输出。
HDL,Hardware Description Language,硬件描述语言。所见即所得,描述硬件功能,而不是告知程序如何运行。综合性更强,不太注重功能模块的逻辑顺序。
q: 代码中的数字,最终都会转换成二进制的01?但不表示数值而是信号?
module module_name(); endmodule //模块定义
assign out=in; //是持续性的赋值;
always@(*)begin end //则是执行语句时的过程赋值。
//总结连续赋值:只要右边表达式任一个变量有变化,
//表达式立即被计算,计算的结果立即赋给左边信号。
parameter MAX_NUM=100 //全局变量
localparam MAX_NUM=100 //局部变量
//用parameter来定义一个标志符代表一个常量,称作符号常量.他可以提高程序的可读性和可维护性。
//parameter是参数型数据的关键字,在每一个赋值语句的右边都必须是一个常数表达式。即该表达式只能包含数字或先前已经定义的参数。
reg
//寄存器数据类型,reg类型数据的默认初始值为不定值x。
//reg类型的数据只能在always语句和initial语句中被赋值。
//如果过程语句描述的是时序逻辑,即always语句有时钟信号,则寄存器变为触发器;
//如果过程语句描述的是组合逻辑,即always语句没有时钟信号,则该寄存器变为硬件连线。
//reg型只是表示被定义的信号将被用在always模块中,并不是说reg型数据就一定是存储器或触发器的输出。
wire
//默认wire的位宽为1位,默认为高阻值z。
//线网型数据类型,驱动线网型变量的元件有逻辑门、连续赋值语句(assign)等。
//如果没有驱动元件连接到线网类型的变量上,则该变量就是高阻的,即为Z。
//{}的拼接功能
{3'b111, 3'b000} => 6'b111000
{1'b1, 1'b0, 3'b101} => 5'b10101
{4'ha, 4'd10} => 8'b10101010 // 十进制和十六进制转二进制再拼接
//{}的复制功能
{5{1'b1}} // 5'b11111 (or 5'd31 or 5'h1f)
{2{a,b,c}} // 等同于{a,b,c,a,b,c}
{3'd5, {2{3'd6}}} // 9'b101_110_110. 5-->101,6-->110,101和两个重复的110粘连起来。
//将某些信号的某些为列出来,中间用逗号分开,最后用大括号括起来表示一个整体的信号。
//在位拼接的表达式中不允许存在没有指明位数的信号。
b<=a; //非阻塞赋值
//在语句块中,上面语句所赋值的变量不能立即为下面的语句所用;
//块结束后才能完成这次赋值操作,赋值的值为上次赋值得到的;
//在编写可综合的时序逻辑模块时,这是最常用的复制方法。
b=a; //阻塞赋值
//赋值语句完成后,块才结束;
//b的值在赋值语句执行完后立刻改变。
//八大原则
1)时序电路建模时,采用非阻塞赋值;
2)锁存器电路建模时,采用非阻塞赋值;
3)用always块建立组合逻辑模型时,采用阻塞赋值;
4)用always块建立时序和组合逻辑混合电路时,采用非阻塞赋值;
5)不要在同一个always块中同时使用非阻塞赋值和阻塞赋值;
6)不要在一个以上的always块中为同一个变量赋值;
7)用$strobe系统任务来显示用非阻塞赋值的变量值;
8)在赋值时,不要用#0延迟;
时钟信号是高扇出控制信号,由振荡器(信号源)、定时唤醒器、分频器等组成的电路。
包括晶振和RC振荡器
所谓高扇出,当某一信号高扇出时:是指该信号被后面多个模块使用。具体扇出多少算是高扇出,这跟时钟频率有关系,时钟频率越高,所允许的扇出数越低。
影响:高扇出的直接影响就是net delay (网络延迟)比较大,影响时序收敛。
时钟周期的大小对于数字电路设计的影响主要在于触发器之间的所有逻辑电路需要在一个时钟周期内完成所需的逻辑运算并满足触发器的建立时间和保持时间的要求。
作用:
1.同步系统的各器件(CPU、内存、总线等)的工作;
2.驱动处理器内核、外设部件(串口数据发送,定时器计数等)
改进:高扇出常用的三种改进方法:
1.复制寄存器;
2.max_fanout 属性;
3.复位信号可使用BUFG优化。
参考.
同步系统:
各个D触发器的时钟端连在一起,并接在系统时钟端,只有在时钟上升沿到来时,电路的状态才会改变。所有触发器的状态变化都与所加时钟脉冲信号同步。
同步电路的时钟之间有固定的因果关系。
优点:
1.避免毛刺
2.利于器件移植
3.利于静态时序分析
4.验证设计时序性能
缺点:
1.时钟频率高,造成时钟偏移,逻辑电路的可用路径缩短,增加设计难度,增加能源消耗(即使不工作)。
2.同步设计中,最高时钟频率取决于延迟最大的逻辑电路的路径延迟,即工作速度受到最慢电路的限制,导致部分逻辑电路会浪费一个时钟周期中的部分时间用于等待速度较慢的电路。
异步系统:
电路没有统一时钟,时钟之间没有固定的因果关系,即整个系统由多个时钟或无时钟驱动。
优点:
1.不依赖时钟,利于提高工作速度。
2.利于降低系统功耗。
3.由于电路计算的结果实时传输给后续的电路,利于在异步电路系统中建立不平衡但速度更快的流水线,因此不存在时钟周期的浪费。
缺点:
干扰会逐级传递。
在同步电路中,由于时钟的存在,电路产生的干扰和毛刺可以有效的滤除,所有的触发器只会在时钟上升沿产生新的输出值,即使在时钟上升沿之间输入数据被干扰,干扰也不会被传递到下一个逻辑单元或者输出端。
复位信号主要用于将数字电路中的触发器强制设置到一个确定的初始值上,从而使状态机,计数器以及其他控制电路可以从一个已知的初始状态开始工作。
跑马灯
关键代码片
always@(posedge clk or negedge rst_n)begin //不是从复位信号开始的,那么灯泡不亮
if(!rst_n)begin
led <= 4'b0000; //led灯高电平有效,低电平无效,1亮0不亮,这是赋值的方式,不是小于等于,非阻塞赋值
end
else if(cnt == 24'd9_999_999&& flag==1)begin //0.2s else if(cnt == 25'd10_000_000)begin led <= {led[2:0],led[3]};
led <= 4'b0001;
end
else if(cnt == 25'd20_000_000 - 1'd1&& flag==1)begin //0.4s 为什么不都是四百万,是因为并行的,用多少给多少
led <= 4'b0011;
end
else if(cnt == 25'd30_000_000 - 1'd1&& flag==1)begin //0.6s 0100是二进制,此时表示的是信号,而不是数值
led <= 4'b0111;
end
else if(cnt == 26'd40_000_000 - 1'd1&& flag==1)begin //0.8s 非阻塞赋值是指,所有赋值同时发生
led <= 4'b1111;
end
else if(cnt == 24'd9_999_999&& flag==2) begin
led <= 4'b1110;
end
else if(cnt == 25'd20_000_000 - 1'd1&& flag==2)begin
led <= 4'b1100;
end
else if(cnt == 25'd30_000_000 - 1'd1&& flag==2)begin
led <= 4'b1000;
end
else if(cnt == 26'd40_000_000 - 1'd1&& flag==2)begin
led <= 4'b0000;
end
else begin
led <=led; //否则让led保持当前状态,之前的状态,直到时钟振动到特定次数之前
end
end
信号分析
演示结果
短视频.
之前学到,如果要使用FPGA处理顺序结构的算法,比较常用有限状态机模型(FSM)
这是由于实际工程应用中,按照人的思想,我们需要让硬件实现一些具有一定逻辑顺序的工作。
状态机在数字系统设计中非常重要,由多个相互跳转的状态组成,用于对具有逻辑顺序和时序顺序的事件进行描述,在任意时刻,状态机只能处于某一个状态。
状态机有四个要素:现态、次态、输入、输出;
现态:状态机现在的状态;
次态:根据现态、输入、状态转移条件三者判断状态机将要跳转至的新状态;
输入:外部事件,一个外部事件发生,状态机便会根据状态转移函数发生响应。
输出:由现态或者现态和输入共同决定。但是某一状态或某一输入改变,输出并一定会发生变化,可能只是带来状态的迁移。
状态机分为摩尔型和米勒型
摩尔型(Moore):输出仅仅依赖于当前状态,与输入条件无关;
米勒型(Mealy):输出不仅依赖当前状态而且依赖该状态接下来的的输入条件。
输出时序上,Moore状态机同步输出,Mealy状态机异步输出;
输出变化上,Mealy状态机比Moore状态机领先一个时钟周期;
两种状态机均为寄存器型输出:具有更好的时序性能。
一个健壮的状态机应该具备初始化状态或默认状态:即芯片上电或者复位之后,状态机能够自动将所有判断条件复位,并进入初始状态。(大多数FPGA都有GSR,全局复位信号,上电之后,GSR有效,对所有寄存器、RAM等单元复位/置位)
三段式状态机最简单原理介绍
B站对应博客.
一段式:
所有的状态变化以及输出变化都写在一个always块中(个人认为最接近高级语言的逻辑思想)。该always块中既描述状态的同步转移,又描述状态的输入条件和输出。
二段式:
两个always块描述,其中一个采用同步时序逻辑描述状态转移,另一个采用组合逻辑判断状态转移条件。它需要两个状态——现态和次态。然后通过现态和次态的转换来实现时序逻辑。输出采用组合逻辑。
三段式(推荐):
三个always块描述,一个用同步时序逻辑描述状态转移,一个用组合逻辑判断状态转移条件,在二段的基础上,单独一个描述状态的输出(组合逻辑和时序逻辑均可)。
蜂鸣器音乐代码——过程更复杂,但更体现FPGA分块的过程思想.
1.是否可被综合成电路:
Initial 用于仿真,不能被用于综合成电路;for不仅可以用于仿真还可以被综合成电路——此时不是循环而是并行执行,可用于例化;Assign用于连续赋值,可被综合成电路。
2.同步和异步复位补充知识:同步复位是同步系统中的一部分,因此受时钟影响且在时钟沿来临时才会对整个系统进行复位;同步复位和异步复位不能随意替换使用。
3.米勒状态机是可综合的;可被一段式来描述;对输入的反应更快(是组合逻辑输出,输入变,输出马上变,时序逻辑等待时钟沿变化后再变),在相同的周期内反应,不需要等待时钟。
5.Quartus编程产生的编程文件:sof是SRAM Object File,下载到FPGA中,断电丢失;MCS文件不可以用于Quartus编程;FPGA工程最终产生两种不同用途的文件,分别是.sof和.pof;pof是Programmer Object File,下载到配置芯片中,上电重新配置FPGA。
6.标识符不能以数字开头。
8.不可以在两个或两个以上的always过程中对同一变量赋值;不能对同一变量既进行阻塞赋值,又进行非阻塞赋值。
9.FPGA与CPLD的区别:
内部结构不同,FPGA为LUT,CPLD为Product—term结构;
储存结构不同,FPGA为SRAM结构,需要外挂EEPROM,而CPLD本身就是EEPROM;
资源类型不同,FPGA触发器资源丰富,CPLD中组合电路资源丰富;
使用场景不同,FPGA完成比较复杂的算法,CPLD主要针对控制逻辑。
10.PLL:(Phase Locked Loop),锁相回路或锁相环。
11.(Timing Constraint),时序约束。
12.a&b和a&&b的区别:
a=4’b0010,b=4’b0001,a&b=4’0000,a&&b=1
13.设计输入完成后,应立即对文件进行编译/仿真。
14.Verilog HDL中的标识符(identifier)可以是任意一组字母、数字、$符号和_(下划线)符号的组合,但标识符的第一个字符必须是字母或者下划线。另外,标识符是区分大小写的。
15.布局指的是将逻辑网表中的硬件原语或底层单元适配到FPGA固有硬件结构上;布线指的是根据布局的拓扑结构,将所需要的逻辑块正确合理的连接,搭建具有特定功能的模块电路。
16.仿真可以分为前仿真和后仿真。
前仿真是对综合后的网表进行的仿真,它验证设计模块的基本逻辑功能,但不带有布局布线后产生的时序信息,是理想情况下的验证。前仿真是功能仿真,目标是分析电路的逻辑关系的正确性,仿真速度快,可以根据需要观察电路输入输出端口和电路内部任一信号和寄存器的波形;
后仿真是将电路的门延迟参数和各种电路单元之间的连线情况考虑在内后进行仿真,得到的仿真结果==接近真实的应用情况,是时序仿真。==后仿真的速度相对于前仿真慢得多,在观测内部节点波形时比较困难,在一个完整的电路设计中应该包括这两个过程。
17.D寄存器和D锁存器的区别——一个是边沿触发,一个是电平触发。
18.if-else和case语句的区别:
case为多路选择器,是并行选择,各个分支之间相互排斥,if-else有优先级,按照代码顺序进行判断,允许嵌套,占用资源较多。
19.前仿真和后仿真
前仿真是功能仿真,不带时延的仿真,是综合布局布线前的仿真,只有相应的逻辑电路;
后仿真是带时延的仿真,是布局布线之后的仿真,仿真考虑到了电路中门的延迟等。
两个按键分别控制一秒呼吸灯和3秒呼吸灯。自顶向下还不熟练因此直接且只从灯泡控制开始分析:
module led_ctrl (
input clk ,
input rst_n ,
// input [1:0] mod_sel ,//模式选择
output [3:0] led
);
parameter TIME_100MS = 5_000, //50 000 000 Hz为1s,5000为0.0001s,0.1ms,100us
TIME_300MS = 15_000;
//信号定义
reg [13:0] X ;
reg [13:0] cnt0 ;//基准定时 T = 100us/300us
wire add_cnt0 ;
wire end_cnt0 ;
reg [6:0] cnt1 ;//100个基准时间 计10ms或30ms
wire add_cnt1 ;
wire end_cnt1 ;
reg [6:0] cnt2 ;//100个10ms、30ms 1S / 3S
wire add_cnt2 ;
wire end_cnt2 ;
// reg dir_flag ;//方向寄存器
// reg mode_flag ;//模式标志
// reg [1:0] sel_flag ;//模式选择寄存
reg [3:0] led_r ;//灯泡输出寄存器
/计数器
always @(*)begin
if(mode_flag == 0)begin //默认模式为1s呼吸灯
X = TIME_100MS;
end
else begin //按键按下切换模式
X = TIME_300MS;
end
end
always @(posedge clk or negedge rst_n) begin //计数 T 100us / 300us
if (rst_n==0) begin
cnt0 <= 0;
end
else if(add_cnt0) begin
if(end_cnt0)
cnt0 <= 0;
else
cnt0 <= cnt0+1 ;
end
end
assign add_cnt0 = 1'b1;
assign end_cnt0 = add_cnt0 && (cnt0 == X-1) ; //计数5000或15000次,时间经过100us或者300us
always @(posedge clk or negedge rst_n) begin //计数 100 个 T 10ms / 30ms
if (rst_n==0) begin
cnt1 <= 0;
end
else if(add_cnt1) begin
if(end_cnt1)
cnt1 <= 0;
else
cnt1 <= cnt1+1 ;
end
end
assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && (cnt1 == 100-1);
always @(posedge clk or negedge rst_n) begin //计数 100 个 100*T 1s / 3s
if (rst_n==0) begin
cnt2 <= 0;
end
else if(add_cnt2) begin
if(end_cnt2)
cnt2 <= 0;
else
cnt2 <= cnt2+1 ;
end
end
assign add_cnt2 = end_cnt1;
assign end_cnt2 = add_cnt2 && (cnt2 == 100-1) ;
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
dir_flag <= 1'b0; //初始为0
end
else if(end_cnt2)begin
dir_flag <= ~dir_flag; //一个周期 即时间经过1s 之后为真
end
end
//控制灯泡输出
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
led_r <= 4'b0000; //初始不亮
end
else if(dir_flag == 1'b0 && cnt1 <= cnt2)begin //保证了先计时10ms再1s的先后顺序,即同一个计时循环内,因为
//代码并行运行的原因。计数小于等于达到了呼吸的效果——亮的时间越来越长
led_r <= 4'b1111;
end
else if(dir_flag && cnt1 <= 99-cnt2)begin
led_r <= 4'b1111;
end
else begin
led_r <= 4'b0000;
end
end
assign led = led_r;
endmodule
FPGA面试专题——地址生成器设计.
过程验证:
首先直接看第95行代码,了解四个计数器是用来做什么的:
addr <= cnt4*16 + cnt3*1 + cnt2*2 + cnt1*8;
程序开始,所有计数器取0;
第一个加法器:
else if (add_cnt1)
begin //在加1 条件下
if(end_cnt1) cnt1 <= 0; //判断是否为最后一个值, 如果是 ,计数器清0,
else cnt1 <= cnt1 + 1'b1;//如果不是,计数器加1
end
else cnt1 <= cnt1;
end
assign add_cnt1 = 1 ; //一直数数
assign end_cnt1 = add_cnt1 && cnt1 == 2-1; //end_cnt是最后一个值
cnt1取值0和1。取1的时候,endcnt1取1,稍后cnt1取0,这里有个逻辑顺序,endcnt1为真,第二个加法器开始计数;
cnt1取0:0+0+0+0=0;
cnt1取1:0+0+0+1*8=8。
endcnt1为真,cnt1归0。
第二个加法器:
else if (add_cnt2)
begin //在加1 条件下
if(end_cnt2) cnt2 <= 0; //判断是否为最后一个值, 如果是 ,计数器清0,
else cnt2 <= cnt2 + 1'b1;//如果不是,计数器加1
end
else cnt2 <= cnt2;
end
assign add_cnt2 = end_cnt1 ; //add_cnt 为加1的条件
assign end_cnt2 = add_cnt2 && cnt2 == 4-1; //end_cnt是最后一个值
等cnt1取完0、1之后,cnt2才加1,也有逻辑顺序,而非并行执行;
cnt2可取1、2、3、0。取3的时候,第三个加法器开始计数,此时cnt2取0。
承接上一步,cnt1归0后,endcnt1为真,之后cnt2取1:
0+0+2* 1+0=2,
cnt1取1,cnt2保持(endcnt1不为1,不是并行执行该条判断语句,而是稍后执行):
0+0+2* 1+8=10,
cnt1又归零(endcnt1为1,这时cnt2才加一),cnt2取2:0+0+2* 2+0=4,
同理,cnt1取1,cnt2保持:0+0+2* 2+1* 8=12,
cnt1归0,cnt2取3:0+0+2* 3+8* 0=6,
//判断endcnt2的语句要稍后执行,即等cnt1取完一次0、1之后才执行,不是并行执行
cnt1取1,cnt2保持:0+0+2 *3+ 1 *8=14,
//endcnt1为1,endcnt2,为1,不再继续加,直接归零。
之后cnt1取0,cnt2取0。
第三个加法器:
else if (add_cnt3)
begin //在加1 条件下
if(end_cnt3) cnt3 <= 0; //判断是否为最后一个值, 如果是 ,计数器清0,
else cnt3 <= cnt3+ 1'b1;//如果不是,计数器加1
end
else cnt3 <= cnt3;
end
assign add_cnt3 = end_cnt2 ; //add_cnt 为加1的条件
assign end_cnt3 = add_cnt3 && cnt3 == 2-1; //end_cnt是最后一个值
cnt3可取1、0。
承接上一步,cnt1立马取0,cnt2立马取0,cnt3取1:0+1*1+0+0=1
cnt1取1,cnt2取0,cnt3保持(要从最内嵌的加法器开始,因此是稍后执行endcnt3=1的语句):0+1 *1+0+1 *8=9
cnt1归零,cnt2此时取1(endcnt1为1),cnt3保持:
0+1 *2+1 *1+0=3
cnt1取1,cnt2保持,cnt3保持:
1 *8+1 *2+1 *1+0=11
cnt1取0,cnt2加一,取2,cnt3保持:
0+2 *2+1 *1+0=5
cnt1取1,cnt2保持,cnt3保持:
1 *8+2 *2+1 *1=13
cnt1取0,cnt2加一取3,cnt3保持:
0+3 *2+1 *1=7
cnt1取1,cnt2保持,cnt3保持:
1 *8+3 *2+1 *1=15
cnt1取0,cnt2取0,endcnt2为真,这时才判断endcnt3为真,cnt3取0,cnt4加一
第四个加法器:
else if (add_cnt4)
begin //在加1 条件下
if(end_cnt4) cnt4 <= 0; //判断是否为最后一个值, 如果是 ,计数器清0,
else cnt4 <= cnt4 + 1'b1;//如果不是,计数器加1
end
else cnt4 <= cnt4;
end
assign add_cnt4 = end_cnt3 ; //add_cnt 为加1的条件
assign end_cnt4 = add_cnt4 && cnt4 == 5-1; //end_cnt是最后一个值
cnt4取1、2、3、4、0。
承接上一步:
cnt1取0,cnt2取0,endcnt2为真,这时才判断endcnt3为真,cnt3取0,cnt4加一
0 *8+0 *2+0 *1+1 *16=16
cnt1取1,cnt2保持,cnt3保持,cnt4保持:
1 *8+0+0+16=24
cnt1取0,cnt2加一,取1,cnt3保持,cnt4保持:
0+1 *2+0+16=18
cnt1取1,cnt2保持,cnt3保持,cnt4保持:
8+2+0+16=26
cnt1取0,cnt2加一,取2,cnt3保持,cnt4保持:
0+4+0+16=20
以此类推
首先是FPGA的设计思想之一:利用加法器。加法器就是加法运算,并且只能从0开始计数。
那么这道题用数学的思想来看,就是说这一组数,同一行之间,上下两列之间有某种联系,同一行两两之间也有某种联系。我们只能用a+b+c…和的形式来表示这一组数。
第一行第一个为0,并且假设只用一个加法器,那么直接输出:
addr <= cnt1;
第二个为8,要在满足第一个为0的前提下输出:
addr <= cnt1*8;
第三个为2,只能额外增加计数器,并且令cnt1为0,即cnt1只记两个数0、1,那么cnt2一开始为0,且cnt2*2,此时cnt1取0,cnt2取1
addr <= cnt1*8+cnt2*2;
第四个为10,cnt1取0、1,cnt2暂不清楚,要等于10只能:8+2,即cnt1取1,cnt2取1。
我们对比两次cnt1和cnt2的值,发现cnt2保持不变。cnt1在0和1之间切换(可以继续研究4、12…)
也就是说,这两个加法器并不是独立计算的,而是有联系的,又根据同步系统设计思想(异步的暂且不会),两个加法器同一个时钟,只能是嵌套的——cnt1加完,cnt2才开始加,有一定的逻辑顺序。
那么接下来遇到1,同理需要增加一个计数器,让cnt1=0,cnt2=0,cnt3*1:
addr <= cnt1*8+cnt2*2+cnt3*1;
这时,我们发现,cnt1实际上控制着两组数,且cnt1取值只有两种,那么我们可以推测,cnt1的取值种类决定着几个数为一组。
同理cnt2的取值种类,决定了有多少组。cnt1和cnt2嵌套着相加,共同决定了每两个数为一组,共八个数有相同规律,即有8÷2=4组,因此cnt2取值0、1、2、3。
当我们增加了一个计数器cnt3,cnt3的取值种类决定了每8个数为一组的组数。第一行共16个数,刚好每8个数一组,各有各的规律。因此cnt3取值0、1。且代码如下:
addr <= cnt1*8+cnt2*2+cnt3*1;
取9的时候,cnt1取1,cnt2取0,cnt3取1,我们发现,cnt1 *8还决定着,两个数为一组,相差的值为多少。可以推测,如果第一行有18个数,且第17个数为x,那么第18个数为x+8,
第一行和第二行相差16,第一行的规律已经确定,那么只需要在第一行的基础上加上16就完成。
不考虑换行:
addr <= cnt1*8+cnt2*2+cnt3*1+cnt4*16;
上图是六个(主要的加法器,对应六个位选信号),实则是七个加法器实现时钟显示:
这里以秒钟为例。分为个位和十位,分别寄存。
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt0 <= 'd0;
end
else if(add_cnt0)begin
if(end_cnt0)begin
cnt0 <= 'd0;
end
else begin
cnt0 <= cnt0 + 1'b1;
end
end
else begin
cnt0 <= cnt0;
end
end
assign add_cnt0 = 1'b1;
assign end_cnt0 = add_cnt0 && cnt0 >= CNT_MAX - 1'd1; //一秒钟计数完则停止
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 'd0;
end
else if(add_cnt1)begin
if(end_cnt1)begin
cnt1 <= 'd0;
end
else begin
cnt1 <= cnt1 + 1'b1;
end
end
else begin
cnt1 <= cnt1;
end
end
assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1 >= CNT_SEC_G - 1'd1; //每9个一秒则停止
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt2 <= 'd0;
end
else if(add_cnt2)begin
if(end_cnt2)begin
cnt2 <= 'd0;
end
else begin
cnt2 <= cnt2 + 1'b1;
end
end
else begin
cnt2 <= cnt2;
end
end
assign add_cnt2 = end_cnt1;
assign end_cnt2 = add_cnt2 && cnt2 >= CNT_SEC_S - 1'd1; //每5个10s则停止
分别输出个位与十位,个位满10则换位,即位选加一:
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_sel <= 22'd0;
end
else if(add_10s)begin
if(end_10s)begin
cnt_sel <= 22'd0;
end
else begin
cnt_sel <= cnt_sel + 1'd1;
end
end
else begin
cnt_sel <= 22'd0;
end
end //always end
assign add_10s = 1'b1;
assign end_10s = add_10s && (cnt_sel >= CNT_10S - 1'd1);
//数码管位选信号切换
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sel <= 6'b000_000;
end
else if(end_10s)begin
if(sel == 6'b000_000)begin //每计满10s就换一位
sel <= 6'b111_110;
end
else begin
sel <= {sel[4:0],sel[5]};
end
end
else begin
sel <= sel;
end
end
// data_temp
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_temp <= 4'h0;
end
else begin
case(sel)
6'b110_111:data_temp <= data_min; //分钟显示数据
6'b101_111:data_temp <= data_sec_s; //秒钟十位显示数据
6'b011_111:data_temp <= data_sec_g; //秒钟个位显示数据
endcase
end
end //always end
IP(Intellectual Property) 知识产权
IP核是具有知识产权核的集成电路芯核总称。
是经过反复验证过的、具有特定功能的宏模块,与芯片制造工艺无关,可以移植到不同的半导体工艺中。
IP的使用,能够大幅度减轻设计人员的工作量,提高设计可靠性。
IP核已经变成系统设计的基本单元,并作为独立设计成果被交换、转让和销售。
IP核通常将其分为软核、硬核和固核。
在FPGA设计中指的是对电路的硬件语言描述,包括逻辑描述、网表和帮助文档等。
软核只经过功能仿真,需要经过综合以及布局布线才能使用。软核是IP 核应用最广泛的形式。
1.优点:灵活性高、可移植性强,允许用户自配置;
2.缺点:对模块的预测性较低,在后续设计中存在发生错误的可能性,有一定的设计风险。
在FPGA设计中可以看做带有布局规划的软核,通常以RTL 代码和对应具体工艺网表的混合形式提供。
将RTL描述结合具体标准单元库进行综合优化设计,形成门级网表,再通过布局布线工具即可使用。
和软核相比,固核的设计灵活性稍差,但在可靠性上有较大提高。目前,固核也是IP核的主流形式之一。
在FPGA 设计中指布局和工艺固定、经过前端和后端验证的设计,设计人员不能对其修改——系统设计对各个模块的时序要求很严格,不允许打乱已有的物理版图;保护知识产权的要求,不允许设计人员对其有任何改动。
IP 硬核的不许修改特点使其复用有一定的困难,因此只能用于某些特定应用,使用范围较窄。
简单双口介绍第一句话有歧义,前半句能同时读写操作是指,具备读和写的功能都具备,后半句则是指读和写共用同一个端口,同一组地址线,读和写不能同时进行。
原理简单分析和设计.
FIFO( First Input First Output)先进先出。
FIFO存储器是一个先入先出的双口缓冲器,即第一个进入其内的数据第一个被移出,其中一个是存储器的输入口,另一个口是存储器的输出口。
FIFO存储器分为写入专用区和读取专用区。读操作与写操作可以异步进行,写入区上写入的数据按照写入的顺序从读取端的区中读出,类似于吸收写入端与读出端速度差的一种缓冲器。