欢迎光临专业集成电路测试网~~欢迎加入IC测试QQ群:111938408

专业IC测试网

当前位置: 网站主页 > 相关技术 > 芯片制造 >

【芯片设计】从RTL到GDS(二):Verilog

时间:2024-07-29 18:57来源: lawliet 芯时代青年 作者:ictest8_edit 点击:

 

本篇文章将和大家讨论Verilog相关的东西。本篇文章不会手把手教你写Verilog,而是谈一些Verilog的注意事项,怎么去写好Verilog。看这篇文章之前你应该写过一定的Verilog,同时要有一定的数字电路基础,否则可能会一脸懵。文章内容很多都是我自己写了几年Verilog的经验之谈,希望对你有帮助。


1、Introduction

首先,Verilog并不是一门编程语言。所谓的HDL,即Hardware Description Languange。其用于描述硬件,请记住这个描述二字。对于编程语言,你可以发挥你的想象力,天马行空都可以,但是HDL不可以。你所做的实际上都是在描述数字电路。所以写的越简单越好,不要写花里胡哨的语句

并且,写代码的时候,时刻思考你的这段代码会生成怎么样的电路,这里的电路指的是数字电路,各种门,寄存器等。我们后面结合实例加深理解。

 

我们再来看一下Verilog的抽象层次,有三种层次的抽象,第一种是门级,第二种是寄存器传输级,第三种是行为级。我们将重点放在寄存器传输级RTL上。

 

我们再来看一下什么是DUT,DUT就是你的设计本身,它的名字也说明了,它是等待被测试的Design。我们直接看下面这个图,这个DUT按理来说是你设计的数字电路本身。比如最简单的,加入它是一个加法器,有两个输入,一个输出。这就是一个DUT。

为什么还需要Testbench呢?因为我们不知道加法器设计的对不对,我们可以写个外部激励,给两个输入分别输入1,然后看输出如果是2,我们可能认为,哦,好像设计的是对的。当然这样的验证非常不完备,这里是简单举个例子。

 

对于单元测试(Unit Test)理论上是设计人员自己写,自己验。我们可以将输入组织成一个向量,遍历这个向量,然后输出的结果和Golden Model比(按理说这里的Golden Mode是一个文本文件,文件的内容由高级语言生成,类似于一个查找表,在Verilog中可以组织成RAM的形式,当然这里的Golden Model也可以是一个行为级别的Verilog),看是不是符合预期。这种方式希望大家能够好好掌握。可以有效地减轻以后系统级验证工作。(自己对自己设计的模块有个预期,不要全部让系统级验证去验,系统验证很难完美覆盖单元测试

 


2、Verilog Syntax


我们来看一下最最基本的Verilog语法。其实Verilog语法相比编程语言,真的太简单了。我觉得只要会例化,写assign就够了。(尽可能不要写always,后面详细讲)

学过数字电路的朋友都知道,有三种最最基本的门电路,与或非。这三种是什么含义就不用我多说了,对于任意的数字电路。我们都可以将其抽象成一个模块

这里的模块比Verilog的Module更广泛,所谓的模块就是有输入有输出的一个数字电路。比如一个或门,它是不是模块?是的。它有两个输入,一个输出,这种原语调用很简单,or(out,in1,in2)即可。大家仔细看这段代码和它上面的电路图,啥意思肯定能理解吧?

Verilog中的信号有四个值,分别是0,1,X,Z。实际上的电路是没有X态的,我们都知道要么是0要么是1。仿真的时候我们用X态表示不知道的状态,比如不带复位的寄存器刚上电的状态,违反建立时间的输出的状态。都可以是X态。

然后Verilog中有Wire的概念,就是所谓的线,比如上面这个或门,这个in1,in2,out都是wire。这个相信大家都能理解。然后就是寄存器,寄存器可以保持状态,其为什么可以保持状态涉及到数字电路的知识,这里我不想深入细节,大家记住寄存器可以保存状态即可。

 

到此为止我们已经建立了最基本的概念。一些简单的Design,你应该也能看懂了。还看不懂?没关系,我们接着往下看。

假设你不想用上面的方式调用或门,有没有别的方法?当然有,并且很简单。直接assign out = in1 | in2即可。assign用来干什么我们过会再说。先搞清楚out = in1 | in2 是什么意思。这个大家应该能看懂吧,就是in1这根线和in2这个值或一下,得到了out这个值。

我们再看一下常数怎么表示,用W'Bval,比如1'b0表示位宽为1的一个固定为0的值。这个对应到电路,大家可以理解为一根线,接地了,所以固定是0。可以用这个线给别人用,给别人赋值等等。

 

上述的例子相信大家都能理解,如果不能理解说明你大概率没学过数字电路,请先有一定的数字电路基础再看这篇文章。

我们在看一下Verilog进程块。我们不用去纠结为什么叫做进程块,只要知道我们但凡赋值,都需要有进程块这个东西就行。对于Inital Block,我们可以看到写法如下所示。其描述了一个顺序的行为,当然我们知道,数字电路所有的模块都是同时在运算的,没有所谓的先后顺序。因此inital语句,只能用作Testbench。Testbench不对应具体的电路。

然后就是Always Block,很多人学Verilog迷惑的地方,就是Always怎么用,always是不是对应寄存器。这里我们只要记住,采用时钟边沿驱动的always,对应寄存器,并且它对应的可能也不只是一个寄存器,如果里面有运算可能还对应组合逻辑。

所以大家以后自己做设计的时候,准备好几个最基本的always库,以后要使用寄存器的时候,直接例化它,不要自己去写always语句了。就像下面这样。这样做有什么好处呢?这样做的好处在于你能非常清楚的知道,你的代码对应了什么样的电路。

比如很多人还喜欢在always里面写d<=a+b之类的话,其不光对应了寄存器,还对应了加法器。如果你写成assign d =a+b,然后将d作为dffr 的输入端,是不是看上去就更加清晰了?assign也是一个进程块

这样做还有个什么好处呢?就是不要写reg了,尽可能不要写reg,Verilog中的reg和wire和实际对应寄存器的输入还是输出,还是普通的连线,没有一分钱关系。不要去纠结写reg还是wire,用了上述的方法,一切的一切都可以写wire,并且什么时候调用寄存器,调用了几个寄存器,你自己一清二楚。

dffr #(.WIDTH(1))
inst_dff (
  .clk    (clk),
  .rst_n  (rst_n),
  .d      (d),
  .q      (q)
);

 
只有一种情况我建议用always,那就是有很多if else语句,你用assign已经无法表达清楚的时候,这种情况下你可以用always@(*),其它情况一律用assign即可。

 

采用了上述的方法,大家也不用纠结什么阻塞赋值非阻塞赋值了。你自己需要写的逻辑块,其实都是组合逻辑,这种情况全部用=即可。

 

上面我们讲了,如何去调用寄存器,这涉及了例化的内容。例化就是实例化出一个模块,比如我们有一个多路选择器,我们可以采用下图右边的方式例化它。

 

课件还介绍了一下System Task,这个对于设计人员来说不是重点,大家有需要的时候再去查就行。

 


3、Simple Examples


我们来看一些简单的例子,首先是打印Hello World,毫无疑问这段代码是不可以综合成电路的,这里简单看一下就行。

 

下面这个例子展示了使用三种方式实现Mux,我建议用第一种,即使用assign。这三段代码生成的电路是一模一样的,但是我们可以看到,out有的被声明成了reg,有的声明成了wire。但实际上其对应的电路,就是一个最最简单的Mux,一个组合电路而已。所以声明成什么,跟是否使用寄存器,没有任何关系,这完全是Verilog设计的遗留问题。

 

我们再来看一下,时序电路。下面的代码我建议大家准备好几个模板即可,真的需要使用的时候,去调用它,不要再重新写always了。

比如你想调用一个没有复位的寄存器,最最简单的D-FlipFLop,怎么做?像下面这样,直接去例化。不要自己重新写always块,我再强调一下,不要自己去写always时序逻辑。

dff #(.WIDTH(1))
inst_dff (
  .clk    (clk),
  .d      (d),
  .q      (q)
);
 
 

Verilog的算术运算如下所示,大家简单看下课件就行。需要注意一下,如果不声明的话,Verilog仿真器会将所有的值看作无符号数。

 

课件也提了一下reg和wire之分。这里我强调一下,尽可能写assign,不要写always。这样的话你基本都使用的是wire。只有逻辑很复杂,你用assign没法写清楚的时候,可以写always@* ,这种情况下里面的变量需要用reg声明,但其对应的仍然是组合逻辑。

除了原始的几个寄存器模板,不要自己再去写always@(posedge clk) 之类的语句了。需要使用的时候请去例化它。

 

然后课件说了一下Testbench怎么写,大家看看就行。建议网上找点例子,课件提的太简单了。

 


4、FSM Implementation


 

接下来我们看一下状态机,状态机非常的通用。理论上软件可以实现的功能,状态机都可以实现。状态机可以从电路层面去解释,也可以从数学的层面解释。

我先从数学的层面解释,一个有限状态机可以定义为一个五元组 (S, I, O, T, Φ),其中:

S:状态的有限集合。状态是系统可能处于的条件或模式的离散集合。例如,一个交通信号灯的状态集合可能包括“红”、“黄”和“绿”。

I:输入的有限集合。输入是导致系统状态改变的外部信号或事件的集合。在交通信号灯的例子中,输入可能包括“行人请求过街”和“定时器超时”。

O:输出的有限集合。输出是系统对外部环境产生影响的结果或动作的集合。例如,交通信号灯的输出可能是灯光颜色的变化。

T:转移函数。T 是一个函数,它根据当前状态和输入决定下一个状态。通常表示为 T: S × I → S。在数学上,这可以看作是一个状态转移表或状态转移图。

Φ:输出函数。Φ 是一个函数,它根据当前状态和输入决定输出。通常表示为 Φ: S × I → O。输出函数可以是一个查找表,或者更复杂的逻辑表达式。

状态机的工作原理是,系统根据当前的内部状态和接收到的输入信号,按照转移函数 T 转移到下一个状态,并根据输出函数 Φ 产生相应的输出。这个过程在每个时钟周期或事件触发时重复进行。

恰好的是,数字电路可以完美的去实现这个数学模型。如下图所示。通过当前的状态和输入,我们可以去决定这个周期的输出。根据当前的状态和当前的输入,我们可以决定下一个周期的状态。其对应的电路请大家一定要好好掌握

 

我们来看个实际的例子,一个计数器。计数器是最常见最常见的状态机。我记得之前我刚学Verilog的时候,一个师兄和我说计数器可以实现绝大多数数字电路。我现在一回想,计数器不就是状态机吗?状态机当然可以实现各种各样的数字电路!

这里的例子如下图所示,其输入输出含义如下,我们接着往下看。

 

其对应的状态转移如下图所示,这个相信大家都能看懂吧?和数字电路没有关系,状态转移图就是个数学模型而已。

 

我们看一下代码怎么写,时刻牢记,代码就对应你的电路,回顾一下状态机的电路图长什么样。我们首先看一下如果计算下一个时钟周期的状态。首先这肯定是个组合逻辑,并且这个组合逻辑很复杂,这种情况下,大家是可以使用always块的。

 

然后我们写状态转移时序和输出逻辑,代码如下。这种情况下我不建议大家像下面这样写。我重新组织一下,希望大家反复看这段代码,明白其设计思路。
//状态转移逻辑实际上就是个寄存器,请你这么写(这段代码除了位宽,其它都是固定的!)

dffr #(.WIDTH(4))
inst_state (
  .clk    (clk),
  .rst_n  (rst_n),
  .d      (next_state),
  .q      (state)
);
//输出逻辑请你这么写

assign ovflw = (state==OVFLW)?1'b1:1'b0;//正常的组合逻辑

//这里的count依赖于上一个周期的值,count本质上也是状态机
assign count_d = (state==CNTUP)?(count_q+1'b1):
                 (state==CNTDOWN)?(count_q+1'b1):count_q; 
dffr #(.WIDTH(4))
inst_state (
  .clk    (clk),
  .rst_n  (rst_n),
  .d      (count_d ),
  .q      (count_q)
);   
 
 

课件然后讲了一下Testbench怎么写,大家简单看看就行。

 


5、Coding Style for RTL


我们看一下Coding Style,作为设计语言的话,Verilog用的越简单越好。当你发现一个人用Verilog做Design,都写的非常花里胡哨,那他大概率是个半桶水。

 

一个.v尽可能只放一个模块,注意换行。

 

写组合逻辑的时候,有一点需要注意,考虑所有的情况!尤其是if else或者case语句。如果你不考虑,仿真器会认为你想保持默认值,会综合出Latch。绝对不要使用预期以外的Latch,OK?

 

然后注意一些参数化,规范等。这里就没特别多好说的,大家先掌握怎么写,再慢慢考虑这些吧。

 

以下是参考文献:

 

说实话,这节课的课件,我觉得写的不太好。很多也是泛泛而谈,属于会的人不用看,不会的人看不懂。因此我补充了很多内容。

但下节课的逻辑综合及后面的内容,讲的非常精彩,我看了以后收获很大。我也会尽可能将后面的内容写好。

 
 
顶一下
(0)
0%
踩一下
(0)
0%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
用户名: 验证码: 点击我更换图片