赞
踩
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就很香!
logic
, always_ff
与always_comb
Verilog中我们有wire
和reg
,当然reg
可能对应组合逻辑中的信号线,也可能对应时序逻辑中的flip-flop。在System Verilog中我们可以把wire
和reg
替换成logic
,至于综合成什么,交给综合工具吧。不过作为数字电路设计工程师,代码写下之前你就应该知道综合成组合逻辑还是时序逻辑。你可以继续使用always
,组合逻辑使用always @(*)
,时序逻辑用always @(posedge clk)
之类。或者更进一步,我们使用System Verilog里的always_ff
和always_comb
。顾名思义,always_ff
是要综合成时序逻辑中的flip-flop,always_comb
是综合成组合逻辑。如果实际综合结果不是名字所提示的那样,那么工具会报错的,这时你可以有改正的机会。比如下面这段代码:
- always_comb
- if (en)
- a = b;
对应的电路是个latch,和组合电路的期望不符,会得到compiler的报错。
正如你所认为的那样,always_ff
和always_comb
是特意给可综合代码设计的。
不过需要注意的是,logic
是单驱动的,不像wire
是可以多驱动的,但这对于可综合电路来说完全不是问题。
至于always_latch
?除了极特殊场合,一般用不上。
typedef
,struct
与parameter type
, package
能够自由地自定义类型是System Verilog非常大的优势。比如32位的数据,我们会附加一位的校验位,于是我们在代码里可能有多处logic [8:0]
来表示带校验位的数据。那如果某天我要把一位的校验位换成3位的ECC怎么办?里面所有logic [8:0]
要改为logic [10:0]
,增加了很多工作量,且容易出错。
有了typedef
我们就好办了,我们可以这样使用:
- typedef logic [8:0] data_t;
- data_t data0, data1;
下次变成3位ECC只需要改typedef
里的位宽即可。
甚至我们可以把它定义成struct
,把校验位单独拎出来:
- typedef struct packed {
- logic [7:0] val;
- logic par;
- } data_t;
- data_t data0, data1;
-
- //......
- if (data0.par != ^data0.val)
- //......
struct
的功能极其强,我们可以把一些联系紧密的信号捆绑在一起,实现轻量级的interface
功能。packed
表示此类型是压缩的,即一维形式存储的,在waveform里显示出来也是一个一维向量,当然仿真工具会把其成员的值也显示出来,方便debug。
如果我想在input
, output
里使用自定义的类型怎么办?因为在输入输出列表里,此时还没有经过typedef
定义。一种简便的方法是定义在parameter
列表里:
- module foo #(
- parameter data_t = typedef struct packed { logic [7:0] val; logic par;}
- ) (
- input data_t data,
- //......
或者参数类型由上一层module
传进来:
- module foo #(
- parameter data_t = logic
- ) (
- input data_t data,
- //......
- endmodule
-
- module top;
- typedef struct packed {
- logic [7:0] val;
- logic par;
- } data_t;
-
- foo #(
- .data_t(data_t)
- ) foo (
- //......
- endmodule
当然,关于类型的定义可以写在package
里面,然后使用的时候import
即可,这是另一种用法。比如:
- package example;
- typedef struct packed {
- logic [7:0] val;
- logic par;
- } data_t;
- endpackage
-
- module foo
- import example::*;
- (
- input data_t data,
- //......
typedef
及类型的相关用法用得好了,System Verilog HDL编码水平可以上一个台阶。
在电路设计中,有些描述天然就是对应多维数组的,而多维数组一般来说都是可综合的,使用多维数组可以让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;
enum
与状态机在Verilog里表示状态机的状态一般是自定义的几个值,比如用parameter
或者localparam
定义好几个状态的名称和值,或者用define
来定义,也可以直接使用状态的编码值。比如使用localparam
来定义:
- localparam IDLE = 3'b000;
- //......
- if (~rst_n)
- state <= IDLE;
- //......
System Verilog里提供了enum
,可以提供类似parameter
的效果,但是更自然,也更容易扩展。比如:
- typedef enum {IDLE, /*other states*/} state_t;
- state_t state;
- //......
- if (~rst_n)
- state <= IDLE;
- //......
enum类型也可以指定位宽,如:
enum bit [3:0] {IDLE, /*other states*/} state;
当然,我们也可以手动指定每个状态的编码,否则默认从0开始递增。以上这些用法是可综合的,System Verilog还为enum
准备了很多函数,如first()
, last()
, next()
等,可综合的设计里就不要使用了,也没必要。
interface
和modport
interface
是为了便于模块之间连接而设计的,里面可以做很多事情,里面可以有一系列的信号,也可以有typedef
定义的自定义类型,也可以用modport
定义信号的方向。modport
定义对于可综合代码来说是很重要的。相比较起struct
,interface
的modport
里可以定义input
和output
,但使用interface
进行模块间连接的时候需要将其实例化,像这样:
- interface intf;
- wire a, b, c, d;
- modport master (input a, b, output c,d);
- modport slave (output a,b, input c,d);
- endinterface
- module m (intf i);
- //......
- endmodule
- module s (intf i);
- //......
- endmodule
- module top;
- intf i();
- m u1(.i(i.master));
- s u2(.i(i.slave));
- endmodule
interface
还有很多花里胡哨的用法,但很多是为了建模和testbench用的,modport
算是interface
里用的最普遍的可综合的功能了。
function void
在Verilog里,function
只能返回一个输出,而有时候我们可能会有多个输出,如果使用function的话只能把多个输出拼在一起输出,然后再人为解析开。比如一个坐标点,我们可以算出角度和幅度,在Verilog里只能这么写(假设通过组合逻辑算出来,不然就不能用function
了):
- function [15:0] cordic;
- input [7:0] x;
- input [7:0] y;
- begin
- //......
- cordic = {angle[7:0], mag[7:0]};
- end
- endfunction
-
- assign {angle, mag} = cordic(x, y);
而SystemVerilog 则提供了function void
,参数里可以加多个output
,所以上面的例子可以这么实现了:
- function void cordic(
- input [7:0] x, y,
- output [7:0] angle,
- output [7:0] mag
- );
- //......
- endfunction
-
- always_comb
- cordic(x, y, angle, mag);
genvar
与for
循环其实genvar
并不算System Verilog的新feature,Verilog里就有。但genvar
真是个好东西,可以让代码更简洁明了。比如这样:
- module axi_lite_demux #(
- //......
- )(
- //......
- output req_t [NoMstPorts-1:0] mst_reqs_o,
- input resp_t [NoMstPorts-1:0] mst_resps_i
- );
- //...
- logic [NoMstPorts-1:0] mst_aw_valids, mst_aw_readies;
- for (genvar i=0; i<NoMstPorts; i++) begin: gen_mst_aw
- assign mst_reqs_o[i].aw_valid = mst_aw_valids[i];
- assign mst_aw_readies = mst_resps_i[i].aw_ready
- end
配合for
使用genvar
的时候也无需generate
。也可以直接用for
完成:
- always_comb begin
- for (int i=0; i<NoMstPorts; i++) begin: gen_mst_aw
- mst_reqs_o[i].aw_valid = mst_aw_valids[i];
- mst_aw_readies = mst_resp_i[i].aw_ready;
- end
- end
使用for
也是让代码简洁明了的一个重要手段。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。