python 结构体 枚举类型_【Block-Level Verification】 SystemVerilog 数据类型_数组操作_队列_结构体_枚举类型_字符串_过程块和方法_变量生命周期_例...

System Verilog芯片验证 System Verilog语言

1、数据类型

Verilog本身是来做硬件描述,是对硬件本身的行为进行建模。 SystemVerilog是Verilog的生命延续,.sv是对SystemVerilog进行编译,.v是对Verilog进行编译,SystemVerilog文件对Verilog是完全兼容的,所以把.v文件改成.sv文件进编译是允许的,SystemVerilog是侧重于Verification的语言。

SystemVerilog是Verilog的3.0版本,相比较Verilog将寄存器类型(register)即reg(存储数据)和线网类型(net)即wire(模拟硬件连线)分得很清楚,在sysytem verilog中引入了新的数据类型logic,他们的区分和联系在于:

1、Verilog作为硬件描述语言,倾向于设计人员自身懂得所描述的电路中哪些变量应该被实现为reg,而哪些变量应该被实现为wire。这不但有利于后端综合工具,也更便于阅读和理解。而System Verilog作为侧重于验证的语言,并不十分关切logic对应的逻辑应该综合为reg和wire,因为logic被使用的场景如果是验证环境,那么它只会作为单纯的变量进行赋值操作,而这些变量也只属于软件环境的构建。

2、logic类型被推出的另外一个原因也是为了方便验证人员驱动和链接硬件模块、而省去考虑究竟该用reg和wire的精力,这即节省了时间,也避免了出错的可能,logic可能被综合为reg也可能会综合为wire。 与logic相对应的是bit类型,它们均可以构建矢量类型(vector),logic为四值逻辑,即可以表述为0、1、X、Z(没有被驱动),bit为二值逻辑,只可以表述为0和1。之所以要引入二值逻辑,因为在systemVerilog里期望将硬件的世界与软件的世界分开,在硬件的事件里更多的是用logic(四值事件),在软件的事件里更多的是用bit去做(二值事件)。

四值逻辑类型:integer(32位)、logic、reg、net-type   与Verilog里面某些变量对应

二值逻辑类型:byte、shortint、int(32位)、longint、bit   与C里面有些变量对应

如果按照有符号和无符号的类型进行划分,那么可以将常见的变量类型划分为:

有符号类型:byte、shortint、int、longint、integer

无符号类型:bit、logic、reg、net-type (例如wire、tri)

//example 已知程序求display的结果

logic [7:0] logic_vec = 8’b 1000_0000;

bit [7:0] bit_vec = 8’b 1000_0000;

byte signed_vec = 8’b1000_0000;

initial begin$display(“logic_vec = %d”, logic_vec );

$display (“bit_vec = %d”, bit_vec );

$display (“signed_vec = %d”, signed_vec );

end

结果分别是128,128 ,-128,$display调用byte,因为是有符号数,最高位1代表负数,那结果应该是0,可是8位有符号数的取值为-128 ~ 127,从-127~127都可以正常表示,他们只需要7位,而0对应的有两个,为了解决数值一一映射的问题,-128由1000_0000表示。

2、内建数据类型

尽量避免两种不一致的变量(不同数据类型、有无符号、数据位宽)进行操作,导致意外的错误,需要强制类型转换。

静态转化用:想要的数据类型 ’(),转成无符号数:unsigned’()

//example 求两次display的值

byte signed_vec = 8’b 1000_0000 ; //有符号8位

bit [8:0] result_vec; //无符号9位

initial beginresult_vec = signed_vec; //直接把有符号数赋给无符号数是不对的

$display(“@1 result_vec = ‘h%x”, result_vec); //以十六进制输出

result_vec = unsigned’(signed_vec); //强制类型转换后再赋值(高位默认填0)

$display(“@2 result_vec = ‘h%x”,result_vec);

end

答案:’h180, ’h080

动态转化用$cast(tgt,scr)。 静态类型转换一般是不会检查转换是否合法的,因此具有一定的危险性。如i = int’ (10.0-0.1), 但是动态转换函数$cast却不是这样,它在运行时将进行类型检查,如果转换失败,会产生运行错误。动态转化会在方针的时候做操作,而静态转化是在编译的时候做操作。静态转换和动态类型转换都需要操作符号和系统函数介入,我们统称为显示转换。不需要进行转换的操作叫隐式转换。

如下博客里写得非常好

四值逻辑X、Z变为二值逻辑只会转化为0,不同长度的类型赋值会把高位截掉。

//example 求两次display的结果

logic [3:0] x_vec = ‘b111x; //4位宽4值逻辑

bit [2:0] b_vec; //3位宽两值逻辑

initial begin$display(“@1 x_vec =’b%b , x_vec”);

b_vec =x_vec;

$display(“@2 b_bec=’b%b” , b_vec);

end

答案 ‘b110,高位被截掉

3、数组声明

方法: 数据类型+名字+宽度

int lo_hi [0:15]; //16 位int [0]...[15]

int c_style [16]; //16 位int [0]...[15]

多维数组的声明和使用

int array2 [0:7][0:3]; //完整声明

int array3 [8] [4]; //紧凑声明 左高右低,高维度为8,低维度为4

array2 [7][3]=1; //赋值最后一个元素

//初始化和赋值

int ascend [4] = ’{0,1,2,3}; //对4个元素初始化

int descend [5];

descend = ‘ {4,3,2,1,0} ; //为5个元素赋值

descend [0:2] =’{5,6,7} ; //定向赋值

ascend = ‘{4{8}} ; //四个值全为8

descend = ‘{9,8, default:1 };// {9, 8 ,-1, -1, -1}

存储空间考量,以节省空间为导向,中括号放前面是连续存储的,放后面是非连续存储的。

//下面两个数组都可以表示24bit的数据容量,但实际硬件容量的角度出发,哪种方式的存储空间更小呢?(8bit是一个byte,32bits是一个word)

bit [3][7:0] b_pack ; //合并型,会分配连续的24bit的内存空间,占据32bits空间,系统是总是以一个word整数倍分配空间

bit [7:0] b_unpack[3] ; //非合并型,会分配三个不连续(并行的)的内存空间,每个内存空间有8个bit, 占据3个word的存储空间;

//如果使用logic类型来存储24bit的数据,实际的存储空间应该是多少呢?

logic [3][7:0] 1_pack; //由于是四值类型,故占用连续的48bit,即2个word就够了

logic [7:0] 1_unpack[3]; //3是分开存储的,而 8是连续存储的,需要3个word

如何判断最高维度和最低维度

int [8][4] array [16][2] //1 2 3 4维度为16 2 8 4,最高维度为4

4、for循环和foreach循环

initial beginbit [31:0] scr[5] dst[5]; //数组声明另一种声明方式,数据类型,宽度,名字,宽度

for (int i=0; i

src[i] =i;

对数组做循环时,用foreach比较方便

foreach ( dst [ j ] ) //默认创建为J的变量为维度的索引,如果为

dst [ j ] = src [ j ] *2; //bit [31:0] scr[10:6] 用foreach写的比较方便

end

5、基本数组操作复制和比较

对于赋值,可以利用赋值符号“=”直接进行数组的复制。

对于比较,可以用“==”或者“!==”来比较数组的内容。

数组复制用单引号

bit [31:0] src[5] = ‘{0,1,2,3,4};

dst[5] =’{5,4,3,2,1};

if (src==dst)

$display(“src == dst”); //比较数组

else$dispaly (“sec !=dst”);

dst = src; //数组复制

src[0] = 5; //修改数组中的一个元素

6、动态数组

定宽数组类型的宽度在编译时就确定了,但如果在程序运行时数组的宽度不需要立刻定下来,就需要使用动态数组了,动态数组最大的特点即是可以在仿真运行时灵活调节数组的大小。

动态数组在一开始声明时,需要利用‘[ ]’来声明,而数组此时是空的,即0容量,编译时候是空的,其后,需要使用new []来分配空间,在方括号中传递数组的宽度(个数)。

此外,也可以在调用new[ ] 时将数组名也一并传递,将已有数组的值复制到新的数组中。

int dyn[], d2[]; //声明动态数组,当前元素数量为0

initial begindyn = new[5]; //分配5个元素的空间foreach (dyn[j]) dyn[j] = j; //对元素初始化d2 = dyn; //复制一个动态数组,只是值一样,空间是一样的,d2开始是0个元素,复制之后是5个元素d2[0] = 5; //修改复制的值,d2和dyn有着各自独立的空间,复制并不会改变其值$display( dyn[0], d2[0] ); //显示数值0和5dyn = new[20](dyn); //增加分配20个数值并进行复制,小括号里为低位填充为dyn, 最后结果为0,1,2,3,4..00000dyn = new[100]; //重新分配100个数值,而旧值不复存在dyn.delete(); //删除所有元素也可以为dyn=new[0] 或dyn=’{ };

end

intager整形的默认型为X, int整形的默认型为0

7、队列

队列结合了链表(C语言里那样)和数组的优点。

链表:当前元素可以索引到上个元素或者下个元素。

队列:在任何地方可以添加/插入/删除元素,可以通过索引的方式对任何元素进行访问。

队列的声明是使用带美元的符号[$],队列元素的标号是从0到$

队列不需要new[ ]去创建空间,创建队列其默认为空,你只需要使用队列的方法为其增减元素,一开始其空间为0。

队列的一个简单使用即是通过其自带的方法push_back()和pop_front()的结合来实现FIFO的用法。push_back()从后面往里推一个数据,push_frount从前面往里加一个数据,pop_front()从前面往外拿一个数据,pop_back()从后面往外拿一个数据。

int j = 1, q2[$] = {3,4}, q [$] = {0,2,5}; //队列不使用“’”,声明和初始化

initial beginq.insert ( 1 , j ); // {0,1,2,5},在1后插入j,1表示位置1(0-3位置)q.insert (3, q2); //{0,1,2,3,4,5} 在q中插入队列q2q.delete (1); // {0,2,3,4,5} 删除第一个元素

//下列从操作运行速度更快(自有方法)q.push_front (6) ; // 结果为{6,0,2,3,4,5} 从队列头部插入j = q.pop_back (); //结果为{6,0,2,3,4} 从队列尾部弹出q.push_back (8); //结果为{6,0,2,3,4,8} 从队列尾部插入j= q.pop_front ( ); //结果为{0,2,3,4,8} 从队列头部弹出次南 foreach ( q[i] )

$display ( q [i] ); //打印整个队列q.delete () ; //删除整个队列

end

8、关联数组 --- 为了超大容量数组来设计(为了保存稀疏矩阵来设计)

仿真器采用树或者哈希表的形式存放关联数组。

如果只是需要偶尔需要创建一个大容量数组,那么动态数组就足够了,但是如果你需要一个超大容量的呢? (比如模拟存储空间,如果有200000个data,对于内存负担很大)(又不如对一个有着几个G字节寻址范围的处理器进行建模,在典型的测试中,这几个处理器可能只访问了用来存放可执行代码核数据的几百行或几千个字节,这种情况下对几G字节的存储空间进行分配和初始化显然是浪费的)。动态数组的限制在于其存储空间在以开始创建时即被固定下来,那么对于超大容量的数组中有相当多的数据不会被存储和访问。

我们在真正的测试里,在很大的数组空间内,不会真正的存放很多数。只是截取非常有限的一部分来测试空间。意味着这些大量的空白部分不会去测试到,不需要白白浪费掉这些空白空间,故设计关联数组。不希望空白空间在内存中被开辟出来。

如上图,只希望0、3、42、1000、4521、200000空间被开辟出来,其他的空间是有数的,但是不需要访问。

关联数组,可以用来保存稀疏矩阵的元素,当你对一个非常大的地址空间寻址时,该数组只为实际写入的元素分配空间,这种实现方法所需要的空间比定宽或动态数组所占用的空间要小得多。

此外,关联数组有其他灵活的应用,在其他软件语言也有类似的数据存储结构,perl里面被称之为哈希(Hash)或者python里被称作词典(Dictionary),可以灵活赋予键值(Key)和数值(Value)

索引值不需要成为整形,只要是数据类型就OK,即使是字符串。

bit [63:0] assoc [int], idx = 1; //关联数组内容的数据类型为bit[63:0],idx数据类型也相同

repeat (64) begin //对稀疏元素进行初始化

assoc[idx] = idx; //1,2,4,8....

idx = idx << 1;

endforeach ( assoc[i] ) //使用foreach遍历数组,不一定按照一定顺序进行遍历,可以利用.sort()进行排序

//索引值i是int类型

$display( “assoc[%h] = %h” , i , assoc[i] );

//使用函数遍历数组

if (assoc.first(idx)) begin // .first()返回第一个索引值,判断对应的空间是否为空

do

$display(“assoc[%h]=%h”, idx, assoc[idx]);

while(assoc.next(idx)); //get next index .next()返回下一个索引

end

//找到并删除第一个元素

assoc.first(idx); //返回第一个数组元素的索引值

assoc.delete(idx); // 删除该索引值对应的空间

//关联数组也可以用字符串索引进行寻址,使用字符串索引读取文件,并建立关联数组,可以实现字符串到数字的映射。

int switch[string],min_address,max_address;

initial begin

inti,r,file;

strings;

file =$fopen(“switch.txt”,r);

while(! $feof(fire) ) beginr=$fscanf (fire, “%d %s”, i, s);

switch[s] =i;

end$fclose(fire);

//获取最小地址值,缺省为0

min_address=switch[“min_address”]

//获取最大地址值,缺省为1000

if(switch.exists(“max_address”))

max_address =switch[“max_address”]

elsemax_address =1000

//打印数组的所有元素

foreach(switch [s])

$display(“switch[ ‘ %s ’ ] = %0d ”, s ,switch[s] );

end

9、结构体

Verilog最大缺陷之一是没有数据结构,在SV中可以使用struct语言创建结构,和C语言类似。

不过struct的功能少,它只是一个数据的集合,其通常的使用方式是将若干相关的变量组合到一个struct结构定义中。

伴随typedef(自定义新的数据类型)可以用来创建新的类型,并利用新类型来声明更多变量,常常伴随结构体struct使用。

struct {bit [7:0] r, g, b;} pixel; //创建一个pixel结构体,里面有r,g,b三个变量

//这样做太冗余,我们可以直接创建数据类型,提高复用性

//为了共享该类型,通过typedef来创建类型

typedef struct {bit [7:0] r, g, b; } pixel_s; //把此结构体命名为pixel_s 表示

//_s 是书写习惯,表示数据类型

pixel_s my_pixel; //声明变量

my_pixel = ‘{ ‘h10, ‘h10, ‘h10 }; //结构体类型的赋值

什么时候用单引号做初始化,什么时候用单引号做初始化。非合并型,数据不是紧挨着存放的,就用‘{ },如动态数组和结构体。合并型的,数据是紧挨着存放的,用{ },如队列里面

10、软件常用类型--枚举类型

规范的操作码和指令例如ADD、WRITE、IDLE等有利于代码的编写和维护,它比直接使用’h01这样的常量使用起来可读性和可维护性更好。

枚举类型enum经常和typedef搭配使用,由此便于用户自定义枚举类型的共享使用。

枚举类型的出现保证了一些非期望值得出现,降低设计风险,多用于写状态机。

typedef enum {INIT, DECODE, IDLE} fsmstate_e; //枚举类型是三个确定的字符串?组成的fsmstate_e pstate, nstate; //声明自定义变量pastate, nstate

//他们的数据类型是fsmstate_e;

case(pstate)

IDLE: nstate = INIT; //数值赋值INIT: nstate =DECODE;

default : nstate =IDLE;

endcase$display(“Next state is %s”,nstate.name()); // .name()显示状态名

问题:INIT,DECODE,IDLE如果是整型int的话,值是多少呢?0,1,2

如果用整型直接赋值,那么合法的范围是多少呢?该赋值行为不合法,枚举类型可以直接赋值给整型,整型不可以直接赋值给枚举类型,需要进行强制类型转换。

10、字符串

VHDL/Verilog 没有字符串设计,非常难受。

所有与字符串相关的处理,请使用string来保存和处理。

与字符串处理相关的还包括字符串的格式化函数,即如何形成一个你想要的字符串句子呢?可以使用SV系统方法$sformatf(), 如果你只需要将它打印输出,那么就使用$display()吧。

string s; //声明了字符串,SV里面没有类似于C语言空字符“\0”这个概念

initial begins = “IEEE ”; //设置了5个字符串,有一个空字符 ” ”$display ( s.getc(0) ); //字符串函数,显示’I’ $display ( s.tolower() ); //引用字符串函数,全显示小写ieees.putc(s.len()-1,”_”); // len()表示长度,长度-1表示最后一个字符,将空格变为’-’s = {s, ”P1800”}; // ”IEEE-P1800” 字符串拼接$display(s.substr(2,5)); //显示 EE-P,起始位置2,终止位置5,拿到子字符串显示

//创建一个临时字符串并将其打印my_log ( $sformatf (“%s %5d”,s,42) ); // 格式化声明字符串,$sformatf(相当于printf)会返回一个字符串,生成一个字符串

end

task my_log(string message); //打印消息$dispaly(“@%0t: %s”, $time,message);

endtask

11、过程块和方法

硬件过程块initial 和always,initial是不可综合的(不是硬件行为,本身设计就是为了做测试),always是可综合的。

SV有一部分语句是放在硬件里,有一部分是放在软件里用的。

为了区分软件世界和硬件世界,我们先引申出一个概念域(scope),为了区分硬件世界、软件世界,我们将定义的软件变量或者例化的硬件所在的空间成之为域。

因此,module/endmodule, interface/endinterface可以被视为硬件世界,program/endprogram和class/endclass可以被视为软件世界,掌握了这一概念有助于我们接下来分析initial和always的使用域。

always

always为了描述硬件行为,而在使用时需要明晰是时序逻辑电路还是组合逻辑电路,时序逻辑电路敏感列表里要有时钟,@(event)是为了模拟硬件信号的触发行为,always可能被综合成三个电路:寄存器、锁存器、组合逻辑电路,always经常被用在module里。

不同always之间是并行执行的,不同的硬件块肯定是并行执行的。

不可以在always里初始化变量,初始化变量是软件的行为,硬件行为叫复位,一般在定义变量时进行初始化。

initial

initial只执行一次。和always块一样,initial模块无法被延迟执行,即在仿真一开始它们都会同时执行,而不同的initial和always之间在执行顺序上是没有顺序可言的,硬件块之间都是并行执行的,initial不可综合,不应该出现在disign文件里,initial就是为了测试而生的,由于测试需要按照时间的顺序即软件的方式来完成,所以initial便可以实现这一要求。

Verilog时代,所有的测试语句都可以放置在initial中,为了便于统一管理测试顺序,建议将有关测试语句都放置在同一个initial中。

initial过程块可以在module、interface和program中使用。

对于过程块的书写方式,请记住用begin..end将其作用域包住。这一建议同样适用于稍后提到大的控制语句、循环语句等等,初学者可以将其对应于C语言中的花括号{},便于记忆。

function

SV函数定义同C语言非常类似。可以在参数列表中指定输入参数(input)、输出参数(output)、输入输出参数(inout)或者引用参数(ref)。 可以返回数值或者不返回数值(void)。

function int double(inout a); //double为函数名,返回值是intreturn 2*a;

endfunction

initial begin$diaplay(“double of %0d is %0d”, 10 ,double(10));

end

除此之外,function还有以下属性。

1、默认数据类型为logic,例如inout[7:0] addr

2、数组可以作为形式参数传递。

3、function可以返回或者不返回结果,如果返回即需要用关键词return,如果不返回则应该在声明function时采用void function()。

4、如果验证世界里用到了函数和task,记住他们只能传递变量,不能传递线网型、硬件信号、硬件存储器,只有在数据变量可以在形式参数列表中被声明为ref类型,而线网类型则不能被声明为ref类型,也就是说只能传递数据,一定不能传递硬件的信号,存储器,why?

5、在使用ref时(和inout有点类似,但不完全类似),有时候为了保护数据对象,只被读取不被写入,可以通过const的方式来限定ref声明的参数。

6、在声明参数时,可以给入默认的值,例如input[7:0] addr=0,同时在调用时如果省略该参数的传递,那么默认值即会被传递给function

task

task无法通过return来返回结果,因此只能通过output、inout或者ref的参数来返回。

task内可以置入耗时语句,而function不能,这是最重要的,常见的耗时语句包括@event、wait event、#delay等。

task mytask1 (output logic [31:0] x ,inputlogic y);

....

endtask

function和task的建议使用方式:

1、对于初学者,傻瓜式用法即全部采用task来定义方法,因为它可以内置常用的耗时语句,返回值可以写在变量列表里。

2、对于有经验的使用者,请今后对这两种方法类型加以区别,在非耗时的方法定义时使用function,在内置耗时语句(等待某些事件)时使用task,这么做的好处是在遇到了这两种方法定义时,就可以知道function只能用于纯粹的数字或者逻辑运算,而task则可能会被用于需要耗时的信号采样或者驱动场景。

3、如果要调用function,function和task均可对其调用;而调用task只能是task,function不能调用task,因为编译器不能将有延时的task编译到有延时的function里,编译会报错。

typedef struct {

bit [1:0] cmd;

bit [7:0] addr;

bit [31:0] data;

} trans; //trans是结构类型

function automatic void op_copy(trans t, trans s); //赋值函数,automatic如果不知道先不管,后面会介绍t=s; //为什么失败了呢?函数变量模块没有指明方向,默认是input类型,function automatic void op_copy(input trans t, input trans s)相当于t没有返回到参数t中,因为只是输入,退出之后t仍然停留在000,用ref可以修改它,或者用output明确方向。

endfunction

initial begintrans s; //trans类型的变量strans t; //trans类型的变量t

s.cmd =‘h1;

s.addr =‘h10;

s.data =‘h100;

op_copy(t,s);

t.cmd = ‘h2; //把t里的cmd赋值为2

end

最后的变量t的数值为{‘h2, ‘h0, ‘h0},为什么传参失败了呢?见上面代码注释

12、变量生命周期

在SV中,我们将数据的生命周期分为动态(局部变量)(automatic)和静态(全局变量)(static)。

局部变量的生命周期同其所在域共存亡,例如function/task中的临时变量,在其方法调用结束后,临时变量的生命也将终结,所以他们是动态生命周期。

wire/reg等硬件元素的都是全局变量,生命周期从仿真到结束都有,在Verilog中不需要被声明static,但是这些变量确实是static类型。

全局变量即伴随着程序执行开始到结束一直存在,例如module中的变量某人情况下全部为全局变量,用户也可以理解为module中的变量由于存在模糊硬件信号,所以它们是静态生命周期。

如果数据变量被声明为automatic,那么在进入该进程/方法后,automatic变量会被创建,而在离开该进程/方法后,automatic变量会被销毁,而static变量在仿真开始时即会被创建,而在进程/方法执行过程中,自身不会被销毁,而可以被多个进程和方法所共享,是全局的。

Veirlog是硬件设计语言所有的肯定都是static类型,而做verification需要用到动态数据类型来做高层次验证。

module //开始

function automatic int auto_cnt(input a); //automatic为动态变量,局部变量类型

int cnt =0; //垃圾值会被清除cnt +=a;

return cnt;

endfunction

function static int static_cnt(input a); //指明了变量类型,静态的,垃圾值不会被清除static int cnt =0;

cnt +=a;

return cnt;

endfunciton

function static int def_cnt(input a); //如果不指定变量类型,则自动为静态变量static

int cnt =0; //空间未释放,垃圾值会存在cnt +=a;

return cnt;

endfunciton

endmodule //结束

initial begin$display(“@1 auto_cnt = %0d”,auto_cnt(1)); //结果为 @1 auto_cnt =1$display(“@2 auto_cnt = %0d”,auto_cnt(1)); //结果为 @1 auto_cnt =1$display(“@1 auto_cnt = %0d”,static_cnt(1)); //结果为 @1 static_cnt =1$display(“@2 auto_cnt = %0d”,static_cnt(1)); //结果为 @1 static_cnt =2$display(“@1 auto_cnt = %0d”,def_cnt(1)); //结果为 @1 def_cnt =1$display(“@2 auto_cnt = %0d”,def_cnt(1)); //结果为 @1 def_cnt =2

end

在module、program、interface、task、function之外声明的变量都是静态生命周期,即存在于整个仿真阶段,这同C定义的静态变量一致。

在module、interface和program内部声明,且在task、process或者function外部声明的变量也是static变量,且作用域在该块中。

在module、program和interface中定义的task、function默认都是static类型。

在过程块中(task、function、process)定义的变量句跟随他的作用域,即过程块的类型。

如果过程块为static,则他们也默认为static,反之亦然,这些变量也可以由用户显式声明为automatic或者static。

为了使得在过程块中声明的变量有同一默认的声明周期,可以在定义module、interface、package或者static来区分,对于上述程序块默认的声明周期类型为static。

总之,该定义为automatic的地方一定要定义成automatic。

电路信号就是静态的,验证是在硬件语言引入软件工程的特征,即动态。

13、设计例化和链接

// modulel为硬件域,在定义时候需要标注方向、位宽和端口名。

modulemcdt(

inputclk_i,

inputrstn_i,

input [31:0] ch0_data_i,

inputch0_valid_i,

outputch0_ready_o,

output [4:0] ch0_margin_o,

input [31:0] ch1_data_i,

inputch1_valid_i,

outputch1_ready_o,

output [4:0] ch1_margin_o,

input [31:0] ch2_data_i,

inputch2_valid_i,

outputch2_ready_o,

output [4:0] ch2_margin_o,

output [31:0] mcdt_data_o,

outputmcdt_val_o,

output [1:0] mcdt_id_o

);

// 设计例化,首先给出tb的信号列表,然后给出design名称 例化模块的名称{}

// ,. deisgn的端口名 ( tb的信号列表)

moduletb1;

regclk;

regrstn;

reg [31:0] ch0_data;

regch0_valid;

wirech0_ready;

wire [4:0] ch0_margin;

reg [31:0] ch1_data;

regch1_valid;

wirech1_ready;

wire [4:0] ch1_margin;

reg [31:0] ch2_data;

regch2_valid;

wirech2_ready;

wire [4:0] ch1_margin;

reg [31:0] ch2_data;

regch2_valid;

wirech2_ready;

wire [4:0] ch2_margin;

wire [31:0] mcdt_data;

wiremcdt_val;

wire [1:0] mcdt_id;

mcdt dut(

.clk_i(clk)

,.rstn_i(rstn)

,.ch0_data_i(ch0_data)

,.ch0_valid_i(ch0_valid)

,.ch0_ready_o(ch0_ready)

,.ch0_margin_o(ch0_margin)

,.ch1_data_i(ch1_data)

,.ch1_valid_i(ch1_valid)

,.ch1_ready_o(ch1_ready)

,.ch1_margin_o(ch1_margin)

,.ch2_data_i(ch2_data)

,.ch2_vaild_i(ch2_margin)

,.mcdt_data_o(mcdt_data)

,.mcdt_val_o(mcdt_val)

,.mcdt_id_o(mcdt_id)

);

模块连接

在testbench中的链接(connection)指的是有硬件模块参与作为信号驱动方(drive)或者负载方(load)。

Tb中常见的链接有两个硬件模块中的链接,譬如实例A与实例B的链接,可由logic类型完成链接;如果是硬件模块与TB中发送数据激励的链接,则需要考虑数据激励一端如何正确产生数据并发送至DUT一侧,同时数据激励一端也需要对DUT反馈信号做出相应。

如上图ready为接收DUT端激励成功的响应,未成功继续发送,发送成功响应为1。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值