数字IC入门之一(Verilog)

  Verilog入门书籍大家都是推荐《Verilog HDL数字设计与综合》(书1),但这本书比较薄,里面的内容读起来比较生涩,好比骨头没有肉,所以还是推荐《Verilog HDL数字设计与建模》(书2)这本书里面有大量的例子,数字IC设计通用的一些结构比如数据选择器,编码译码器,还配有相应的电路,这些通用结构练习熟练了,以后进行大型设计都会无意识的用到,然后在看看书1,适当的查缺补漏。

  Verilog硬件描述语言,我觉得描述两个字很本质的说明了这个语言的用处,就是像中文或者英文一样把你内心想出来的电路结构描述出来,所以当你能够用verilog把内心想到的电路快速的以一种简洁的可以综合的方式描述出来以后,verilog语言本身对你来说就没有太大的提升空间了,再厉害的人写出来的代码也就是那样,唯一的区别是他内心想出来的电路是厉害的,如果你能想出他心中想出来的电路,那么代码也是差不多的。所以,就这个意义来说,电路设计最重要的并非语言本身,而是你内心想出来的电路,你熟知的某个领域的算法。

  很多人被verilog中case和if语句如何不生成锁存器而苦恼,即使spyglass报出了你哪个case哪个信号推断出了锁存器,也无从下手改正,这里面我想说的是,case和if里面通常会有很多信号,有些教材给出了一些基本指导原则, 但是死记硬背那些指导原则显然不是好方法,重要的是要去明白什么是锁存器,我这么写为什么就推断出了锁存器,如果你充分理解了锁存器是什么东西,那么你就不需要那些准则,自己就可以感觉出哪里生成了锁存器,而锁存器的介绍很多基本的数字电子技术已经可以讲的很透彻了。在看过了很多前辈写的代码之后,我逐渐养成了下面的写法,管你什么锁存器,统统不要来烦我:

举的例子是状态机的例子,如果你的控制流程很多,输出信号很多,你会发现,如果你每个分支写全输出信号的话,最后你会被大量的Sigx=1'b0;这种代码霸屏,以上这种写法就避免了这种冗余,而且你永远不用担心锁存器被推断,每次仿真器执行这段代码的时候都会从always的开始执行,也就是开始就对所有的信号指定了一个明确的默认值,(比如上面的0)那么当你走case和if的各种分支的末端执行到Sig1=1'b1;的时候,它就由0变成了1,其他的信号虽然没有被明确赋值,但是由于在初始化已经被赋予了默认值,所以你无须在每个分支进行赋值,从而也就避免了锁存器的生成

  `include `define `defif `elsif `endif 这些在夏宇闻第三版Verilog HDL都可以找到,也都是一看一实验就能明白的东西,所以没什么难点,而for循环以及generate for还有function和task这些结构的熟练运用,可以让你的代码简洁、易读、易于维护和调试,举个例子,当你浏览到下面这些代码:

如果说位宽为32位呢?那么你不停的翻页,电路的功能真的很难在脑子里展现,如果用下面的代码代替呢:

无论原来的assign有多少行,都可以由上述generate for搞定不是吗,这种写法可以把代码行数缩短,让你一页就可以看到整个程序

但是我们看看下面的写法:

就是一行代码就可以替代上述两种描述方式,你可以把你的function体放到代码的大后方,一旦你验证了function的正确性,那么这一行代码就可以清楚的展现你想实现的功能,看着代码就可以浮现出电路结构,找错定位,或者其他设计验证人员查看的时候会相当舒服,这种风格一定要开始就养成习惯,越早越好。

  可综合的verilog仅仅是verilog全部语法的一个子集这个是大家都知道的,我想verilog可综合部分以及验证代码部分最关键最难理解的应该是两种赋值操作了,阻塞赋值以及非阻塞赋值,这两种赋值的重要性几乎每种书都有介绍,在通常的RTL风格中我们描述组合逻辑通常都是用阻塞赋值,而在时序逻辑中通常都是用非阻塞赋值,这背后的原理是verilog语言本身的事件队列问题,我曾经一直被这个问题困惑,尤其是非阻塞赋值采样右值这件事情无法理解,直到我看到了书2中附录讲解的verilog事件队列,一定要把这个仔细看好,当你看完这个之后还可能对其中一些事情有疑惑,这里再介绍一个资料,就是《Verilog数字系统设计》(第三版夏宇闻),里面有仔细讨论两种阻塞赋值的执行顺序,你可以按照看到书2附录中事件队列结合着分析,之后一定不要忘记自己设置一些特殊情况,用NC_Verilog来验证自己的想法。

  至此你已经可以理解为什么组合逻辑和时序逻辑要分别用阻塞以及非阻塞赋值了,那么各种书籍中常常有强调测试平台不要在待测设计的时钟有效沿处用阻塞赋值给定激励呢?下面我来谈谈自己的理解,当然假设这个时候你已经看完了上述推荐书籍,也可以自己验证看看我说的是否正确:

                                                                                       

以下的分析都是建立在假设您已经看完了上述推荐书籍后的情况下:

假设DUT有上左图所示的代码,右侧为TB中的代码,我希望能够在Delay时刻给tb_abcd四个信号赋值为1,心想看看A是否是1,不妨假设tb_abcd在此次赋值之前都为0,那么如果你的Delay时刻正巧同clk上升沿为同一时刻,让我们来看看仿真器是如何执行的,首先测试平台中的clk信号上升沿到来(假设仿真器首先处理了clk的赋值),当tb_abcd四个信号被赋值为1之后,由于top层将DUT和TB的tb_abcd分别与abcd相连,设计中的abcd也相继被赋值为1,那么左侧设计中的三个always块同时被激活,为什么说三个always都被激活,看block_A的posedge clk使其激活,而block_B中的a和b变化所以也处于激活状态,block_C同理block_B,那么仿真器先执行哪个always块呢?答案是随机的,好让我们假设一种坏的情况,例如首先执行block_B这个时候由于a和b为1,所以B从原来的0变为1(如果你按照我的建议读了上面教材,你就会很清楚阻塞赋值会采样到等号右侧值之后立即赋值给左边变量,所以B立即为1而不会等待),接下来执行block_C还是执行A呢?让我们假设其执行block_A,这个时候仿真器采样B & C,得出结果为0(B为1,而C为0),但是由于这条语句是非阻塞赋值,仿真器仅仅是记住了B & C的值,不会立即变更A的值,也就是说A到这里还是0,接下来没的选择只能执行block_C,立即将C变更为1,这里是关键,即使C变为了1,仿真器也不会重新采样block_A中的B & C的值了,而是在当前时间片的最后,将第一次执行block_A的采样到的B & C赋值给A,真正的实现了A的变更,最终得到A为0,也就是接下来的持续一个周期的时间A都会是0,所以当你以为赋值abcd全为1后A会在接下来的时钟周期中为1,那你就会不知道到底哪里错了,甚至质疑,是不是工具出bug了?当然了,你恰巧得到了正确的结果,但是如果你按照这种风格继续工作下去,终有一天你会怀疑工具坏了。这就是阻塞赋值与非阻塞赋值的区别,当然从实际电路的延迟模型来看,信号也不可能在时钟上升沿那一刻赋值的,也就是说,这种情况从理论的语言角度看,还是从实际电路来看。都是不可能的,所以,如果你用verilog来写测试平台,这个情况一定要避免。

如果涉及了SystemVerilog这一语言,不妨去找夏宇闻老师的一篇PPT,上面摘录了sv语言参考手册中的段落,或者直接查看sv参考手册,你会发现sv中的事件队列有了很大改进,如果你用sv在program块中进行测试代码的编写,尽管去用阻塞赋值在时钟上升沿赋值吧,sv事件队列已经帮你处理好了一切,需要分清楚的是,用接口中的是终端款与否会直接影响你对于寄存器的采样周期,这方面内容说起来篇幅会很长,有了verilog事件队列基础,看sv事件队列应该不成问题。

上面说了,verilog熟练到一定程度,就可以了,但是有很多追求极简的人,来尽可能的使自己的代码变得漂亮,虽然两种版本的综合效果几乎没有差别,至于我觉得如果不是一些典型的常用电路,是没必要如此较真的,比如说格雷码转换为二进制,我可以查找资料得到如下的基本表达式:

G3 = B3;

B2 = G3 ^ G2;

B1 = G3 ^ G2 ^ G1;

B0 = G3 ^ G2 ^ G1 ^ G0;

也许你会想到generate for,或者简单的for,那么让我们用sv中的for来实现它(细节忽略)

always @* begin

  for(int i = 0; i < 4; i ++)

    B[i] = ^ G[3:i];

end

当循环到i=3的时候你会发现,出现G[3] = ^ B[3:3]这将导致不可综合,你可能会改变想法,写出下面的代码:

always @* begin

  for(int i = 0; i < 3; i ++)

    B[i] = ^ G[2:i];

end

assign B[3] = G[3];

如果你是那种追求极致的人,那么你可能会花费一些时间来想出如下的基本事实:

B3 = 0 ^ 0 ^ 0 ^ G3;

B2 = 0 ^ 0 ^ G2 ^ G3;

...

于是写出了如下的简洁代码:

always @* 

  for(int i = 0; i <= 4; i ++)

    B[i] = ^ (G >> i);

这种写法同上述带着assign(看上去不怎么简洁)的代码综合效果是没有任何差别的

但是这种类似纯代码的优化背后隐藏的道理是布尔逻辑,说这么多的意义就是要认清一个事实,要专注代码背后的电路,而不是代码本身。

后续会继续从电路的角度去说说有哪些参考书,要达到什么样的程度,再次说明;以上着重强调参考教材,本人分析部分由于仓促或者水平有限可能存在重大错误,仅仅用于讨论,和指出推荐教材中比较重要和难以理解的地方,好了Verilog HDL在RTL中的应用重点和难点差不多总结完了,开始看下面的部分吧

猜你喜欢

转载自www.cnblogs.com/HIT1719292537/p/9188989.html