[X]关闭

23 FPGA知识_认识时序逻辑和组合逻辑代码

文档创建者:FPGA课程
浏览次数:285
最后更新:2024-08-15
FPGA基础知识
FPGA基础: FPGA编程语言 » Verilog编程入门

软件版本:无

操作系统: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 @(*)中”*”符号表示了对所有信号敏感,也就是电路只要有任何的输入改变,输出就会改变。输入到输出除了电路上的延迟是瞬间完成输出的。

  1. module ADDER(
  2. input  [5:0]A,
  3. input  [5:0]B,
  4. output [6:0]Q
  5. );
  6. reg [6:0 ]Q=7'd0;
  7. always @(*)begin
  8.    Q = A + B;
  9. end
  10. endmodule
复制代码

看下代码翻译出来的原理图,如下图所示。

image.jpg

再看下仿真结果如下图,对于代码和仿真文件的编写再后面的章节还有具体讲解,读者先根据我的教程学习,这节课主要掌握组合逻辑电路和时序逻辑电路及代码的差异。

image.jpg

再给出仿真代码,仿真代码的专业描述叫做test bench,就是通过一些测试量的输入,看代码在仿真条件下是否满足功能要求。编写复杂并且正确的测试文件,很多时候比实现具体的功能代码都复杂。如果你从事的IC设计或者验证领域,就必须熟练掌握并且编写test bench文件。IC验证设计,仿真文件的编写可以使用system C会比用纯Verilog更方便。我们做应用开发的,一般不需要编写那么复杂的仿真文件。很多时候我们还会直接用芯片厂家提供的仿真模型,这样大大降低了我们自己开发的难度。

  1. module adder_tb;
  2. reg [5:0]A;
  3. reg [5:0]B;
  4. wire [6:0]Q;
  5. ADDER ADDER_inst(
  6.                 .A(A),
  7.                 .B(B),
  8.                 .Q(Q)
  9.                 );
  10. initial begin
  11.   #100;
  12.   # 10 A = 1; B = 1;
  13.   # 5  A = 2; B = 3;
  14.   # 2  A = 3; B = 4;
  15.   # 10 A = 1; B = 2;
  16. end
复制代码

从仿真中也验证了前面我说的内容,对于组合逻辑,除了电路固有的延迟外,输入到输出的反应是非常迅速的。但是如果你觉得速度越快越好,那你就错了,因为如果用纯组合逻辑去写大规模的代码,速度反而快不起来。因为随着组合逻辑的代码规模变大,走线边长等因素,电路会越来越难以满足时序的要求。

3时序逻辑代码

下面是一段基于时序逻辑的代码也是使用always模块,时序逻辑代码在always @(posedge clk)的括号内的敏感信号,一般是时钟上升沿和时钟下降沿,而以时钟上升沿最常用, always模块中的代码会在每个时钟沿执行一次。以下是以时序逻辑实现的加法器的代码。

  1. module ADDER(
  2. input  clk,
  3. input  rstn,
  4. input  [5:0]A,
  5. input  [5:0]B,
  6. output [6:0]Q
  7. );
  8. reg [6:0 ]Q = 7'd0;
  9. always @(posedge clk)begin
  10.    if(!rstn)begin
  11.      Q <= 7'd0;   
  12.    end
  13.    else begin
  14.      Q <= A + B;
  15.    end
  16. end
  17. endmodule
复制代码

以下是翻译出来的原理图,可以看到蓝色的部分是多出来的电路,增加的D触发器的部分电路。由于D触发器的CE为1始终有效,所以每个时钟的上升沿,D触发器都会执行一次。

image.jpg

仿真代码的编写,在仿真代码中,我们得确保输入的A和B是基于时钟同步的,只有这样才能确保时序的同步,否则也是无法保证执行结果正确,这就是时序逻辑的特点,我们得确保和时钟同步。

  1. module adder_tb;
  2. reg clk;
  3. reg rstn;
  4. reg [5:0]A;
  5. reg [5:0]B;
  6. wire [6:0]Q;
  7. ADDER ADDER_inst(
  8.                 .clk(clk),
  9.                 .rstn(rstn),
  10.                 .A(A),
  11.                 .B(B),
  12.                 .Q(Q)
  13.                 );
  14. initial begin
  15.   #100;
  16.   clk = 0;
  17.   A = 6'd0;
  18.   B = 6'd0;
  19.   rstn = 1'b0;
  20.   #100;
  21.   @(posedge clk); rstn = 1'b1;
  22.   @(posedge clk); A = 1; B = 1;
  23.   @(posedge clk); A = 2; B = 3;
  24.   @(posedge clk); A = 3; B = 4;
  25.   @(posedge clk); A = 1; B = 2;
  26. end
  27. always #10  clk = ~clk;
  28. endmodule
复制代码

以下是仿真结果。

image.jpg

时序逻辑电路,里面实际上也是也是有组合逻辑电路,如下图红框所示:

image.jpg

如果简单看我们的仿真代码,仿真代码结果是没问题的,但是我们刚才使用的是Run Behavioral Simulation仿真,这种仿真不能看出电路的延迟,我们看下增加电路延迟后的仿真,这样要选择Run Post-Synthesis Functional Simulation。如下图所示:

image.jpg

仿真结果如下图所示,对于初学FPGA的人来说,是不是有点神奇?输出结果是晚于输入一个时钟周期。没错,以下的执行结果才是最接近真实的执行结果的。通过我们前面的红色框图的组合逻辑知道,输入A和输入B的值改变后,

image.jpg

但是我们仍然又疑问,说好的每个时钟上升沿代码才执行一次代码呢?通过上面的分析我们已经得出结论,逻辑部分,也就是在查找表部分是逻辑时序,除了电路固有的从延迟,是瞬间执行的,但是后面的D触发器需要在下一个时钟才能把数据输出。

这个是不是有点烧脑?

4时序逻辑实现计数器

我们再看一个计数器的仿真,加深下理解时序逻辑电路代码,为了能够让代码简单分析,去掉了复位,以及使用2位的计数器,这样的电路方便我们分析。

  1. module counter(
  2.     input  clk,
  3.     output [1:0]Q   
  4.     );
  5. reg [1:0 ]Q = 2'd0;   
  6. always @(posedge clk)begin
  7.        Q <= Q + 1'b1;
  8. end
  9. endmodule
复制代码

以下是VIVADO根据代码翻译出来的电路图:

image.jpg

通过查看LUT的属性,可以查到LUT的初值,这个初值是用于查找表实现逻辑电路的。

image.jpg image.jpg

LUT1的查表值为2’h1查表如下

I0
ADDR
O
0
0
1’b1
1
1
1’b0

LUT2的查表值为4’h6查表如下

I0
I1
ADDR
O
0
0
0
1’b 0
1
0
1
1’b 1
0
1
2
1’b 1
1
1
3
1’b 0

计数器时序逻辑真值表如下图:

LUT1
Q_reg[0]
LUT2
Q_reg[1]
Q
I0
O
Q_reg[0]_D
Q_reg[0]_Q
I0
I1
O
Q_reg[1]_D
Q_reg[1]_Q
Q_reg[1]_Q
Q_reg[0]_Q
0
1
1
0
0
0
0
0
0
0
0
1
0
0
1
1
0
1
1
0
0
1
0
1
1
0
0
1
1
1
1
1
0
1
0
0
1
1
1
0
0
1
1
1

上表中,D触发器的Q是在D端数据到达后下一个时钟上升沿输出,本课程的内容理解了对于后面我们分析代码的时序会有很大的帮助。


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则