ModelSim入门教程和两个典型例子

1 ModelSim入门教程

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

  1. 使用LiberoSoc+ModelSim进行FPGA设计的前仿真
  2. 使用ModelSim进行FPGA设计的功能仿真

ModelSim是Model Technology(Mentor
Graphics的子公司)的HDL硬件描述语言的仿真软件,该软件可以用来实现对设计的VHDL、Verilog HDL
或是两种语言混合的程序进行仿真,同时也支持IEEE常见的各种硬件描述语言标准。
无论是从使用界面和调试环境,还是从仿真速度和效果上看,ModelSim都可以算的上是业界比较优秀的HDL语言仿真软件。它是唯一的单内核支持VHDL和Verilog
HDL混合仿真的仿真器,是做FPGA/ASIC设计的RTL级和门级电路仿真的好选择,它采用直接优化的编译技术,Tcl/Tk技术和单一内核仿真技术,具有仿真速度快,编译的代码与仿真平台无关,便于IP核的保护和加快错误程序定位等优点。

ModelSim不仅可以用于数字电路系统设计的功能仿真,还能用于时序仿真。一般在ModelSim创建工程的步骤包括创建工程、编写或者添加源代码、编译、启动仿真器、debug与分析。

2 组合逻辑电路的功能验证

本部分和第三部分分别是ModelSim-TestBench完整设计方法及实例4.1和4.2部分功能验证和时序验证的实例,顺便通过两个实例练习ModelSim的基本使用。

2.1 两位二进制全加器

要求:设计一个两位二进制全加器,全加器的A和B两个是1位二进制加数的输入端,CI是低位来的进位输入端,CO是向高位进位的输出端,SO是全加器的本位和值。

全加器Verilog代码:adder.v

module adder( so, co,a, b, ci );
input a, b, ci;
output so, co;
wire a, b, c, co,so;
assign {co, so} = a + b + ci;

endmodule

代码中co是进位,so是相加之后的本位值。 input、output、inout预设值都是wire型,因此第四行代码可以省略。

全加器TestBench代码:adder_tb.v

`timescale 1ns/1ns
module adder_tb;
wire so,co;
reg a,b,ci;

adder U(.so(so),.co(co),.a(a),.b(b),.ci(ci));

initial // initializes the register variable to a definite value of one 

begin 
    #20 a=0;b=0;ci=0;
    #20 a=0;b=0;ci=1;
    #20 a=0;b=1;ci=0;
    #20 a=0;b=1;ci=1;
    #20 a=1;b=0;ci=0;
    #20 a=1;b=0;ci=1;
    #20 a=1;b=1;ci=0;
    #20 a=1;b=1;ci=1;
    #20 $stop;
end
endmodule

仿真结果及分析
二位加法器的组合逻辑仿真结果如下:
仿真结果
分析:从上图可以看出a+b+ci确实是等于co+so的,而且上述仿真结果可以看出a,b,c的八种输入均以真值表的形式输入,输出也是符合预期的。

程序解读
第一次看代码,务必尽量看懂每一行。这样后续再看其他代码就会发现只需要补充记忆,而且补充记忆的会越来越少。

    1. 首先看程序第二行的
`timescale1ns/1ns

这句代码,这个是时间尺度指令,它是用来定义模块的仿真时间单位和时间精度的,其使用格式为:`timescale 仿真时间单位/时间精度,一般新建文件的模板也会有提示。

//`timescale <time_units> / <precision>

用于说明仿真时间单位和时间精度的数字只能是1、10或100,不能为其它的数字,单位可以是s、ms、us、ns、ps和fs。单位换算关系为:1 s = 10e3 ms = 10e6 us = 10e9 ns = 10e12 ps。

仿真时间单位是指的模块仿真时间和延时的基准单位,也就是说只有定义了仿真时间单位,程序中的延时符号"#"才有意义,如程序中的一行 #20 a = 0; b = 0; c = 0; 前面的 #20 就是延时20个时间基准单位,按照程序中的1ns这个基准,就延时了20个ns。需要说明的是该行程序的下一行 #20 a =0; b = 0; c = 1; 前面的延时20个ns是相对于前一个的延时来说的,也就是第二行在第一行完了之后延时20ns执行。这时候再看看仿真的波形图就不难理解最开始的线为什么是红色而不是正常的绿色的原因了,因为我们在程序中begin的下一行就是 #20 a =0; b = 0; c = 0; 它前面的延时20个ns是相对于begin的延时,也就是说程序开始的时候是什么都不做的,输出为不确定的值,过了20个ns才将全0赋给了a、b和ci,这个时候才是最开始的绿线的部分。

因此,如果我们把begin……end模块的最后一行

#20 $stop;

中的“#20”去掉,就观察不到仿真结果的最后一个输入的结果,就是下图蓝色圈中的部分。
在这里插入图片描述

    1. 用元件例化语句
adder U(.so(so),.co(co),.a(a),.b(b),.ci(ci));

把全加器设计电路嵌入到Test Bench程序中。引用模块时端口可以通过以下两种方式连接。
(1)adder U(so, co, a, b, c);
这种方式U(so, co, a, b, c)严格按照adder模块中的端口顺序连接,好处是不用标明原模块的端口名,但是必须严格对应。
(2)adder U(.a(a),.b(b),.ci(ci),.so(so),.co(co));
这种方式不要求严格对应,直接用“.端口(连接信号)”的形式表示,好处是提高了程序的可移植性和可读性。

    1. 程序的后面有一句
 #20 $stop; 

这个是一个系统任务,用来暂停仿真过程的,将控制权交还给用户,用户在取得控制权以后可以输入其它的控制命令或者查看仿真结果等,之后可以从暂停的地方恢复仿真过程。$stop有两种表达形式,带参数的和不带参数的:

$stop;
$stop(n); //n可以取0、1或2

  • 不带参数的$stop等同于$stop(0),在暂停时不输出任何信息;
  • $stop(1)在暂停时输出当前仿真时刻和暂停处在程序中的位置;
  • $stop(2)不仅有$stop(1)的作用,还能输出仿真时占用内存大小和CPU时间。

为了测试$stop指令,将TestBench代码增加了几个stop断点,每次运行到stop断点时,在Transcript中输入run,运行的过程如下图示。
stop断点测试通过设置断点和$stop(0)可以发现,两个是一模一样的,即$stop(0\1\2)指令是采用Breakpoint的方式暂停的,不同的是$stop指令能输出运行时间、占用内存等更多的消息。

而用于退出仿真过程的系统任务是$finish,我们在点击Run(开始运行)的时候,系统会询问我们是否要结束仿真,假如我们选"是",这个系统任务会把ModelSim软件在完成仿真后关闭,假如我们选"否",则可以继续留在仿真界面。

3 时序逻辑电路的功能验证

3.1 八位加法器

源程序

module adder8(clk,aclr,load,load_din,dout);
// Portdeclarations
input clk;
input aclr;
input load;
input [7:0] load_din;
output [7:0] dout;

//InternalVariables
wire  clk,aclr,load; 
wire [7:0] load_din;
wire [7:0] dout;
reg [7:0] counter = 0;
   
//CodeStarts Here
always @(posedge clk or negedge aclr)
    if(!aclr)
          counter <= 0;
    else if(load == 1)
          counter <= load_din;
    else
          counter <= counter + 1;  
    assign dout = counter;
endmodule

TestBench代码

`timescale 1ns/1ns
module adder8_tb;
reg clk, aclr, load;
reg [7:0] load_din;
wire [7:0] dout;

initial

begin
    clk = 0;
    aclr = 1;
    load = 0;
    load_din = 0;
    #120 aclr = 0;
    #40  aclr = 1;
    #20  load = 1;
         load_din = 100;
    #20  load = 0;
    #100 $stop(0);
end

always #10 clk = ~clk;
adder8 U(.clk(clk), .aclr(aclr), .load(load), .load_din(load_din), .dout(dout));

endmodule

结果分析

  1. 和组合逻辑不同的是,我们要利用always #10 clk = ~clk; 这个语句来产生周期为20个时间基准单位(1ns)的时钟(方波),即就是20ns的时钟信号。注意:时钟只能用always块才能生成,但要在initial块中赋给时钟的初始值(如clk=0或clk=1),如果不设置时钟初始值,则在仿真的时钟输出端是一个未知x(不变,就是例1中的那段红线了)。

  2. 在initial块中生成复位信号和加载信号,注意:一定要给复位信号和加载信号赋给初始值,否则和不设置时钟初始值一样会出现问题的。

  3. 在initial块的begin语句一开始就设置相关的初始值是一个好习惯。

  4. 和test_counter8.v进行全部的对比后发现和该仿真波形完全一致,仿真结束。load_din和load_out后面是采用16进制。

至于为什么是16进制,我现在也没搞懂????? 搞懂了,双击wave下面的信号,可以弹出radix进制选择。
在这里插入图片描述

4 .v格式源代码和.v格式TestBench代码的3种编写方法

最初不太清楚LiberoSoc与ModelSim之间的关系,也不熟悉ModelSim,只能在LiberoSoc中建立源代码和TestBench,然后通过Simulate打开ModelSim,观察仿真。摸得多了,现在总结了3种编写方法

4.1 LiberoSoc编程+ModelSim仿真

这也是Microsemi LiberoSoc软件自带的方式,是初学者最早接触的方法,方法是在LiberoSoc中建立project,然后添加HDL源代码和HDL TestBench,然后在ModelSim中进行仿真。

操作视频:使用LiberoSoc+ModelSim进行FPGA设计的前仿真

4.2 ModelSim编程+仿真

直接在ModelSim新建工程,然后添加文件时,选择新建文件,然后完成源代码和TestBench的编程工作,再在TestBench上右键选择compile all,就可以进行仿真。

缺点是软件自带的编辑环境不是很好,而且没有LiberoSoc自带的代码语法检测功能。

操作视频:使用ModelSim进行FPGA设计的功能仿真

4.3 第三方编程+ModelSim仿真

使用第三方的编辑工具是推荐的方法。建议使用UltraEdit或Notepad++这些专业的代码编辑软件。UltraEdit偏重于功能的强大和丰富的用户可定制化特性,而Notepad++更加注重易用性。两者在普通功能上差异不是特别大,根据自己的喜好选择一款即可。

也可以直接使用text编程,保存的时候注意存成".v"或".vhd"格式即可,然后ModelSim添加文件时加进去,这种方法也可以,但适用于熟练掌握Verilog语法的工程师。

猜你喜欢

转载自blog.csdn.net/zhanshen112/article/details/107511779