组合逻辑写法陷阱

logic   a  =  b + c ;//仿真时只t=0赋值1次
logic   a ;
assign a = b + c ;//正确写法

logic类型

  • 可替代wire和reg,不可用于inout双向多驱动,inout用wire。
  • 能用logic的地方尽量都用logic显示定义,能在代码笔误,设置错了端口方向情况下,用logic能避免潜在多驱bug。
  • 四状态逻辑:0、1、x、z 占用仿真资源。

双状态类型

在这里插入图片描述

合并(压缩、打包、packed)数组 bit [3:0][7:0] Arr;

  • 连续存储。可以和标量直接赋值。
  • 为了赋值的目的,压缩数组被视为向量。
  • 任何向量表达式都可以分配给任何压缩数组。
  • 目标压缩数组的压缩数组边界不影响分配。如果没有显式强制转换,则不能将压缩数组直接分配给未压缩数组。

非合并(非压缩、非打包、unpacked)数组 bit Arr [3:0][7:0] ;

  • 非连续存储。
  • bit [7:0] arr [256]是[0:255],不是[255:0]
  • 初始化的位置:
bit   [7:0]    arr   [3] === bit   [7:0]    arr   [0:2]='{8'h00,8'h01,8'h02}
arr[0]  |arr[1]    |arr[2]
8'h00   |8'h01     |8'h02

//=============
int   md [2][3] = '{`{0,1,2},`{3,4,5}};
等价于:
int   md [2][3]= '{
                 `{0,1,2},//md[0][0:2]//md[0][0]=0;md[0][1]=1;md[0][2]=2;
                 `{3,4,5} //md[1][0:2]
                 };
数组的声明和初始化
logic [7:0]   array0   [0:3]  ='{0,1,2,3} ;
等价:logic [7:0]   array0   [4]  ='{0,1,2,3} ;
等价:logic [7:0]   array0   [4]  ='{0,1,2,default:3} ;                 
  • 非合并数组的通用方法:
 —min() //最小
 —max() //最大
 —unique() //唯一
 —find()   //returns all the elements satisfying the givenexpression.
 —find_index()  //returns the index of all the elements satisfying the given expression.
 —find_first() //returns the first element satisfying the givenexpression.
 —find_first_index()   //returns the index of the first element satisfying thegiven expression.
 —find_last()   //returns the last element satisfying the givenexpression.
 —find_last_index()    //returns the index of the last element satisfying thegiven expression.

数组的维度和引用关系

在这里插入图片描述

  • 读数组越界了,返回值是元素类型(如int)的缺省值(0)。

定宽数组

  • SystemVerilog新增用维度简写来声明数组:int arr [4]等价int arr [0:3]

常数数组

  • 单引号可理解为:数组格式的标记。
int ascend[4] = '{0,1,2,3};    //1维
int array2 [0:1][0:1] = '{'{0,1}, '{2,3}}; //多维
int array3 [8][4] = '{8{'{0,1,2,3}}}; // 紧凑声明 //每行初始化为0,1,2,3
//可通过切片操作为部分元素赋值,未赋值元素保留默认值(如 int类型默认为0)
int desc[5] = '{4,3,2,1,0};
desc[0:2] = '{5,6,7}; // 前三个元素变为5,6,7,后两个保持1,0
//默认值指定:使用 default:关键字为未显式赋值的元素指定默认值:
int arr[5] = '{9,8,default:1}; // 未赋值的元素(如第3、4位)为1

动态数组

  • 优点:通过动态调整数组大小。非合并结构,适用于数据量不确定的场景。
  • 缺点:方法太少。
  • 动态数组=定宽数组;//会自动调用new[]分配空间并赋值。
定义:
int scores[];       // 声明一个整型动态数组
scores = '{5, 10, 15};  // 自动分配大小为3
scores = new[10];   // 分配10个整型元素的数组
扩展数组:通过 new[size](source)扩展并保留旧数据
int data[] = '{1,2,3};// 3个元素
data = new[5](data);  // 扩展到5个元素,新增元素初始化为0
data[3] = 4;          // 直接赋值新元素
缩小数组:截断多余元素:
data = new[2](data);  // 仅保留前2个元素
  • new[]方法
    • 每个new[]只能作用于动态数组的最高未定的那一维。
    • int arr2 [ ] [ ] = new [ 4 ] ; //int arr2 [ 4 ] [ ] //只能new最高未定的那一维,子数组还需再new[ ] //不初始化
    • int arr2 [ ] = new [ 8 ] (‘{0,1}) ;//将dyn1[0:1]=’{0,1}
    • integer mem[2] [ ];//不创建
  • size()方法
    • 定义:function int size ( );
    • 返回值:动态数组的元素个数,没有创建动态数据,则返回0。
  • delete()方法
    • 定义:function void delete();
    • 返回值:无。数组大小变成0。

队列

缓存长度不确定的数据流。

int q [$]  ={0,1};//不需要用单引号'。长度是动态的。可直接初始化,不能用new。
queue_name.size                  //返回queue的大小
insert(index,item)    //index位置:在index索引处插入item元素,原先的后移
delete(index)         //index位置:刪掉某元素或整个queue
pop_front()           //去除第一个元素(队首) function element_t pop_front();
pop_back()            //去除最后一个元素(队尾)
push_front()          //插入元素到queue(0)(队首)
push_back()           //插入元素到queue($)(队尾)function void push_back(input element_t item);
  • [$:N]:$代表最小值。[N: $]:$代表最大值。
  • { }代表空队列;q = {} ;//删除队列
    在这里插入图片描述

队列拼接(适用于无界队列)

通过 队列拼接操作符 {}直接合并两个队列:

module tb;
  int a[$] = {1, 2, 3};//a[0] = 1
  int b[$] = {4, 5};

  initial begin
    // 拼接队列(a 的元素追加到 b 的末尾)
    b = {b, a};
    $display("b after concatenation: %p", b);  // 输出: {4,5,1,2,3}
  end
endmodule

注意:此方法会创建新队列并替换原队列 b,适用于无界队列。

如何避免questasim优化了队列的pop_front操作?

systemverilog的队列的连续2次pop_front操作,实际却只生效1次

1.-voptargs=+acc
2.+cover=bces
3.中间插入无关操作
下面是错误现场:
在这里插入图片描述
在这里插入图片描述

动态数组忘记调用new

如果一个动态数组没有用 new 函数的话,Systemverilog会默认给其分配一个长度为 2000 的值。当传递过来的长度大于 2000 时就会报错。

数组间赋值

  • 关联数组仅能被关联数组赋值;
  • 如果满足下面条件,fixed-size非合并数组、动态数组、队列、前面数组的部分都能兼容。
    • 源和目的的元素类型相同。
    • 若目的是fixed-size数组,则源数组需要是相同的元素个数。

关联数组

  • 通过键值对(Key-Value)映射实现非合并结构,键可以是任意类型(整数、字符串等)。
  • 关联数组除了复制或比较数组外,其它必须从数组中选择一个单独的元素,然后才能在大多数表达式中使用它。
定义:data_type array_id [ index_type ];
logic  [63:0]  arr [bit[31:0]]
logic  [63:0]  arr [*];//*代表unsigned整数类型
  • num()和size()方法
    • function int num();
    • function int size();
    • 返回值:数组元素个数。0代表空。
  • delete()方法
    • function void delete( [input index] );//index可选。
    • 若指定index,则删除index那一个。若没有指定index,则删除所有。若指定index对应的元素不存在,则也不会告警。
  • exists()存在
    • function int exists( input index );
    • 返回值:1代表存在。0代表不存在。
  • first() 第一个的索引
    • function int first( ref index );//索引值通过index从function传出来。
    • 返回值:0代表数组是空。1代表数组非空。
    • 遍历顺序的“伪有序”表现,使用first/next方法元素的顺序取决于索引类型的排序规则:
    1. 整型索引:按数值升序遍历。
    2. 字符串索引:按字典序升序遍历。
    3. 类索引:顺序不可预测。
  • last() 最后一个的索引
    • function int last( ref index );;//索引值通过index从function传出来。
    • 返回值:0代表数组是空。1代表数组非空。
  • next() 下一个的索引 next()方法查找值大于给定索引参数的最小索引。
    • function int next( ref index );//索引值通过index从function传入传出。
    • 返回值:如果有下一个条目,则为索引变量分配下一个条目的索引,函数返回1。否则,索引不变,函数返回0。
  • prev() 方法查找值小于给定索引参数的最大索引。
    • function int prev( ref index );//索引值通过index从function传入传出。
    • 返回值:如果有前一个条目,则将前一个条目的索引分配给索引变量,函数返回1。否则,索引不变,函数返回0。
      在这里插入图片描述

foreach

  • 顺序规则​​:foreach循环变量顺序严格匹配数组维度声明顺序,每个维度按左–>右遍历。[7:0] 从7—>0;[0:7]从0—>7
  • 是[ i , j ]在一个[ ]内,而不是[ i ] [ j ] 。
  • 忽略某维度,空着不填变量就行。[ i , , k ]
  • i,j不需要定义。
  • 能自动遍历数组的下标,foreach的遍历规则:
logic   [M0:M1] [S0:S1] Arr [D0:D1] [H0:H1] ;
foreach(Arr[i,j,k,l])//jikl写在同一个[]方括号内,逗号分开。
foreach(Arr[i,j,,l])//某个维度不想被遍历,则对应的位置,空着,不写索引变量

- 索引变量i/j/k自动声明,是局部变量。
- 遍历顺序是: Arr[D0-->D1][H0-->H1][M0-->M1][S0-->S1];在方括号[]内是从左到右遍历。

foreach的遍历规则:[A:B]从A-->B遍历,不关心AB的大小;
        可以与日时分秒的变化来类比记忆:
        logic    [A:B][C:D]    Arr     [E:F][I:J]    ;
                 --------->            --------->
                 |分   |秒             |日    |时

        foreach(Arr[i,j,n,m]);//遍历的顺序如下:
        Arr[E-->F][I-->J][A-->B][C-->D]
           |日   |时    |分    |秒

    跳过某个维度不遍历,可以空置即可,例如不想遍历[A:B],将其当做一个整体,可以这样写:
        foreach(Arr[i,j, ,m]);//m从I开始,从左往右
    注:i/j/n/m不用提前定义

fork join

在这里插入图片描述

1 join

在这里插入图片描述
在这里插入图片描述

2 join_any

在这里插入图片描述
在这里插入图片描述

3 join_none

在这里插入图片描述
在这里插入图片描述
join 父进程会阻塞直到这个分支产生的所有进程结束。
join_any 父进程会阻塞直到这个分支产生的任意一个进程结束。 然后继续执行
join_none 父进程会继续与这个分支产生的所有进程并发执行。在父线程执行一条阻塞语句之前,产生的进程不会启动执行。

  • 使用有符号类型要慎重!!!

signed 有符数和无符号数

  • 为什么要发明补码?用补码可以借用无符号加法器实现有符号数的“加“、”减”。
  • 实际上fpga是不区分二进制数是“正”还是“负”的,它就是一个二进制数而已。
  • 对于正数或无符号数,存储的就是原码,对于负数,存储是补码。
  • 正数的补码 = 本身。
  • 负数的补码 = 取反+1。取反:符号位不参与,数值位参与;+1:符号位和数值位都参与。
  • 常数(比如整数、位宽指定的常数)本身并没有固定的符号属性,它们的符号性取决于上下文。

有符号数​​

  • 最高位为​​符号位​​:0表示正数,1表示负数。
  • 补码表示的数值范围: − 2 ( n − 1 ) -2 ^{(n-1)} 2(n1) 2 ( n − 1 ) − 1 2^{(n-1)}-1 2(n1)1(n为位宽=1位符号位+(n-1)个数值位。 − 2 7 = − 128 -2 ^{7}=-128 27=128 2 7 − 1 = + 127 2^{7}-1=+127 271=+127
  • 为什么 − 0 = − 128 -0=-128 0=128 ?因为这是特例,人为定义的,不能用"取反加一"的规则推出。
    在这里插入图片描述
    正数的补码是其本身。
    一个负数,当用n比特表示其补码时,可以用原码取反加1,也可以用2^n-|负数|,两种操作是等价的。
    在这里插入图片描述

​​无符号数​​

​​无符号位​​,所有位均用于表示数值,范围为 0 到 2 n − 1 0 到 2^n-1 02n1。例如,8位无符号数范围为 0 到 255

运算

  • 加了signed关键词的本质是运算时,会先将数据扩位至相同的位宽,然后按照有符号数的运算规则进行处理。
  • 有符号数与无符号数混合运算时,结果默认按​​无符号数​​处理,可能导致意外结果。需通过 $signed() 或 signed 关键字显式声明符号属性
  • 符号位的识别和位宽拓展,有符号数最高位被识别为符号位,高位拓展时拓展符号位,无符号数高位拓展0;
  • 数据的实际值(人的角度如何如何读这个数);

运算规则

常用规则:
1、(有/无符号) = 无符号 + (有/无符号);   //=右边只要出现1个无符号数,右边全按照无符号扩展,高位添0,不管左边的类型。
2、(有/无符号) = 有符号 + 有符号;//=右边全是有符号数,则右边全按照右符号扩展,高位添符号位,不管左边的类型。
3、self-determined写法:a+= (有/无符号);  //=右边是有/无符号数,右边全按照有/无符号扩展,高位添 符号位/0,不管左边的类型。
坑1:十进制数字有符号。!!!!
坑2:单bit位宽的有符号数要特别注意:
logic   signed    ci  ;
如果ci=0,进行符号位扩展到3bit还是000
如果ci=1,进行符号位扩展到3bit却是111 ,!!!!!!!!!!!!千万小心
================
以下是确定表达式结果类型的规则:
-表达式类型仅取决于操作数。它不依赖于左侧(如果有的话)。
-8 十进制数字有符号。!!!!
-4'h12无符号。这种基数是无符号的,除非在基数说明符中使用s符号(如4'sd12)。
-a[0]无符号。无论操作数如何,bit-select(位选)结果都是无符号的。
-a[7:0]无符号。part-select(部分选择) 无论选中整个向量还是部分向量,选择结果也是无符号的,与操作数无关。
— {}拼接,无符号。结果是无符号的,与操作数无关。
—无论操作数如何,比较和&a缩减运算符的结果都是无符号的。
—通过类型强制转换:real转为integer后是signed有符号的。
—任何self-determined (a+= 1'd1) 操作数的符号和大小都由操作数本身决定,与表达式的其余部分无关。
—对于非self-determined操作数,适用以下规则:
    —如果任何操作数为实数,则结果为实数。
    —如果任何操作数都是无符号的,则无论运算符是什么,结果都是无符号的。
    —如果所有操作数都有符号,则无论运算符如何,结果都将有符号,除非另有规定。


在这里插入图片描述

原码与补码之间的转换规则:

- 符号位不变,数值位取反,得到反码。
- 反码(含符号位)+1,得到补码。
- 补码再求补码就是原码。

[a]补 与 [-a]补 转换:

- 全部位按位取反,末位加1。

-a需要多1bit位宽存储。
例如“-a”​,直接写成-a和写成 2 n − a 2^{n}-a 2na,在语法上是完全等价的。
-128特殊考虑。
参考:https://blog.csdn.net/u5588888/article/details/146768725

[a] 与 [-a]补 转换:

- 符号位取反,a数值位按位取反,加1。

原码

反码

补码

无符号加法器

补码

反码

原码

正数和负数的原码、反码、补码

reg [7:0] a ;//无符号的128=8’b1000_0000

原码(-127~+127) 反码(-127~+127) 补码(-128~+127)
正数 符号位=0,数值=原数值 原码 原码
负数 符号位=0,数值=原数值 原码符号位不变,数值位取反 原码符号位不变,反码数值位+1
+127 0111_1111 0111_1111 0111_1111
+1 0000_0001 0000_0001 0000_0001
+0 0000_0000 0000_0000 0000_0000
-1 1000_0001 1111_1110 1111_1111
-127 1111_1111 1000_0000 1000_0001
-128(-0) 不存在 不存在 1000_0000

问题一:取反+1,+1的是否包含反码符号位?
可以包含符号位,也可以不含符号位,两者是一致的。因为+1不会导致符号位翻转。
对于8位原码,负数原码的数值部分范围1127(因为没有-0),取反后数值部分范围1260,加1后范围127~1,不会向符号位进位,所以符号位保持1。但更一般地,我们通常将原码的符号位视为补码的符号位,然后数值部分取反加1,得到补码的数值部分。
反推:要实现符号翻转,反码必须是1111_1111,原码是1000_0000=-0(-128),而-128不存在原码。-128是人为特殊定义的补码,-128作为补码参加±运算没有问题,-128不适用与求原码,因为不在原码的范围。
补码的好处
计算机或芯片内部之所以采用补码来表示负数,因为补码经过运算处理仍然是补码。加乘移位等运算结果都是补码

函数

在这里插入图片描述

全等 与 逻辑等

在这里插入图片描述

仿真的时间函数

  • $time 返回值:64 位无符号整数,按当前模块的时间单位(time unit)缩放后的整数。
  • $stime 返回值:32 位无符号整数,与 $time类似但范围更小。
  • $realtime 返回值:real类型浮点数,保留时间精度的小数部分。
    time t;
    t = $time;
    $error("assert failed at time %0t",t);

clog2(n) 函数

缺点:n=0和1时,返回值是0,不太好,最好自己写函数设置返回值最小值是1

0-7 共8种情况,应该使用多少位宽的reg来表示? 3bit位宽

0-8 共9种情况,应该使用多少位宽的reg来表示? 4bit位宽

0-9 共10种情况,应该使用多少位宽的reg来表示? 4bit位宽

方法一

使用系统函数 $clog2(x)是将x取以2为底的对数并且向上取整。
clog2(0) = 0(特殊规定)
clog2(1)= 0(因log2(1)=0)
clog2(2) = 1
clog2(3)=clog(4) = 2(向上取整)
clog2(8)=3
clog2(9)=clog(16)=4
clog2(10)=clog(16)=4

方法二

自己写一个function

代码

function integer clog2(input integer n);
  n = n - 1;
  for (clog2 = 0; n > 0; clog2 = clog2 + 1)
    n = n >> 1;
endfunction

流操作

  • 流操作 = 位流视角 ,不改原数据,只改“看待方式”
  • 队列 = 逻辑容器
  • {>>{}}表示从左往右变成bit流,可以指定分段的宽度。

非合并数组 流操作

bit [7:0] j[4] = '{8’h0A,8’h0B,8’h0C,8’h0D} ;
非合并数组 视为存储方式: {j[0]=8’h0A,j[1]=8’h0B,j[2]=8’h0C,j[3]=8’h0D} ;
{>>{j}}表达式等价:32’h0a0b0c0d
{>>8{j}}表达式等价:32’h0a0b0c0d
分段对>>无意义。而对于<<有意义,是可以调整最小块的顺序。
{<<{j}}表达式等价 位倒序:32’hb030d050
{<<8{j}}表达式等价 8bit分段倒序:32’h0d0c0b0a
bit [7:0] g[4] ;
g = {<<8{j}} ;// {g[0],g[1],g[2],g[3]} = 32’h0d0c0b0a
等号(=)左边不管是 打包还是pack或unpack数组,都会默认当作pack的来排列,类似:{g2[0],g2[1],g2[2],g2[3]}

队列的流操作

bit [15:0] wq [$] = {16’h1234,16’h5678} ;//{>>{wq}} 视为 {16’h1234,16’h5678}

mailbox

指定类型
mailbox #(string) str_mbx   ;//字符串类型的邮箱
使用typedef
typedef mailbox #(string) s_mbox;
s_mbox sm                   ;
无类型邮箱
mailbox mbx                 ;//无类型
mbx = new()                 ;//无限大  function new(int bound = 0);
mbx = new(5)                ;//容量为5个邮件  function new(int bound = 0);
mbx.num()                   ;//获取邮件个数 function int num();
mbx.put(8'h55)              ;//阻塞,如果满了则死等 task put( singular message);
mbx.try_put(8'h55)          ;//非阻塞,返回0表示满  function int try_put( singular message);
mbx.get(val)                ;//阻塞,如果空了则死等,结果取出放在val变量内  task get( ref singular message );
mbx.try_get(val)            ;//非阻塞,返回0表示空,如果非空,结果取出放在val变量内 function int try_get( ref singular message );
mbx.peek(val)               ;//阻塞,如果空了则死等,结果复制放在val变量内  task peek( ref singular message );
mbx.try_peek(val)           ;//非阻塞,返回0表示空,如果非空,结果复制放在val变量内  function int try_peek( ref singular message );

assert

//简单用法
assert #0 (condition);//condition=0|x|z,若条件不通过,则产生默认告警打印。

//定制断言打印,不会产生默认打印
assert表达“设计必须满足的条件”,例如:“condition必须==1,不然打印display”。
assert #0 (condition) $display("condition==1");//这样写   assert等同于if
else               $display("condition==0");

interface

时钟可以是接口的一部分或者是一个单独的端口。
接口是一组双向logic信号的组合。
接口信号必须使用非阻塞赋值来驱动。
接口信号分组modport指定了方向,加强工具检查。
class内不能定义使用接口类型,要使用虚接口类型。
接口还可以有clocking块,来解决竞争
接口数组:
module top;
  // 接口数组:4个 bus_if 实例
  bus_if bus[4]();
endmodule

interface arb_if(input bit    clk);//定义接口类型
    logic                rst     ;
    logic   [1:0]        grant   ;
    logic   [1:0]        request ;

    modport in (//可选,定义方向检查更加严格  ,不用带位宽,仅仅表示信号方向
        input clk,rst,request,
        output grant//不用output [1:0] grant
    );
    modport out (//可选,定义方向检查更加严格
        input clk,rst,grant,
        output request
    );
endinterface

arb_if     inst_arb_if    ;//例化接口
arb_if     inst_arb_if()    ;//例化接口
module   test_in(inst_arb_if.in  a_if);//例化接口的modport
...
endmodule
module   test_out(inst_arb_if.out  a_if);
...
endmodule

generate

/////////////////////////////////////////
genvar	i;		    //利用genvar声明正整数变量
generate for(i=0;i<;i=+1)//复制模块
	begin : gfor    //begi_end的名字
		assign temp[i] = data_in[2*i+1:2*i]; 
	end
endgenerate
/////////////////////////////////////////////
localparam    S = 6;//定义模块所需参数,用于判断产生电路
generate 
	if(S < 7)		
		assign d = t0 | t1 | t2;
	else
		assign d = t0 & t1 & t2;
endgenerate
/////////////////////////////////////////////
localparam    S = 8;//定义模块所需参数,用于判断产生电路
generate 
	case(S)
	0:
		assign d = t0 | t1 | t2;
	1:
		assign d = t0 & t1 & t2;
	default:
		assign d = t0 & t1 | t2;
	endcase
endgenerate

参考

https://zhuanlan.zhihu.com/p/358807792

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐