内容纲要

可综合的Verilog---用于电路设计

Veriog的结构

总的来说,我们需要将芯片划分为不同的块(block)、子块(sub-block),乃至于更小的模块(module)。

模块为最低层次的电路单元,每个模块都具有一些输入和输出端口。一个模块通过输入端口不断接收到数值病通过输出端口输出其产生的信号。

将模块拼接在一起可以构成整个芯片,当模块的数量变大时,我们需要创建一定的层次接口,高层的模块有多个低层的模块构成。多个高层次模块连接又可以构成更高层次的模块,直至完成整个设计。采用层次化、模块化设计在工程设计中非常有益,可以使复杂设计易于开展。

硬件RTL代码的执行

与CPU代码执行的区别是,CPU执行在具体的某个核上不会出现同时执行两条指令的情况,CPU是顺序执行的。

在数字系统中,所有的逻辑门在上电那一块,都在同时进行着逻辑运算,每个时钟有效出发边缘出现时,所有的触发器都将进行输出数值的更新,因此硬件电路是真正意义上并行执行的。

Verilog中的触发器

综合后的电路由触发器和组合逻辑电路构成,其中触发器主要是指D触发器,其他类型的触发器在实际数字系统中应用并不广泛。

D触发器的工作模式非常简单,在每个时钟上升沿,触发器的输出端所春当前输入端的电平值。

如果有复位引脚(实际设计中通常使用异步复位),当复位引脚输入电平有效时,输出端被置为1或0.

触发器分为带复位引脚的触发器和不带复位引脚的触发器,这两种各有优缺点。

组合逻辑

always块语句

  • 使用(*)代表所有的敏感信号,这里的敏感信号为块语句内出现的所有信号。
  • 在组合逻辑中,使用阻塞赋值符号“=”,而不是“<=”。
  • always块内部的第一句话表述了信号的默认值,当敏感信号发生变化时,always块内的语句执行,并重新计算新的值。
  • 在always块内部开始时为变量分配默认值,可以保证综合后不会生成锁存器。
  • 默认值被分配后,使用if-else语句获得新的值。
  • 如果有一个以上的语句要在一定的条件下执行,那么需要把它们放在begin-end结构中。
  • 如果只有一条语句,不需要begin-end。
    reg reset_cnter;
    reg [1:0] cnter, cnter_next;
    always @(*) begin
        //put all default values here
        cnter_next = cnter;
        //describle the equation in an algorithmic manner
        if (reset_cnter)
            cnter_next = 'd0;
        else if (cnter == 2'b11)
            cnter_next = 'd0;
        else
            cnter_next = cnter + 1'b1;

    end
    initial begin
        reset_cnter = 1;
        #1 reset_cnter = 0;
        #10 cnter = 1;
        $monitor("monitor cnter_next %d", cnter_next, $time);
    end

case和if-else语句

有一种特殊用法,case语句可以选中其中为1的比特对应的条件分支。需要注意的是,data_sel中只能有1比特为1,不能有1比特以上同时为1。

    reg [7:0] data_out_nxt;
    reg [7:0] data_in, data_in0, data_in1, data_in2, data_in3;
    reg [3:0] data_sel;
    always @(*) begin
        data_out_nxt = data_in; //declare the default value in the beginning
        case (1'b1)
            data_sel[0]:data_out_nxt = data_in0;
            data_sel[1]:data_out_nxt = data_in1;
            data_sel[2]:data_out_nxt = data_in2;
            data_sel[3]:data_out_nxt = data_in3;
        endcase
        $display("display data_out_nxt %d", data_out_nxt, $time);
    end
    initial begin
        data_in = 0;
        data_in0 = 0;
        data_in1 = 1;
        data_in2 = 2;
        data_in3 = 3;
        #1 data_sel = 0;
        #1 data_sel = 2;
        #1 data_sel = 4;
        #1 data_sel = 8;
    end

赋值语句

可以使用assign语句为组合逻辑赋值。此时变量被声明为wire类型而不是reg类型。

Verilog操作符

Verilog操作符 描述 举例
&& 逻辑“与” if ((a>b) && (c<d))
& 按位“与” 按位操作
标量
wire a,b,c; assign c = a&b;
矢量
wire [1:0] a,b,c //两个矢量位宽需要一致
assign c = a&b;
//等价于
c[0] = a[0] & b[0];
c[1] = a[1] & b[1];
|| 逻辑“或” 类似于逻辑与
| 按位“或” 类似于按位或
逻辑“非” 类似于逻辑与
~ 按位“非” 类似于按位与
^ 按位“异或” 类似于按位与
~^ 按位“异或非” 同上 写法:assign c = a ~^ b;
& 缩位“与” 矢量的每一个bit都参与操作,结果为1bit位宽
例:检查是否所有bit均为1
wire [2:0] a;
wire b;
assign b = &a;
| 缩位“或” 类似缩位“与”
^ 缩位“异或” 同上
~& 缩位“与非” 同上
~| 缩位“或非” 同上
~^ 缩位“异或非” 同上
== 逻辑“相等” 同逻辑操作
!= 逻辑“不相等” 同上
>, < , >=, <=, +, -, *, / 常见操作符 与C语言类似
{} 并位 将多个变量或者常量并位,获得位宽更大的变量
<<, >> 左右移位 与C语言类似,右移高位补0,左移高位丢失
条件运算 与C语言类似
% 求模 同上
** 指数运算 z=x**y 其中x和y可以是实数或者整数

操作符的执行顺序

明确指出操作符执行顺序最好的办法是使用括号,括号内的操作优先执行。

Verilog中的注释

与C语言类似

可重用和模块化设计

增加同一个模块的可重复利用率,可以大大提高设计效率。同一个模块可以在不同的芯片,或者同一芯片不同模块中重复使用。

Verilog提供了许多可综合的语法结构,常用的包括parameter、function、generate和`ifdef等。

参数化设计

  • 参数在使用前需要被提前定义。
  • 参数可以在模块中被定义,也可以在`include指出的头文件中定义,如chiptop_parameters.vh。
  • 在模块例化时,可以将不同的parameter值传递到模块内部。这一点对于提高设计的可重用性非常有好处。
  • 例如我们可以用参数来表示一个FIFO(First In First Out,先入先出)存储器的位宽和深度。

参数文件的内容(chiptop_parameters.vh)

在Verilog中,参数文件扩展名是.vh,而不是.v。这个文件包含了该芯片的全局参数。在芯片规模非常大的情况下,有可能除芯片顶层参数文件外,在不同的层次上还会有多个不同的参数文件,所以需要确定参数名称是唯一的,因为一个模块可能在多个地方被调用。

Verilog函数

函数可用来生成可综合的组合逻辑。

函数具有一个函数名、一个或多个输入信号,一个输出信号,输出信号位宽可以具有多个bit。

函数内部所描述的逻辑功能是可综合的,综合后得到的是组合逻辑。因此内部不能出现不可综合逻辑,如#或者@,wait等。

Verilog中的generate结构

generate结构式是Verilog 2001中新增加的语法结构,它是一个可综合的语法结构。

综合工具会在对代码进行编译时,将电路展开从而生成完整的设计代码,然后对其进行电路综合。

Verilog中的`ifdef

和C语言的#ifdefine类似。

`ifdef的用法与mux语句的用法不同,在mux预计汇总,不同的分支都会出现在综合后的网表中,根据select信号(选择控制信号)的值决定执行其中的一条路径。而在ifdef中,只有一个分支对应的代码会再网表中实际出现。

数组、多维数组

二维数组一个简单的应用是对存储器建模。

最后修改日期: 2024年 1月 19日

作者