Maggie • 来自相关话题

8天前

使用Modelsim进行简单仿真

这里记载一下使用modelsim进行简单的仿真,方便以后使用的时候进行查看。所谓的简单的仿真,就是没有IP核、只用图形界面不用tcl脚本进行的仿真。简单的仿真步骤为:1、改变路径到工作环境下的路径下面,创建工程。2、添加仿真的源文件(.v文件等)。3、编译源文 ...查看全部

这里记载一下使用modelsim进行简单的仿真,方便以后使用的时候进行查看。所谓的简单的仿真,就是没有IP核、只用图形界面不用tcl脚本进行的仿真。简单的仿真步骤为:

1、改变路径到工作环境下的路径下面,创建工程。

2、添加仿真的源文件(.v文件等)。

3、编译源文件。

4、启动仿真,添加仿真信号。

5、调试、查看仿真波形。

这里就使用一个简单的例子——8分频电路,进行演示和讲解:

1、改变路径到工作环境下的路径下面,创建工程:

  ①打开modelsim后如果有工程,则先关掉工程:

blob.png

关掉工程:         

  ②改变工作路径:

 

   ③创建工程


         

        

OK之后,在当前的工作目录下面就创建了一个“work”的文件夹,这也就是物理库:

  

2、添加仿真源文件

            

或者右击空白处:    

 

然后进行选择添加:


      

        

        

3、编译源文件    

或者:

         

 

编译成功后:

      

如果编译不成功的,根据提示的信息,进行修改代码,然后对不成功的模块进行部分编译:     

(如果modelsim看到的中文是乱码,那么可以在编辑器中进行模式转换,如从ASC转换成xxx编码模式),然后修改再编译(注意,所有的目录都应该放在英文路径中)。

4、启动仿真,添加仿真信号。

  ①切换到库选项卡,点开work,启动仿真

  在信息栏上面有两个选项卡:library和project两个选项卡,编译完之后,文件被编译到work目录下(library上面有work的选项,点开可以看到设计和仿真的.v文件),work文件夹里面包含此次工程编译库的信息;用modelsim打开的是.mpf
文件,也就是.mpf是modelsim的工程文件。                  

右击tb文件(如下图所示),选中测试模块的文件,右击,选择第二个simulation without optimistic(不进行优化代码再仿真,因为优化代码可能会把一些信号给优化掉),然后就启动了仿真:

           

  ②进行添加仿真信号

   启动仿真后,信息栏上面的选项卡会增加一个选项:sim;也就是总共有三个选项卡在信息栏上面(Library,project,sim)在sim选项中,左边是模块的整体结构;右击例化的设计文件,选择 ADD wire,就可以添加信号到波形窗口   

 

然后就弹出了波形窗口:     

5、调试、查看仿真波形。

  ①点击运行

         

就可以看到波形了:  

Run是每点击一次运行固定的时间(如100ns);

Continuerun是一直运行,直到点击break,点击break后显示波形;

runall是运行所有,直到点击break,点击break后显示波形。

  ②看波形,发现不对;修改代码后,需要在project区进行重新编译,然后回到sim区,点击重新开始,然后根据①的步骤就可以继续完成简单的仿真工作了。

注:添加仿真信号可以直接把信号拖到波形窗口区:      

然后可以调试查看波形。关于其他调试、查看波形的技巧,以后学习了、用到了再进行记录。

Maggie • 来自相关话题

8天前

UVM寄存器模型regmodel

本文来自于猴哥验证公众号。 从这一期开始我们聊一聊regmodel这个话题,regmodel涉及的方方面面很多,我们分几期展开描述。1. regmodel是什么? 对于初入验证这个坑的小伙伴来说,regmodel还比较陌生,存在和设计里面的 ...查看全部

本文来自于猴哥验证公众号。

 从这一期开始我们聊一聊regmodel这个话题,regmodel涉及的方方面面很多,我们分几期展开描述。

1. regmodel是什么?

 对于初入验证这个坑的小伙伴来说,regmodel还比较陌生,存在和设计里面的寄存器怎么个关系理解不太清晰的情况。 

 字面上看regmodel就是寄存器的模型,这个寄存器是设计里面的控制寄存器组,并不是Verilog的reg信号类型。

  一个寄存器设计中是实现一个或者多个功能,比如:

  reg[15:0]  src_addr;

  这是一个16位源地址的控制寄存器。

  regmodel是一个模型,它是验证环境的一部分,可以看做是DUT设计里面寄存器组在环境中的抽象。

  regmodel的内容来源于DUT,是模块设计在环境中的一个映射,模块设计在实现的映射就是DUT代码咯,这个概念之前有不少小伙伴没太搞懂,大家注意一下。

 regmodel和DUT之间的连接一个是前门方式也就是通过总线的访问,另外一个是后门方式,这个问题我们后面再展开讲。

 所以,regmodel和DUT设计其实是寄存器组的两个表现,它们是一致的,但是一个是环境中的抽象组件,一个是实际的rtl代码,通过行为方式将它们联系起来,保持一致性。

2. 为什么使用regmodel?

  regmodel是随验证方法学发展起来的,OVM里面是叫RGM,VMM里面是regmodel,后来UVM采用了VMM的方式。

  SOC系统中寄存器是非常关键的一个部分,系统中的各项功能都是根据寄存器的配置来实现的。因此寄存器验证涉及到两个方面,一个是对寄存器本身的验证,一个是寄存器的使用。

寄存器本身的验证包括reset 默认值的检查,寄存器的读写属性检查,不使用regmodel的时候一般怎么来做呢?

 `define  SRC_ADDR 16'h10  // 宏定义src_addr寄存器的地址

//dut reset

reg_read(`SRC_ADDR, data );  //读取默认值

reg_write(`SRC_ADDR,0 );     //写0

reg_read(`SRC_ADDR, data );  //读取检查是不是0

reg_write(`SRC_ADDR,16'h55AA );     //写55AA

reg_read(`SRC_ADDR, data );  //读取检查是不是55AA

reg_write(`SRC_ADDR,16'hAA55 );     //写AA55

reg_read(`SRC_ADDR, data );  //读取检查是不是AA55

reg_write(`SRC_ADDR,16'hFFFF );     //写AA55

reg_read(`SRC_ADDR, data );  //读取检查是不是AA55

以上是一个简单的寄存器测试流程,地址使用宏定义传递给task,对寄存器做默认值检查,写0写全1,写全翻转值检查。

这是一个比较纯粹的RW寄存器的测试,那么还有其它属性的寄存器,比如有效位小于寄存器宽度的,寄存器属性只读,只写,读清0,写清0。

对于这些属性,就需要做个class或者struct表示这些属性,然后在寄存器处理的时候根据各个属性做对于的处理操作。

这些处理方式对于寄存器来说都是固定的,而各个公司的处理存在实现的差异,那么使用统一的代码来完成这个任务,就会大大提高效率,这只是regmodel的一个功能而已。

使用寄存器做功能验证,不使用regmodel的情况下,大多会采用宏定义地址方式,这个地址值可以由脚本生成,由于宏定义是一个全局的数值,复用性大打折扣。

对于模块级验证SRC_ADDR是'h10地址,到子系统的时候需要加上偏移,这样宏定义变成如下情况:

`define SRC_ADDR  X0_BASE+'h10

如果子系统上例化了2个这样的模块,那么宏定义变成如下情况:

`define X0_SRC_ADDR  X0_BASE+'h10

`define X1_SRC_ADDR  X1_BASE+'h10

到系统级宏定义又变成如下情况:

`define X0_SRC_ADDR  SYS_BASE+X0_BASE+'h10

`define X1_SRC_ADDR  SYS_BASE+X1_BASE+'h10

如果两个系统在一个环境里呢?又要加区分了

`define S0_X0_SRC_ADDR  SYS0_BASE+X0_BASE+'h10

`define S0_X1_SRC_ADDR  SYS0_BASE+X1_BASE+'h10

`define S1_X0_SRC_ADDR  SYS1_BASE+X0_BASE+'h10

`define S1_X1_SRC_ADDR  SYS1_BASE+X1_BASE+'h10

那么对于寄存器操作的sequence复用起来比较困难,再增加一层宏定义处理方式,如果要实现模块到系统的复用,那么必须在一开始做好规划,而且不能有大的改动。

还有一个情况,现在SOC系统一般都是多核异构的系统,存在多个master都会对同一模块操作。不同的master寄存器映射关系又存在不同,那么宏定义又变成如下:

`define X0_CPU_SRC_ADDR  X0_CPU_BASE+'h10

`define X1_CPU_SRC_ADDR  X1_CPU_BASE+'h10

`define X0_DSP_SRC_ADDR  X0_DSP_BASE+'h10

`define X1_DSP_SRC_ADDR  X1_DSP_BASE+'h10

再加上系统级,双系统......

微信图片_20191014200632.jpg

所以regmodel要解决的另外一个问题就是地址问题,通过分block,分subsystem管理寄存器地址,由regmodel自己去解析对于的address信息,提高抽象行为。

regmodel带来了很多好处,我们都怎么来使用它呢?同样的它也有自己的局限性,那都是些什么问题呢?这些都会在后续文章中一一展开描述。

芯片家 • 来自相关话题

9天前

Soc debug经验<5>

 在验证芯片的case中,很多时候需要先在自己的case中配置模块中的一些寄存器(这些寄存器的某些域可能是数据能够成功穿过该模块的条件,因此必须优先配置这些寄存器)。在数据在到达该模块之后,只有先配置这些寄存器,该模块才能成功输出数据到下一个模块。在 ...查看全部

 在验证芯片的case中,很多时候需要先在自己的case中配置模块中的一些寄存器(这些寄存器的某些域可能是数据能够成功穿过该模块的条件,因此必须优先配置这些寄存器)。在数据在到达该模块之后,只有先配置这些寄存器,该模块才能成功输出数据到下一个模块。在配置好这些寄存器之后,可能需要检测这些配置是否达到要求,会有如下代码。

Unsigned int timeout;

unsigned int signal;

timeout=100000;

signal=0;

While( (signal!=1) &&timeout!=0)

{

       signal= get_value(tb.a.siganl_A);

     @ posedge clk;

      Timeout- -;

}

  这段代码的意思是case在执行到该代码时,在10000clk之内,判断模块a中的信号signal_A是否是1。如果一直不是1 说明初始化某些寄存器没有达到要求。在仿真的log中,我们可以看到仿真不会继续走下去,如果在case中关键位置加了打印信息,打印信息到这里就不会继续打印了。

我们如果知道配置哪一个寄存器可以影响到signal_A,我们首先要在log 中去匹配这个寄存器地址,先检查一下我们配置了几次该寄存器,都配置了哪些值。如果不知道具体是哪一个寄存器的话,只能在波形中trace该信号signal_A

在波形中,可以拉出来该信号signal_A,会发现其值是0。一直trace,可能会发现如下代码。

Port_enable=EN?enable_A:enable.

EN值是1 enable_A的值是1,但是Port_enable的值是0Port_enable的值和enable_A的值不一样。现在只需要解决为什么Port_enable的值和enable_A的值不一样即可。

这里可能会发现,该信号Port_enable force0了。我们在波形中的message中,可以看到Port_enable是多驱动,另外一个赋值语句是force语句。

force tb.a.b.Port_enable=1’b0;

  一般情况下,所有的force语句都被按模块分在同一个目录下面,我们在该目录下,去搜索信号Port_enable,就能看到是否被force了。

菜菜 • 来自相关话题

13天前

平头哥岗位--数字设计、数字验证、数字后端、软件、算法岗位、北京、上海、杭州

微信:861112547平头哥岗位 ,欢迎微信详聊

微信:861112547





平头哥岗位 ,欢迎微信详聊

菜菜 • 来自相关话题

13天前

上海、北京--无人驾驶企业--数字ic设计工程师、数字验证工程师、数字后端工程师,薪资70W+

微信:861112547北京、上海、   无人驾驶企业,数字ic设计工程师、数字验证工程师、数字后端工程师,薪资70W+,欢迎自荐和推荐

微信:861112547


北京、上海、   无人驾驶企业,数字ic设计工程师、数字验证工程师、数字后端工程师,薪资70W+,欢迎自荐和推荐


菜菜 • 来自相关话题

13天前

平头哥、乐鑫、TI等企业

平头哥、乐鑫、TI 、等企业,欢迎微信详聊:861112547

平头哥、乐鑫、TI 、等企业,欢迎微信详聊:861112547

admin • 来自相关话题

1月前

芯片大厂内推群开放申请---微信号chipist1

许多芯片人都有一个大厂梦,职业生涯中如果没有大厂经历,总感觉少了点什么。大厂往往意味着高薪,优质人脉,令人羡慕的环境和福利,比钱更有用的平台光环。但同时,成堆的候选人,激烈的竞争,漫长的选拔流程,也让很多人望而却步。而内推,能让你和大厂靠近一大步。于是,芯片内 ...查看全部
许多芯片人都有一个大厂梦,职业生涯中如果没有大厂经历,总感觉少了点什么。


大厂往往意味着高薪,优质人脉,令人羡慕的环境和福利,比钱更有用的平台光环。但同时,成堆的候选人,激烈的竞争,漫长的选拔流程,也让很多人望而却步。

而内推,能让你和大厂靠近一大步。

于是,芯片内推联盟成立了,只要添加微信号chipist1,就可以加入芯片内推群,快速进入大厂参加面试。没有第三方,大厂员工直接帮你内推。



Maggie • 来自相关话题

2月前

验证环境中经常使用的参数$test$plusargs和$value$plusargs

   $test$plusargs和$value$plusargs是Verilog和SystemVerilog仿真运行时调用的系统函数,可以在仿真命令行直接进行赋值,并且不局限于不同仿真器对于参数在仿真命令中定义格式不同的限制,也避免了调换 ...查看全部

   $test$plusargs和$value$plusargs是Verilog和SystemVerilog仿真运行时调用的系统函数,可以在仿真命令行直接进行赋值,并且不局限于不同仿真器对于参数在仿真命令中定义格式不同的限制,也避免了调换参数带来的频繁编译等问题。使用这两条函数对于搭建测试平台有一定的便利,同时对于理解Factory中用例是如何传递进Proxy Class有一定的帮助。

    本文将对$test$plusargs和$value$plusargs使用过程中遇到的问题进行小结。

    首先,在进行宏定义时,我们经常使用`ifdef等命令在代码中,参看下例:

     test.v

     initial begin

          `ifdef dumpon

                $dumpfile("results.vcd");

                 $dumpvars;

             `endif

         end

如果要能够成功调用$dump等函数,需要在编译(compile)时指定`define的宏定义,其使用方法如下:

 -define dumpon test.v

    但是,在仿真过程中不需要该部分定义时该如何处理呢?

当需要改变编译条件时,经常需要重新编译。并且一旦编译通过,在编译阶段指定的宏定义在整个仿真运行过程中一直有效,因此,如果需要修改宏定义,则需要重新进行编译,从而降低了仿真的效率。

   为此,可以使用$test$plusargs和$value$plusargs进行解决,该函数的调用发生在仿真运行(run)阶段。这样仅需要对设计进行一次编译即可,如果需要改变相应的条件,可以在run的时候动态指定,这样有利于脚本处理进行回归的验证,同时也有利于object的动态construct。


1.$test$plusargs

    在运行(run)仿真时指定要选择的条件,即只需要在仿真运行命令(run-options)中指定参数需要选择的条件即可,例如下例中,如果要将test01.dat、test02.dat、test03.dat分别load到各自的men中,仅需要如下命令在运行命令中加入“<+test01+test02+test03>”即可,当仿真运行时,$test$plusargs会在命令行中搜索指定的字符,若找到相应字符,在函数返回“1”,否则返回“0”。如果下次仿真时不需要test01时,仅需要将test01从运行命令中删除即可。

     test.v

      initial begin

           if($test$plusargs("test01"))

               $readmemh("test01.dat",mem01);

           if($test$plusargs("test02"))

                $readmemh("test02.dat",mem02);

           if($test$plusargs("test03"))

                $readmemh("test03.dat",mem03);

          end

+test01+test02+test03...


2.$value$plusargs

$value$plusargs可以讲运行命令(run-options)中的参数值传递给指定的信号或者字符,其语法格式如下:

Integer=$value$plusargs(“string”,signalname);

其中string=”plusarg_format”+”format_string”,”plusarg_format”指定了用户定义的要进行传递的值,”format_string”指定了要传递的值的格式(类似$display中定义的%s、%h、etc.),并且string中”plusarg_format”和”format_string”格式应该为”plusarg_format”(=/+)”format_string”。如果转换后的位宽和传递的值不一致,则按照如下规则转换:

plusarg位宽与sigalname的关系Signalname值
<
plusarg左补零
>plusarg截位
plusarg为负数按照正数处理
不匹配若为指定默认值,则reg类型为x

$value$plusargs使用示例如下:

        test.v

         if($value$plusargs("FINISH=%d",stop_clk)) begin

                  repeat(stop_clk) @(posedge);

                  $finish;

                end

             if($value$plusargs("TESTNAME=%s",testname))

                begin

                    $display("Running test %0s",testname);

                     ...

                 end

              if($vaue$plusargs("FREQ=",frequency)) begin

                   frequency=8.333333;

                   ...

          end


若使用的运行命令如下:

    +FINISH=10000+TESTNAME=this_test+FREQ=5.6666

则上例的运行结果为:

    stop_clk : 10000

    testname:this_test

    frequency:5.6666(如果run-options中没有增加“FREQ=5.6666”,那么frequency为8.333333)。

芯片家 • 来自相关话题

2月前

Soc debug经验<4>

  在soc芯片验证的时候,有时候需要我们验证一段数据通路,我们需要发送一个数据包,让这笔数据包从起始端,到达我们想要的目的端。这里有两个比较重要的点是需要注意的,第一个点是如何保证这笔数据包按照我们想要的数据通路走;第二个点是如果已经按照我们想要的 ...查看全部

  在soc芯片验证的时候,有时候需要我们验证一段数据通路,我们需要发送一个数据包,让这笔数据包从起始端,到达我们想要的目的端。这里有两个比较重要的点是需要注意的,第一个点是如何保证这笔数据包按照我们想要的数据通路走;第二个点是如果已经按照我们想要的数据通路走下去了,如何保证这笔数据能成功穿过每一个模块而不丢掉。

1.       如何保证这笔数据按照我们想要的数据通路走。

例如数据通路A<->B<->C<->D<->E。我们想让数据通过A->B->C->D->C->B->F。在Soc芯片中,如果我们想从A模块发送数据到F中,在编译的时候需要A模块是一个A模块的UVC,也就不是A模块的rtl代码,这样我们就可以利用A模块的UVC发送数据包 。从A发出来的数据包到达了D之后,可以有两路通路,一个是到E模块,另一个可以是从D返回去,也就是D->C->B->F,到达F模块。那这里如何保证数据包到达D模块之后,让数据包沿着D->C->B->F,到达F模块。

     在模块D中,会有各种各样的Decoder小模块,这些Decoder决定了到达模块D之后,数据包接下来的走向。这些Decoder是根据来源不同划分的,比如从模块A发出来的数据包可以叫A_Decoder,从其他模块A1发出来的,可以叫A1_DecoderAA1大概率属于不同类型的模块。这两个Decoder的逻辑很相似,里面都会有一个信号destination,该信号的值决定了要到达哪一个模块。

   举例:

   assign Destination= ~enable? 0:F_mem? `to_F : E_mem? `to_E: 0;

可以看到,我们要想让数据包成功到达F,必须让F_mem拉高。我们继续追踪F_mem

可能会发现这个F_mem的逻辑是对地址的范围判断。

   举例:

   assign F_mem=(addr >=F_mem_low)&&(addr<=F_mem_high);

这里就是说,我们从A模块发送到F模块的地址是有限制的,如果这个地址在F模块的memory区间里,接下来D模块就会发送数据到F,沿着D->C->B->F模块走。这里可能牵扯到在我们的case 里面,要先配置F_mem_low F_mem_high之后,才去从模块A发送数据包。如果你知道配置哪一个寄存器可以配置F_mem_low F_mem_high,那更好了,如果你不知道配置哪一个寄存器,这也没有关系,你可以读完接下来我要说的第二点去找到该寄存器。

 

2.       如果已经按照我们想要的数据通路走下去了,如何保证这笔数据能成功穿过每一个模块而不丢掉。

       假设我们想验证A->B->C->D->C->B->F这段数据通路。从模块A发送出来的地址到达模块C 的输入端口之后,这个时候发现数据包在CD的输出端口上没有了。我们去追踪该输出地址接口,可能会发现如下逻辑。

  Case (state)

  `idle:

       If(enable)

        begin

            C_D_addr<=C_D_addr_;

            state<=`send;

        end

   `send:

        …..

     我们可能发现C_D_addr_是有数据的,但是这个enable是等于0的,这个状态机只停留在idle状态,C_D_addr没有数据。

    这里enable没有起来,所以我们会继续追踪enable为什么没有被拉起来。(这个就像第一个点里面F_mem_lowF_mem_high没有数据是一样的)我们一直追踪enable,会发现如下decoder代码。

case(addr[7:0])

20:  enable_C_D_reg_control <=1;

30:  …….

…….

    我们如果追踪addr,能看到完整的地址,在波形中的这些地址都是我们已经配置的寄存器,这里显然没有我们想要的那个寄存器。实际也没有必要去追踪addr了。首先我们要明白,enable_C_D_reg_control是我们要配置寄存器的一个域,然后要配置寄存器地址的后面8位是20。所有寄存器地址define一般都会放在一个文件里。我们可以通过匹配一些关键词去找到该寄存器。

格式如下:

` enable_C_D_reg  10122120

 …….

    比如我们找到了该寄存器叫enable_C_D_reg,我们还需要确定域enable_C_D_reg_controlenable_C_D_reg是第几位。一般情况下,我们可以去查询寄存器的spec,去了解一下enable_C_D_reg寄存器,也可以通过查询UVM的寄存器模型去了解该寄存器。假设我们查到了该域enable_C_D_reg_control32位寄存器enable_C_D_reg的第二位。那在我们从A端发送数据包之前,需要在自己的case cpp里面配置该寄存器。每个验证环境配置具体函数不一样,但是大致思路都是一样的。配置思路代码如下:

data= api.read(enable_C_D_reg);

data=data|0x00000002;

Api.write(enable_C_D_reg,data);