当前位置:   article > 正文

systemverilog硬件设计及建模_SystemVerilog的一些可综合实用技巧

system verilog 可综合

59e2e222a9304b80629c38002c78c310.png
Not True! SystemVerilog was designed to enhance both the design and verification capabilities of traditional Verilog VCS, Design Compiler and Synplify-Pro all support RTL modeling with SystemVerilog.

作为数字设计工程师来说,一项很重要的任务就是写可综合的HDL代码,不可综合的代码那只能是behavior model或者testbench。什么样的HDL代码是可综合的?你能通过代码构思出其对应电路的就是可综合的,否则就是不可综合的,这样的判断也是八九不离十了。简而言之,能在你颅内综合,一般而言就可以被综合工具综合,反之亦然。

现在很多代码,尤其是IP vendor的代码,还是基于verilog的,而不是system verilog的,这可能与其想尽可能地支持多种或者低版本的EDA工具有关。实际上System Verilog有很多好的可综合feature是非常有用的,有助于我们简化代码,提高可读性。至于EDA工具的支持,主流工具都是支持的,可综合的System Verilog代码,Verilator也是支持的。本文我们只考虑可综合的System Verilog特性。

别眼馋Chisel了,SystemVerilog就很香!

1. logic, always_ffalways_comb

Verilog中我们有wirereg,当然reg可能对应组合逻辑中的信号线,也可能对应时序逻辑中的flip-flop。在System Verilog中我们可以把wirereg替换成logic,至于综合成什么,交给综合工具吧。不过作为数字电路设计工程师,代码写下之前你就应该知道综合成组合逻辑还是时序逻辑。你可以继续使用always,组合逻辑使用always @(*),时序逻辑用always @(posedge clk)之类。或者更进一步,我们使用System Verilog里的always_ffalways_comb。顾名思义,always_ff是要综合成时序逻辑中的flip-flop,always_comb是综合成组合逻辑。如果实际综合结果不是名字所提示的那样,那么工具会报错的,这时你可以有改正的机会。比如下面这段代码:

  1. always_comb
  2. if (en)
  3. a = b;

对应的电路是个latch,和组合电路的期望不符,会得到compiler的报错。

正如你所认为的那样,always_ffalways_comb是特意给可综合代码设计的。

不过需要注意的是,logic是单驱动的,不像wire是可以多驱动的,但这对于可综合电路来说完全不是问题。

至于always_latch?除了极特殊场合,一般用不上。

2. typedefstructparameter type, package

能够自由地自定义类型是System Verilog非常大的优势。比如32位的数据,我们会附加一位的校验位,于是我们在代码里可能有多处logic [8:0]来表示带校验位的数据。那如果某天我要把一位的校验位换成3位的ECC怎么办?里面所有logic [8:0]要改为logic [10:0],增加了很多工作量,且容易出错。

有了typedef我们就好办了,我们可以这样使用:

  1. typedef logic [8:0] data_t;
  2. data_t data0, data1;

下次变成3位ECC只需要改typedef里的位宽即可。

甚至我们可以把它定义成struct,把校验位单独拎出来:

  1. typedef struct packed {
  2. logic [7:0] val;
  3. logic par;
  4. } data_t;
  5. data_t data0, data1;
  6. //......
  7. if (data0.par != ^data0.val)
  8. //......

struct的功能极其强,我们可以把一些联系紧密的信号捆绑在一起,实现轻量级的interface功能。packed表示此类型是压缩的,即一维形式存储的,在waveform里显示出来也是一个一维向量,当然仿真工具会把其成员的值也显示出来,方便debug。

如果我想在input, output里使用自定义的类型怎么办?因为在输入输出列表里,此时还没有经过typedef定义。一种简便的方法是定义在parameter列表里:

  1. module foo #(
  2. parameter data_t = typedef struct packed { logic [7:0] val; logic par;}
  3. ) (
  4. input data_t data,
  5. //......

或者参数类型由上一层module传进来:

  1. module foo #(
  2. parameter data_t = logic
  3. ) (
  4. input data_t data,
  5. //......
  6. endmodule
  7. module top;
  8. typedef struct packed {
  9. logic [7:0] val;
  10. logic par;
  11. } data_t;
  12. foo #(
  13. .data_t(data_t)
  14. ) foo (
  15. //......
  16. endmodule

当然,关于类型的定义可以写在package里面,然后使用的时候import即可,这是另一种用法。比如:

  1. package example;
  2. typedef struct packed {
  3. logic [7:0] val;
  4. logic par;
  5. } data_t;
  6. endpackage
  7. module foo
  8. import example::*;
  9. (
  10. input data_t data,
  11. //......

typedef及类型的相关用法用得好了,System Verilog HDL编码水平可以上一个台阶。

3. 多维数组

在电路设计中,有些描述天然就是对应多维数组的,而多维数组一般来说都是可综合的,使用多维数组可以让HDL代码更简洁、直观。比如一组16个8-bit的数据用于计算FFT。我们可以这样定义:

logic [7:0] data [0:15];

也可以这样定义:

logic [15:0][7:0] data;

后者是packed array,和上面说的packed struct类似,是以一维的形式存储的。对于非packed array,先引用后面的维度,对于packed array,先引用前面的维度。需要注意的是,赋值的时候要注意引用index的ascend或者descend特性。 不仅可以使用多维数组,还可以对struct类型增加维度。比如:

typedef data_t [15:0] data_array_t;

4. enum与状态机

在Verilog里表示状态机的状态一般是自定义的几个值,比如用parameter或者localparam定义好几个状态的名称和值,或者用define来定义,也可以直接使用状态的编码值。比如使用localparam来定义:

  1. localparam IDLE = 3'b000;
  2. //......
  3. if (~rst_n)
  4. state <= IDLE;
  5. //......

System Verilog里提供了enum,可以提供类似parameter的效果,但是更自然,也更容易扩展。比如:

  1. typedef enum {IDLE, /*other states*/} state_t;
  2. state_t state;
  3. //......
  4. if (~rst_n)
  5. state <= IDLE;
  6. //......

enum类型也可以指定位宽,如:

enum bit [3:0] {IDLE, /*other states*/} state;

当然,我们也可以手动指定每个状态的编码,否则默认从0开始递增。以上这些用法是可综合的,System Verilog还为enum准备了很多函数,如first(), last(), next()等,可综合的设计里就不要使用了,也没必要。

5. interfacemodport

interface是为了便于模块之间连接而设计的,里面可以做很多事情,里面可以有一系列的信号,也可以有typedef定义的自定义类型,也可以用modport定义信号的方向。modport定义对于可综合代码来说是很重要的。相比较起structinterfacemodport里可以定义inputoutput,但使用interface进行模块间连接的时候需要将其实例化,像这样:

  1. interface intf;
  2. wire a, b, c, d;
  3. modport master (input a, b, output c,d);
  4. modport slave (output a,b, input c,d);
  5. endinterface
  6. module m (intf i);
  7. //......
  8. endmodule
  9. module s (intf i);
  10. //......
  11. endmodule
  12. module top;
  13. intf i();
  14. m u1(.i(i.master));
  15. s u2(.i(i.slave));
  16. endmodule

interface还有很多花里胡哨的用法,但很多是为了建模和testbench用的,modport算是interface里用的最普遍的可综合的功能了。

6. function void

在Verilog里,function只能返回一个输出,而有时候我们可能会有多个输出,如果使用function的话只能把多个输出拼在一起输出,然后再人为解析开。比如一个坐标点,我们可以算出角度和幅度,在Verilog里只能这么写(假设通过组合逻辑算出来,不然就不能用function了):

  1. function [15:0] cordic;
  2. input [7:0] x;
  3. input [7:0] y;
  4. begin
  5. //......
  6. cordic = {angle[7:0], mag[7:0]};
  7. end
  8. endfunction
  9. assign {angle, mag} = cordic(x, y);

而SystemVerilog 则提供了function void,参数里可以加多个output,所以上面的例子可以这么实现了:

  1. function void cordic(
  2. input [7:0] x, y,
  3. output [7:0] angle,
  4. output [7:0] mag
  5. );
  6. //......
  7. endfunction
  8. always_comb
  9. cordic(x, y, angle, mag);

7. genvarfor循环

其实genvar并不算System Verilog的新feature,Verilog里就有。但genvar真是个好东西,可以让代码更简洁明了。比如这样:

  1. module axi_lite_demux #(
  2. //......
  3. )(
  4. //......
  5. output req_t [NoMstPorts-1:0] mst_reqs_o,
  6. input resp_t [NoMstPorts-1:0] mst_resps_i
  7. );
  8. //...
  9. logic [NoMstPorts-1:0] mst_aw_valids, mst_aw_readies;
  10. for (genvar i=0; i<NoMstPorts; i++) begin: gen_mst_aw
  11. assign mst_reqs_o[i].aw_valid = mst_aw_valids[i];
  12. assign mst_aw_readies = mst_resps_i[i].aw_ready
  13. end

配合for使用genvar的时候也无需generate。也可以直接用for完成:

  1. always_comb begin
  2. for (int i=0; i<NoMstPorts; i++) begin: gen_mst_aw
  3. mst_reqs_o[i].aw_valid = mst_aw_valids[i];
  4. mst_aw_readies = mst_resp_i[i].aw_ready;
  5. end
  6. end

使用for也是让代码简洁明了的一个重要手段。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号