软件版本:无 操作系统:WIN10 64bit 硬件平台:适用所有系列FPGA 板卡获取平台:https://milianke.tmall.com/ 登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑! 1概述 近两年来,米联客在提供可靠的FPGA硬件板卡,以及丰富的FPGA应用方案方面,做出了很大的进步。但是米联客的课程文档能算是最棒的吗?是初学者最佳选择的FPGA教程吗?显示不是。 笔者最近一直在思考这个问题。因此在公司内部会议上对新版本教程提出了非常高的要求。目前我们已经完成了,课程大纲的制定,相关的课程编写已经开始,并且一定以高标准,高质量的水平制作出来。我相信这一版本教程肯定给广大FPGA读者带来更好的体验。 当然对于目前市场上鱼龙混杂的电子类书籍我也有吐槽的,精品太少了,内容重复,没有新意。我甚至怀疑很多书籍是水平没多高的人写的,所以写不出深度,写不出新的名堂。作为读者的你们,可以多翻翻自己买的FPGA书籍,看看里面有多少含金量。 米联客目前没有出书的计划,因为我自知我们的教程内容还谈不上经典,所以电子教程会继续提供大家免费阅读和学习,学习的答疑在米联客FPGA社区www.uisrc.com答疑版块进行。这里也感谢广大支持和使用米联客。 学习FPGA编程,一定要搞清楚是时序逻辑电路,什么是组合逻辑电路。我们编写FPGA的代码尽量使用时序逻辑,因为时序逻辑电路,更有利用控制时序上的同步,这本身和目前的FPGA是以LUT查找表的方式实现的逻辑关系有关,而且也和FPGA逻辑规模越来越大有关系。大规模的组合逻辑,不利于时序的控制。 2组合逻辑代码 还记得前面使用LUT资源实现的加法器不?其实那个就是组合逻辑代码,只是你还不认识这种代码。这节课的代码我们稍加改变,增加了always @(*)模块,在always @(*) 模块里面的代码最终会被VIVADO软件编译成组合逻辑电路。always @(*)中”*”符号表示了对所有信号敏感,也就是电路只要有任何的输入改变,输出就会改变。输入到输出除了电路上的延迟是瞬间完成输出的。 - module ADDER(
- input [5:0]A,
- input [5:0]B,
- output [6:0]Q
- );
- reg [6:0 ]Q=7'd0;
- always @(*)begin
- Q = A + B;
- end
- endmodule
复制代码看下代码翻译出来的原理图,如下图所示。
再看下仿真结果如下图,对于代码和仿真文件的编写再后面的章节还有具体讲解,读者先根据我的教程学习,这节课主要掌握组合逻辑电路和时序逻辑电路及代码的差异。
再给出仿真代码,仿真代码的专业描述叫做test bench,就是通过一些测试量的输入,看代码在仿真条件下是否满足功能要求。编写复杂并且正确的测试文件,很多时候比实现具体的功能代码都复杂。如果你从事的IC设计或者验证领域,就必须熟练掌握并且编写test bench文件。IC验证设计,仿真文件的编写可以使用system C会比用纯Verilog更方便。我们做应用开发的,一般不需要编写那么复杂的仿真文件。很多时候我们还会直接用芯片厂家提供的仿真模型,这样大大降低了我们自己开发的难度。 - module adder_tb;
- reg [5:0]A;
- reg [5:0]B;
- wire [6:0]Q;
- ADDER ADDER_inst(
- .A(A),
- .B(B),
- .Q(Q)
- );
- initial begin
- #100;
- # 10 A = 1; B = 1;
- # 5 A = 2; B = 3;
- # 2 A = 3; B = 4;
- # 10 A = 1; B = 2;
- end
复制代码从仿真中也验证了前面我说的内容,对于组合逻辑,除了电路固有的延迟外,输入到输出的反应是非常迅速的。但是如果你觉得速度越快越好,那你就错了,因为如果用纯组合逻辑去写大规模的代码,速度反而快不起来。因为随着组合逻辑的代码规模变大,走线边长等因素,电路会越来越难以满足时序的要求。 3时序逻辑代码 下面是一段基于时序逻辑的代码也是使用always模块,时序逻辑代码在always @(posedge clk)的括号内的敏感信号,一般是时钟上升沿和时钟下降沿,而以时钟上升沿最常用, always模块中的代码会在每个时钟沿执行一次。以下是以时序逻辑实现的加法器的代码。 - module ADDER(
- input clk,
- input rstn,
- input [5:0]A,
- input [5:0]B,
- output [6:0]Q
- );
- reg [6:0 ]Q = 7'd0;
- always @(posedge clk)begin
- if(!rstn)begin
- Q <= 7'd0;
- end
- else begin
- Q <= A + B;
- end
- end
- endmodule
复制代码以下是翻译出来的原理图,可以看到蓝色的部分是多出来的电路,增加的D触发器的部分电路。由于D触发器的CE为1始终有效,所以每个时钟的上升沿,D触发器都会执行一次。
仿真代码的编写,在仿真代码中,我们得确保输入的A和B是基于时钟同步的,只有这样才能确保时序的同步,否则也是无法保证执行结果正确,这就是时序逻辑的特点,我们得确保和时钟同步。 - module adder_tb;
- reg clk;
- reg rstn;
- reg [5:0]A;
- reg [5:0]B;
- wire [6:0]Q;
- ADDER ADDER_inst(
- .clk(clk),
- .rstn(rstn),
- .A(A),
- .B(B),
- .Q(Q)
- );
- initial begin
- #100;
- clk = 0;
- A = 6'd0;
- B = 6'd0;
- rstn = 1'b0;
- #100;
- @(posedge clk); rstn = 1'b1;
- @(posedge clk); A = 1; B = 1;
- @(posedge clk); A = 2; B = 3;
- @(posedge clk); A = 3; B = 4;
- @(posedge clk); A = 1; B = 2;
- end
- always #10 clk = ~clk;
- endmodule
复制代码以下是仿真结果。
时序逻辑电路,里面实际上也是也是有组合逻辑电路,如下图红框所示:
如果简单看我们的仿真代码,仿真代码结果是没问题的,但是我们刚才使用的是Run Behavioral Simulation仿真,这种仿真不能看出电路的延迟,我们看下增加电路延迟后的仿真,这样要选择Run Post-Synthesis Functional Simulation。如下图所示:
仿真结果如下图所示,对于初学FPGA的人来说,是不是有点神奇?输出结果是晚于输入一个时钟周期。没错,以下的执行结果才是最接近真实的执行结果的。通过我们前面的红色框图的组合逻辑知道,输入A和输入B的值改变后,
但是我们仍然又疑问,说好的每个时钟上升沿代码才执行一次代码呢?通过上面的分析我们已经得出结论,逻辑部分,也就是在查找表部分是逻辑时序,除了电路固有的从延迟,是瞬间执行的,但是后面的D触发器需要在下一个时钟才能把数据输出。 这个是不是有点烧脑? 4时序逻辑实现计数器 我们再看一个计数器的仿真,加深下理解时序逻辑电路代码,为了能够让代码简单分析,去掉了复位,以及使用2位的计数器,这样的电路方便我们分析。 - module counter(
- input clk,
- output [1:0]Q
- );
- reg [1:0 ]Q = 2'd0;
- always @(posedge clk)begin
- Q <= Q + 1'b1;
- end
- endmodule
复制代码以下是VIVADO根据代码翻译出来的电路图:
通过查看LUT的属性,可以查到LUT的初值,这个初值是用于查找表实现逻辑电路的。
LUT1的查表值为2’h1查表如下 LUT2的查表值为4’h6查表如下 计数器时序逻辑真值表如下图: LUT1 | Q_reg[0] | LUT2 | Q_reg[1] | Q | | | | | | I1 | O | Q_reg[1]_D | Q_reg[1]_Q | Q_reg[1]_Q | Q_reg[0]_Q | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
上表中,D触发器的Q是在D端数据到达后下一个时钟上升沿输出,本课程的内容理解了对于后面我们分析代码的时序会有很大的帮助。
|