verilog 新手导航

来源:互联网 发布:增值税发票数据导出 编辑:程序博客网 时间:2024/09/21 08:17

写在前面

verilog不同于其他编程语言,它是一种硬件描述语言。我在verilog的学习中、烧板中走过了许多弯路。

试过写个简单的ALU花了15h+的时间,几天几夜debug无数次。也试过写学号循环,仿真通过了,烧板时灯完全不亮。期间各种google和stackoverflow,最终才渐渐地对verilog有所感悟。

我略总结了几条准则,在写verilog时应该反复在心里确认。

把verilog看成硬件描述语言

简介

verilog这个语言的作用是描述硬件。因此在写代码时,应该反复确认代码能否与硬件构成一一对应的关系。

有些代码能够通过综合(synthesis)和仿真(simulation),但在烧板时却无法达到预期的效果。烧板无法达到预期的代码,称为不可合成的代码(non-systhesizable),这些代码没有真正地”描述“硬件如何工作,无法根据代码产生对应的硬件。

烧板能正确工作的代码,称为可合成代码(synthesizable)。下面我们会讨论如何写出正确的可合成的代码。

写出可合成代码的诀窍是,时刻把verilog看成描述硬件的语言
下面简单举几个例子

延时(delay)不可合成

下列代码

    reg [20:0]cnt = 0;    always begin        #1000000;        cnt = cnt + 1;    end

在时间单位是1ns的情况下,他企图每1ms增加cnt的值。然而在烧板时它无法正确工作。

试想一下,作者希望延迟 106ns,可是这该如何产生对应的硬件?我们该如何能强迫电子在电线中凭空地停留,一段时间后再继续往前走?

类似的代码还有OUT1 #1 = IN1 & IN2;假设一个与门的延迟是5ns,你又如何强迫上述代码必须在1ns内完成任务?

所以,在产生实际电路时,所有的延迟将被忽略。延迟不可合成。

延迟存在的意义是,仿真时模拟竞争与冒险。

拒绝for循环

在verilog中,使用for循环被视为bad practise
试想以下,下述代码

    reg [3:0] i;    for (i = 4'b0000; i <= 4'b1111; i = i + 1) begin        //statement    end

为了让它运行,该产生多么复杂的硬件呢。

首先我们需要4位寄存器存储i的初始值,我们需要比较电路来完成i <= 4'b1111, 我们还需要加法电路实现i = i + 1

原本简单的电路会因此变得十分复杂,难以调错。

使用for很不”硬件描述“

笔者并不反对在TestBench 文件中使用for循环。但谨记,不要在源文件中使用for。虽然for可合成,但为了合成它需要很大的代价。

分清时序电路与组合电路

这是写verilog代码的第二个准则。新手十分容易在此处犯错。

简述

verilog有它基本的语法,例如

    always@(A or B or C)

上述代码always后接敏感表,会对应产生组合电路
又如

    always@(posedge CLK)

上述代码always后接上升沿敏感,会产生时序电路

注意,两行代码差异十分微小,但会由此产生截然不同的电路。对电路的分析、使用也截然不同。

组合电路

观察下述代码

    reg [3:0] cnt;    always@(cnt) begin        cnt = cnt + 1;    end

上述代码没有任何”语法错误“,但却是个绝对错误的代码。

always里接电平敏感信号,表明这是个组合电路。然而组合电路没有记忆功能,即时的输入决定即时的输出。这种情况下,如何”记忆“cnt的现态,把它加一求出次态呢。

上述代码在仿真时会出现不定态(此处不确定)

值得一提的是,组合电路不会含有寄存器,故reg [3:0] cnt;不会产生寄存器。此处用reg声明的原因是,always语句里必须使用reg型变量。若声明为wire会报错

此处是因语法需要而将cnt声明为reg。本质上组合电路中,cntwire型变量的行为是一致的。

时序电路

时序电路会为reg型变量产生寄存器。
观察下述代码

    always@(posedge CLK or negedge CLK) begin        //statement    end

上述代码表面上没有错误,但也是错误的代码。

“在CLK的上升沿或下降沿触发”

“在CLK发生改变时触发”
两者是完全一致的。故这个电路实质上是组合电路。

上述代码的合成将会报错(待确认)

组合电路(2)

组合电路还有一种写法

    assign OUT1 = IN1 & IN2;

assgin的含义是连线。相当于在实验板上,用一根电线把某些接口连接在一起。

显然即时的输入决定即时的输出,构成一种组合电路。

应注意,assign语句构成的组合电路是”每时每刻”都发生变化的。故构成并行语句。
不难理解assignalways语句应该处于同等地位(平行关系)。故不能将assign语句放置在always中。

推荐写法

有时verilog的警告会出现 infer latch(构成锁存器)字样。下面讨论一些good practise 以及与锁存器相关的内容。

if语句必须带有else

以下讨论组合电路。

假设if不带else,则表明在某些情况下(不进入所有的if体),某些变量的值不会被修改。

换句话说,某些情况下,某些变量的值会保留它上一时刻的值。

这在组合电路中是无法发生的。vivado会为这种情况产生寄存器,或把输出反接到输入端。构成锁。(待确认)

故所有的if语句都应该带有else

if语句的硬件实现是优先译码器(prior decoder)

case语句一定带有default

原因同上。
case语句的硬件实现是多路分配器(multipexer)

组合电路禁止出现反馈

这点具体可见组合电路的定义。

组合电路的定义本身禁止门的输出直接相连,禁止将输出反接到输入,禁止成环等。

技巧与注意

  1. 声明变量不要忘记线宽。笔者曾经试过reg a忘记加线宽而出现难以调试出的bug
  2. 位拼接运算符很好用。{ A, B, C, ..., X}会将各个变量的位按次序拼接在一起。用位拼接运算符可以简化代码。
  3. 禁止部分初始化reg。考虑这个代码
reg [3:0]ID;initial begin    ID[3:0] = 4'b0000;end

烧板时会造成错误,但

reg [3:0] ID;initial begin    ID = 4'b0000;end

则是正确的。原因不明(请求信息)。
4. delay(#)real不可合成。real指实数,是一种数据类型。建议大家google verilog synthesizable.学习如何写出可合成代码。
5. initialFPGA上可合成。单独提出这点是因为,并非所有的实验板都支持initial语句。我们(中山大学16届计科)用的板是FPGA,可以根据initial语句产生正确的电路。但不保证其余情况下initial语句能正确工作。

写在最后

always语句中的reg解疑

明明always@(A or B)会被看作组合电路,为何always语句中还要使用带有歧义的reg类型变量呢?

verilog语言的创始人犯了一个很愚蠢的错误。他想当然地认为always语句中的变量都应该是reg,于是在设计语言时将其作为一个语法。

事实上always语句有可能被合成为组合电路,故早期的verilog就在这个矛盾中发展起来了。

reg类型的讨伐越来越多,修改的呼声越来越高。新版的system verilog里不再使用reg类型,而是使用取而代之的logic类型。(待确认)相信这种情况下,初学者会对verilog更易掌握。

可合成代码解疑

许多初学者对verilog有过声讨。为何它的代码要分可合成和不可合成?

原因在于,使用verilog语言的人不完全都是为了烧板。verilog作为一个硬件描述语言,有它自己的存在意义,烧板只是它的功能的一个子集。

许多程序员会希望verilog语言拥有更强大的功能,帮助设计硬件,哪怕这个语法暂时还无法合成。

笔者相信,无法区分可合成与不可合成,是写verilog致错的重要原因。

例如下述代码

    always@ (posedge CLK) begin        $monitor("%h, %0t", cnt, $time);        cnt = cnt + 1;    end

作用是,在每次cnt变量发生修改时,都会在log上打印cnt变量的值以及当前的时间。

这段代码是很好的调错代码。显然,$monitor无法被合成。你不可能希望在烧板后,凭空产生一个屏幕为你显示调错信息。

类似的调错代码还有$display()等。

但不得不强调:通过仿真不代表烧板后行为正确。你必须确保你的代码全都是可合成的代码。

全文完。
via YB

原创粉丝点击