按照一般程序语法书的习惯,讲完if,必定要讲for了,但是Verilog并非程序语言,需要将语法与电路一一对应。if和case对应的是选择器,那么for对应的是什么元器件?答案是什么都不对应,for对应的是逻辑的复制。正是由于它没有对应到真正的元器件,所以新手经常会用错,导致逻辑不能综合或实现面积过大,综合出了意想不到的元器件。有些项目禁止使用for循环也出于此理由,项目中提倡将for循环用Perl脚本展开,尽量在Verilog中表现为简单的没有for循环的语法,这样有两点好处,其一是防止新人用错,其二是出现问题时可以直接定位到元器件。
对于上面提的第二点,需要再进一步解释。使用for循环复制的元器件,在其中某个出现错误时,不容易定位到。例如,复制了3个元器件,名称分别为a[0]、a[1]、a[2],在for循环中都叫a[ii],其中ii是变量,代表0~2。假如元器件a[2]上发现了问题,想定位,在Verilog中无法找到专门针对a[2]的逻辑,只能找到a[ii],对于Debug来讲较为不方便。
但是笔者平时设计时,还是会使用for,因为对于循环多次的代码,将for展开会使代码变得很长,不容易阅读,中间出现笔误很难发现,用脚本语言将其展开,虽然不会有笔误,但在项目推进过程中,此处逻辑可能会经常改动,增加了发生错误的风险,另外也不容易体现出设计者的思想和电路规律,所以循环多次的情况,笔者并不排斥用for,下面介绍它的用法。
在Verilog中,for有两种用法。一种是一次复制多个逻辑的for循环,本书中称类型一,另一种是用在一个always块中的,只复制该块中的逻辑,超出块的范围就不复制,本书中称为类型二。
类型一使用generate块,如下例所示。整个要复制的逻辑都包含在由generate和endgenerate限定的范围内,其中ii是变量,不是信号,是用来表示复制的次数的,因而用genvar这个特殊的类型进行声明。复制的可以是assign和always等语句的集合,只要内部有ii,其代表的电路逻辑都会被复制。此例中每个逻辑都被复制12份,并且每个复制电路都有自己的名称,例如aa[0]~aa[11]和dd[0]~dd[11]等。pp_core是整个generate块的名称,读者可为generate块取各种名称,但不能没有名称。一个代码中可以声明多个generate块,可根据逻辑上的相关性将其划分为多个generate块,并分别取名,但不建议一个generate块过于庞大。
注意 代码中的ii=ii+1,不能使用C语言的常见表示ii++,此语法在综合中不支持。
类型二如下例所示,在一个always块中,不需要generate,ii也不需要声明成genvar,而是用普通的integer(32位整数)。此例的逻辑是:store为一个寄存器组,共12个,其初值都是0。当用户发起写操作(wr),并且地址(addr)与store编号相等时,数据(dat_in)就进入store组中对应的寄存器中。读者注意,没有任何一个寄存器叫store,综合后的寄存器是12个,分别为store[0]~store[11]。若一个代码中,有多处类似本例的for循环,则其循环变量都可以统一使用ii,综合工具会辨认出来,不必为每个for循环都声明一个专门变量。
注意 ii是变量,不是信号,即它没有对应任何电路或金属连线。如果ii声明为wire或reg,然后要表示一个信号a[ii],则语法将不能综合为电路。此处Verilog与C语言有所不同。