数字逻辑实践4->面向硬件电路的设计思惟--FPGA设计总述

2021年11月26日 阅读数:1
这篇文章主要向大家介绍数字逻辑实践4->面向硬件电路的设计思惟--FPGA设计总述,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

本文是对实验课上讲解的“面向硬件电路的设计思惟”的总结,结合数字逻辑课本,进行提炼和整理。网络

主要来源是课件与本人整理,部分参考了网络大佬的博客。函数

本文主要介绍不一样于以前软件设计思惟的硬件设计思惟,从非阻塞赋值、并行、面积速度转换、同步电路设计原则、模块划分设计、if-case对比等方面进行整理。工具

内容太多,我整理了好几天,在浩如烟海的网络前有点无力,想一想是本身的实践不够,有一些问题没有亲身体验;也不能一蹴而就,得久久为功。因此这篇文章就看成一个Verilog学习与FPGA设计的总述性文章,后续继续学习我会加深对这些知识的理解。性能

00 阻塞赋值和非阻塞赋值

概念

回忆一下课本上的相关内容。学习

  • 阻塞赋值:优化

    • "="编码

    • Verilog编译器按照这些语句在always块中的前后顺序依次执行spa

    • 若是一个变量经过阻塞赋值语句赋值,则这个新赋的值会在这个block中的后续语句中使用。设计

    • 至关于串行code

  • 非阻塞赋值:

    • "<="

    • always块中全部非阻塞赋值的语句在求值时所用的值是最初进入always时各个变量已经具备的值

    • 换一个角度讲,"<="左侧的被赋值变量,只在always结束时统一被更新。

    • 至关于并行

时序电路实例

阻塞赋值

先来看一看这段代码:

1 module example5_5 (x1, x2, x3, Clock, f, g);
2     input x1, x2, x3, Clock;
3     output reg f, g;
4     always @(posedge Clock)
5     begin
6         f = x1 & x2;
7         g=f | x3;
8     end
9 endmodule

能够看到关键点是g的表达式,因为是阻塞赋值,因此至关于:

 g=(x1 & x2) | x3; 

综合出的电路如图:

非阻塞赋值

1 module example5_6 (x1, x2, x3, Clock, f, g);
2     input x1, x2, x3, Clock;
3     output reg f, g;
4     always @(posedge Clock)
5     begin
6         f <= x1 & x2;
7         g <= f | x3;
8     end
9 endmodule

这个区别之处就在于这个g,是x3与前一个 f 进行"|"运算。

思考

将阻塞赋值的示例代码中的两个执行语句互换位置,会发生什么状况?

能够料见影响比较大。

可见阻塞赋值描述时序电路有风险。

组合逻辑实例

相反的,若是咱们要实现 f = a1a0 + a2a1这样一个函数;

阻塞赋值

1 always @(A)
2 begin
3 f = A[1] & A[0];
4 f=f | (A[2] & A[1]);
5 end

非阻塞赋值

1 always @(A)
2 begin
3 f <= A[1] & A[0];
4 f <= f | (A[2] & A[1]);
5 end

可见这段代码有两个特征;

  1. 非阻塞赋值的结果在always结束后才能够看到

  2. 屡次赋值时,后覆盖前。

这两个特征使得第二个f语句出现问题,由于第二个语句

 f <= f | (A[2] & A[1]); 

右侧的f的值是不可见的。

总结

  • 对组合逻辑建模采用阻塞赋值

  • 对时序逻辑建模采用非阻塞赋值

  • 用多个always块分别对组合和时序逻辑建模

  • 尽可能不要在一个always块里面混合使用阻塞赋值和非阻塞赋值。若是在同一个块即为组合逻辑又为时序逻辑,应使用“非阻塞赋值”

01 程序是并行执行的

这里给的例子没怎么看懂。本身查了一下。

先说结论:

  1. 各个always块是并行执行的,

  2. always块和initial块之间是并行执行的,

  3. begin-end块内是顺序执行的,

  4. 可是非阻塞赋值(<=)是并行执行的,阻塞赋值(=)是顺序执行的,这条优先。且硬件思想的集中体现就是前面提到过的非阻塞赋值带来的并行执行语句。

再回去看例子:

 1 module test ( clk, reset, a, b );
 2     input  clk;
 3     input  reset;
 4     input [ 3:0 ] a;
 5     output [ 3:0 ] b;
 6  7     reg [ 3:0 ] tempa1, tempa2, b;
 8  9     always @ ( posedge clk ) begin
10         if ( ! reset ) begin
11              tempa1<=0;
12              tempa2<=0;
13               b<=0;
14         end
15         else begin
16               tempa1<= a + 1’b1;
17               tempa2<= tempa1 + 1’b1;
18               b<= tempa2 + 1’b1;
19         end
20     end
21 endmodule                  

能够料见实现的电路:

下面借鉴了这篇文章

1.png

波形图:

2.jpg

能够看到强调的仍是上面说过的非阻塞赋值的特色。

02 程序的可综合性

实验任务里总有这么一句:“用可综合的代码......”

什么是可综合性,什么又是不可综合性呢?

下面学习了这篇文章

这篇文章讲的挺多,可是我如今这个RTL级还没搞明白的菜鸟用不到这么多,基本筛选以下:

不可综合的Verilog语句:

  • initial

    只能在test bench中使用,不能综合。

  • assign 和deassign

    不支持对reg 数据类型的assign或deassign进行综合,支持对wire数据类型的assign或deassign进行综合。

  • fork join 不可综合,可使用非块语句达到一样的效果。

    这个块是并行执行的,可是不可综合。

    敏感列表里同时带有posedge和negedge 如:(如今也碰不到

    1 always @(posedge clk or negedge clk) 
    2     begin
    3         ...
    4     end

03 面积和速度的转换

这里咱们说的面积:设计所占用的FPGA逻辑资源数目,通常用所消耗的触发器和查找表(还没学)来衡量。

速度:是指在芯片上能够稳定运行时能达到的最高频率

二者性能上的调配方法:

  1. 模块复用

  2. 串并变换

     

     

    能够看到这种串并转换,用更大的面积(即多个子模块并行),达到高频率的效果。这一点后面还会再提到。

  3. 流水线

后续学习入口

04 同步电路的设计原则

同步设计的优势:

  1. 能够有效避免毛刺的影响,提升设计的可靠性

  2. 能够简化时序分析过程

  3. 能够减小工做环境对设计的影响

设计原则:

(因为应用还很少,对这些体会还不深入,简单记录:

  • 单时钟

    全局时钟网络的时钟是性能最优,最便于预测的时钟,具备最强的驱动能力

  • 单时钟沿

    混合时钟会使时序分析复杂、电路工做频率下降

  • 避免使用门控时钟

    • 即时钟不要与组合逻辑再进行组合,以下:

    •  

       

    • 可能引发毛刺、偏移

  • 在模块内部不要再产生时钟了。

     

05 模块划分的设计原则

封装复用

这有点像C++的封装。

上一层模块只负责下一层模块的依据(即原材料),而具体行为互不相关。

这样就保证了各个模块的相对独立性和内部结构的合理性,便于维护,也使得相同逻辑能够复用同一模块。

同步时序模块的寄存器划分原则

在设计时,应尽可能将模块中的同步时序逻辑输出信号以寄存器的形式送出,以便于综合工具区分时序和组合逻辑;

而且时序输出的寄存器应符合流水线设计思路,能工做在更高的频率,以极大地提升模块吞吐量。

流水线设计思路是什么?

就是将组合逻辑系统地分割,并在各个部分(分级)之间插入寄存器,并暂存中间数据的方法。 目的是将一个大操做分解成若干的小操做,每一步小操做的时间较小,因此能提升频率,各小操做能并行执行,因此能提升数据吞吐率(提升处理速度)。

06 通用代码风格

逻辑复用与逻辑复制

对于我这个初学者来讲,逻辑复用和逻辑复制十分类似,了解后就知道确实不一样,主要是涉及性能衡量尺度:速度和面积的统筹。

  • 逻辑复用是经过提升工做频率来节省面积的优化方法,常常用于存在多个资源可共享单元的设计中。

    • PS:至关于为了节省人力,而让一我的干三我的的活。

      • 10MHZ乘法器

      • 两个5MHZ乘法器

  • 逻辑复制——面积换速度

    • 逻辑复用是经过增长面积而改善设计时序的优化方法,常常用于调整信号的扇出。

      • 举例就相似于上面的面积换速度

逻辑结构

  • 链状结构

  • 树状结构

if和case语句的使用原则

  1. If语句指定了一个有优先级的编码逻辑,

    而case语句生成的逻辑是并行的,不具备优先级。

    这里的if的优先级就引发一些其余问题:

     1 always@(in0,in1,in2,in3) 
     2 begin
     3     sel = 2’b00 ;
     4     if(in0) sel = 2’b00 ;
     5     else if(in1) sel = 2’b01 ; 
     6     else if(in2) sel = 2’b10 ; 
     7     else if(in3) sel = 2’b11 ; 
     8 end
     9 //当这里in0和in1都==1时,se1=2'b00而不是2'b01;
    10 //这就是if-else的优先逻辑。

    而case是并行的,没有优先级。

  2. if语句能够包含一系列的表达式;/有时甚至一个else就能够是一个二路选择器。

    而case语句比较的是一个公共的控制表达式。整个语句块一块儿构成了一个多路选择器。

    这个很好理解。

  3. 一般if-else结构速度较慢,但占用的面积小;

    case语句结构速度较快,但占用的面积较大。

  4. 嵌套的if语句若是使用不当,就会致使设计的更长延时。

  5. 若是想利用if语句来实现那些对延时要求苛刻的路径,应将最高优先级给最迟到达的关键信号。(最小生成树思想)。

  6. 有时为了兼顾面积和速度,能够将if和case语句合用

举例

 1 //if-else实现四选一
 2 module sdata_if (clk, reset, x, s, y);
 3      input clk;
 4      input reset;
 5      input [3:0] x;
 6      input [1:0] s;
 7      output y;
 8  9      reg y;
10      always@(posedge clk)begin
11          if(!reset)begin
12             y<=0;
13           end
14         else begin
15             if(s==2'b00)
16                  y<=x[0];
17             else if (s==2'b01)
18                  y<=x[1];
19             else if (s==2'b10)
20                  y<=x[2];
21                else 
22                  y<=x[3];
23          end
24      end
25 endmodule

想象一下是什么电路?

就是一个四选一,须要两位的控制信号,这个控制信号就正好是s。

 

 

 1 //case语句
 2 module sdata_if (clk, reset, x, s, y);
 3      input clk;
 4      input reset;
 5      input [3:0] x;
 6      input [1:0] s;
 7      output y;
 8  9      reg y;
10      always@(posedge clk)begin
11          if(!reset)begin
12             y<=0;
13           end
14          else begin
15          case(s)
16            2'b00: y<=x[0];
17            2'b01: y<=x[1];
18            2'b10: y<=x[2];
19            2'b11: y<=x[3];
20      end
21 end
22 endmodule
23 24

实现电路也是相似上面的电路。

避免意外锁存器

锁存器在课本里学过,是用于存储一位数据的元件,电平触发。

其特色是:锁存器在不锁存数据时,输出随输入变化;但一旦数据锁存时,输入对输出不产生任何影响。

而咱们在设计电路时,应该避免无心之间产生这种锁存器,不然会致使一些逻辑上的错误。

引发意外锁存器的缘由

翻阅了不少网上的总结。有一些规则比较复杂,考虑到我如今的水平,我只记录对初入门水平够用且好理解的方面。后续继续学习能够深刻了解更多。

  • if……else……结构中缺乏else

  • case结构中的分支没有包含全部状况且没有default语句。

建议

  1. 若是使用if语句,最好写上else分支;

  2. 若是使用case语句,最好写上default语句。

  3. 即便须要锁存器,也经过else分支或default分支来显式说明。而不要利用语言特性触发生成(由于不可控)

内容真的好多,一时间难以彻底消化...

上传于2021年11月25日23时