Verilog编写的注意事项

时间 版本 内容
2024/03/19 V0 初版

根据写Verilog的经验记录一些注意事项,以减少产生错误的可能性。


1. 对于任何非1 bit宽的信号都要确保进行了定义

Verilog是容许对信号不声明的,这种隐含声明会认为信号是1 bit wire信号。“wire” 信号这点基本所有人都知道,但是“1 bit” 是容易忽略的。

1.1. 例子

    wire [N:1]      fifo_empty;

    Chn_read_fifo_multi #(
        .N      (N),
        .OFFSET (OFFSET)
    )   u_Chn_All9_Readout (
        .SiTCP_CLK      (clk_125M),
        .dout           (dout),
        .rdclk          (clk_syn),
        .rst_n          (GLB_RST_n),
        .len_data       (len_data),
        .rd_en          (rd_en),

        .fifo_dout_pack (fifo_dout_pack),
        .tsm_done       (tsm_done),
        .wr_done        (wr_done),
        .fifo_full      (fifo_full),
        .fifo_empty     (fifo_empty)
    );

    fifo_combine u_fifo_combine (
        .LVDS_CLK0_P        (LVDS_CLK0_P),
        .LVDS_CLK0_N        (LVDS_CLK0_N),
        .SiTCP_CLK          (clk_125M),
        .rst_n              (rst_n),
        .fifo_dout_pack     (fifo_dout_pack),
        .fifo_empty         (fifo_empty),
        .tsm_done           (tsm_done),
        .wr_done            (wr_done),
        .SFP_RX_P           (SFP_RX_P),
        .SFP_RX_N           (SFP_RX_N),
        .chn_en             (chn_en),

        .rd_en              (rd_en),
        .SFP_TX_P           (SFP_TX_P),
        .SFP_TX_N           (SFP_TX_N)
    );

注意fifo_empty信号,它是一个实例的输入又是另一个实例的输出,因此不用从外部驱动,即使不定义检查器也不会报错。

但是假如没有wire [N:1] fifo_empty的定义,fifo_empty就会被误解为1 bit信号,仿真时候会发现fifo_empty[1]正常但其余全为Z,而这是不合理的。

2. Verilog中使用除法计算参数时请格外小心

Verilog是类C语言。C语言中关于整数除法实际上是定义为整数除法取除数的行为的,因为两整数int类型运算后必然也是int类型(除非进行类型转换)。

显然,在C语言中3/2结果是13%2结果是1

在Verilog中,除和取余运算也都存在。取余时候不会犯错,但是算除法时极易忘记结果是整型这一事实。

2.1. 例子

以下是一个常见于tb中的产生时钟的代码,参数用于控制时钟周期。以下的代码中时钟希望是1 GHz。

    parameter   PRD_CLK_DLL =   1.0;  // 1G sample rate
    initial begin
        clk_DLL = 0;
        forever #(PRD_CLK_DLL/2)
            clk_DLL = ~clk_DLL;
    end

但是请注意一定不能写作PRD_CLK_DLL = 1;,因为1/2结果是0。这会使得仿真时进入死循环而报错。写1缺省类型指定为integer,但是如果写1.0缺省类型就会指定为real。这也是C语言中常用的技巧。

还有一种可能的写法是parameter real PRD_CLK_DLL = 1;,将参数定义为实数而非整数,不过我没有试验过。

3. 调用含参模块的格式

注意参数在实例名称之前,格式参考以下例子。

3.1. 例子1:简单传参

按照定义参数顺序传参。

    Chn_read_fifo_multi #(9,0) u_source(
        .SiTCP_CLK          (SiTCP_CLK),
        .dout               (dout),
        .rdclk              (clk_syn),
        .rst_n              (rst_n),
        .len_data           (len_data),
        .rd_en              (rd_en),

        .fifo_dout_pack     (fifo_dout_pack),
        .tsm_done           (tsm_done),
        .wr_done            (wr_done),
        .fifo_full          (fifo_full),
        .fifo_empty         (fifo_empty)
    );

3.2. 例子2:参数名称传参

按照名称传参。注意,还有其他方法传参,这两种方法是ANSI风格的。

    Chn_read_fifo_multi #(
        .N      (N),
        .OFFSET (OFFSET)
    )   u_Chn_All9_Readout (
        .SiTCP_CLK      (clk_125M), // SiTCP CLK 125MHz,also for SiTCP FIFO,RBCP
        .dout           (dout),
        .rdclk          (clk_syn),
        .rst_n          (GLB_RST_n),
        .len_data       (len_data),
        .rd_en          (rd_en),

        .fifo_dout_pack (fifo_dout_pack),
        .tsm_done       (tsm_done),
        .wr_done        (wr_done),
        .fifo_full      (fifo_full),
        .fifo_empty     (fifo_empty)
    );

4. TB中可以多使用force

TB的语法是自由的,不用考虑综合性。使用force可以不用在乎某个信号的类型和原本的值而强行赋值。反向操作是release

5. 信号是允许跨层调用的

信号跨模块层级引出并不一定非要用input/output来进行。也可以直接调用模块内部信号。

5.1. 例子

.符号来表征层级关系。

initial force uut.u_fifo_combine.SiTCP_CLK = uut.clk_125M;
initial force uut.u_fifo_combine.LVDS_CLK0_P = uut.clk_125M;
initial force uut.u_fifo_combine.LVDS_CLK0_P = ~uut.clk_125M;

6. TB中使用\$stop好过使用$finish

$finish的含义是 “结束仿真”,所以理论上是会退出仿真器的(虽然很多仿真器会二次确认是否退出)。而$stop只是单纯的指 “仿真时间停止”,更符合TB的需求。

7. generate语句需要命名

不知道是否所有检查器都会检查这一条。

generate...for之后紧跟的begin...end是需要命名的。

7.1. 例子

generate
    genvar i;
    for (i = 1; i <= 9 ; i = i + 1)
    begin : gen_Chn_read_fifo
        Chn_read_fifo u_Chn_read_fifo(
            .SiTCP_CLK      (SiTCP_CLK),
            .sca_data       (dout[i]),
            .sca_rdclk      (rdclk),
            .rst_n          (rst_n),
            .len_data       (len_data[8:1]),
            .rd_en          (rd_en[i]),

            .fifo_dout      (fifo_dout_pack[8*i -: 8]),
            .tsm_done       (tsm_done[i]),
            .wr_done        (wr_done[i]),
            .fifo_full      (fifo_full[i]),
            .fifo_empty     (fifo_empty[i])
        );
    end
endgenerate

之所以需要这么做,是因为寻找实例时需要一个名字来区分generate产生的并联模块。比如假如要寻找以上generate语句的LSB模块,名称为<top>.gen_Chn_read_fifo[1].u_Chn_read_fifo<top>代表更高层的模块实例)。

如果没有起名字,也许系统也会自动起一个名字?但是规范起见应该自己起个名。

从这个角度看,generate...if可能不需要命名?但是没用过这个语句,无法确定。

posted @ 2024-03-19 14:34  白发戴花君莫笑  阅读(27)  评论(0编辑  收藏  举报