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

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

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

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

00 阻塞赋值和非阻塞赋值

概念

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

  • 阻塞赋值:

    • "="

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

    • 如果一个变量通过阻塞赋值语句赋值,则这个新赋的值会在这个block中的后续语句中使用。

    • 相当于串行

  • 非阻塞赋值:

    • "<="

    • 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

可以料见实现的电路:

下面借鉴了这篇文章

波形图:

可以看到强调的还是上面说过的非阻塞赋值的特点。

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时

数字逻辑实践4->面向硬件电路的设计思维--FPGA设计总述的更多相关文章

  1. FPGA学习笔记(三)—— 数字逻辑设计基础(抽象的艺术)

    FPGA设计的是数字逻辑,在开始用HDL设计之前,需要先了解一下基本的数字逻辑设计-- 一门抽象的艺术. 现实世界是一个模拟的世界,有很多模拟量,比如温度,声音······都是模拟信号,通过对模拟信号 ...

  2. 转载--关于FPGA设计数字信号处理电路的心得

    FPGA使用的越来越广泛,除了可用于设计控制电路以为,数字信号处理电路更是FPGA的强项和难点.个人可以说才刚刚入门FPGA设计,也做过一些数字信号处理方面的电路设计,记录下个人心得体会. (一)善用 ...

  3. 小梅哥FPGA数字逻辑设计教程——基于线性序列机的TLC5620型DAC驱动设计

    基于线性序列机的TLC5620型DAC驱动设计 目录 TLC5620型DAC芯片概述:    2 TLC5620型DAC芯片引脚说明:    2 TLC5620型DAC芯片详细介绍:    3 TLC ...

  4. uTenux\AT91SAM3S4C开发套件&mdash;&mdash;&mdash;硬件电路介绍

    无论写什么嵌入式软件,我们都应该首先对硬件有所了解,这样更有助于我们写出高效精简的程序代码.本次活动我们使用的硬件平台是有悠龙公司提供的uTenux\AT91SAM3S4C开发套件,在悠龙公司的主页可 ...

  5. 数字逻辑与EDA设计

    目录 第一章 数字逻辑基础 1.1数制与码制★★★ 数制 码制 1.2基本及常用的逻辑运算★★ 1.2逻辑函数表示方法★★ 1.3逻辑函数的化简★★★ 1.4常用74HC系列门电路芯片★ 第二章 组合 ...

  6. 高速数字逻辑电平(8)之LVDS差分信号深度详解

    原文地址点击这里: LVDS(Low-Voltage Differential Signaling ,低电压差分信号)是美国国家半导体(National Semiconductor, NS,现TI)于 ...

  7. Python3机器学习—Tensorflow数字识别实践

    [本文出自天外归云的博客园] Windows下Anaconda+Tensorflow环境部署 1. 安装Anaconda. 2. 开始菜单 > 所有程序 > Anaconda 3 (64- ...

  8. gcc 头文件是用户应用程序和函数库之间的桥梁和纽带 功能的真正逻辑实现是以硬件层为基础

    gcc GCC, the GNU Compiler Collection - GNU Project - Free Software Foundation (FSF) http://gcc.gnu.o ...

  9. 04-时序逻辑电路设计之计数器——小梅哥FPGA设计思想与验证方法视频教程配套文档

    芯航线--普利斯队长精心奉献 实验目的:以计数器为例学会简单的时序逻辑电路设计 实验平台:芯航线FPGA核心板 实验原理: 时序逻辑电路是指电路任何时刻的稳态输出不仅取决于当前的输入,还与前一时刻输入 ...

  10. 全数字锁相环(DPLL)的原理简介以及verilog设计代码

    随着数字电路技术的发展,数字锁相环在调制解调.频率合成.FM 立体声解码.彩色副载波同步.图象处理等各个方面得到了广泛的应用.数字锁相环不仅吸收了数字电路可靠性高.体积小.价格低等优点,还解决了模拟锁 ...

随机推荐

  1. 描述Linux系统开机到登陆界面的启动过程(计时2分钟)

    简述: 1.开机BIOS自检 2.MBR引导 3.grub引导菜单 4.加载内核kernel 5.启动init进程 6.读取inittab文件,执行rc.sysinit,rc等脚本 7.启动minge ...

  2. 【翻译三】java-并发之线程对象和实现

    Thread Objects Each thread is associated with an instance of the class Thread. There are two basic s ...

  3. nodejs express 框架解密1-总体结构

    本文是基于express3.4.6的. 1.express 代码结构为: bin/express 是在命令行下的生成express 框架目录文件用的 lib/express 是框架的入口文件 lib/ ...

  4. intersection

    用来找到两个rdd的交集,注意,最终的new rdd的分区数量取决于两个rdd中的最大分区数量. 测试一下: val data1 = sc.parallelize(1 to 20,1) val dat ...

  5. 服务管理--systemctl命令

    摘要: systemctl 是系统服务管理器命令,它实际上将 service 和 chkconfig 这两个命令组合到一起. 任务 旧指令 新指令 使某服务自动启动 chkconfig --level ...

  6. 图解Go语言内存分配

    目录 基础概念 内存管理单元 内存管理组件 mcache mcentral mheap 内存分配流程 总结 参考资料 Go语言内置运行时(就是runtime),抛弃了传统的内存分配方式,改为自主管理. ...

  7. C++实现对文件中各单词词频的统计及其代码优化

    先给出github上的代码链接以及项目需求 1.项目概述 这个项目的需求可以概括为:对记事本(txt)文件进行单词的词频统计和排序,排序结果以指定格式输出到默认文件中,并要求能够快速地完成整个统计和结 ...

  8. mongodb 添加字段并设置默认值

    db.doc名称.update({}, {$set: {新字段名称: 默认值}}, false, true) 如:db.fly_bill.update({}, {$set: {usableStatus ...

  9. 20175316 盛茂淞 实验一 Java开发环境的熟悉

    20175316 盛茂淞 实验一 Java开发环境的熟悉 实验目的 使用JDK编译.运行简单的Java程序 实验要求 1.建立"自己学号exp1"的目录 2.在"自己学号 ...

  10. pandas获取当前时间

    datetime.now()用于获取当前的日期和时间 print pd.datetime.now() #encoding:utf8 import pandas as pd print("(p ...