zoukankan      html  css  js  c++  java
  • 【第一季】CH06_FPGA设计Verilog基础(三)

    第一季】CH06_FPGA设计Verilog基础(三)

    一个完整的设计,除了好的功能描述代码,对于程序的仿真验证是必不可少的。学会如何去验证自己所写的程序,即如何调试自己的程序是一件非常重要的事情。而RTL逻辑设计中,学会根据硬件逻辑来写测试程序,即Testbench是尤其重要的。Verilog测试平台是一个例化的待测(MUT)模块,重要的是给它施加激励并观测其输出。逻辑模块与其对应的测试平台共同组成仿真模型,应用这个模型可以测试该模块能否符合自己的设计要求。

    编写TESTBENCH的目的是为了对使用硬件描述语言设计的电路进行仿真验证,测试设计电路的功能、性能与设计的预期是否相符。通常,编写测试文件的过程如下:

    • 产生模拟激励(波形);

    • 将产生的激励加入到被测试模块中并观察其响应;

    • 将输出响应与期望值相比较。

    6.1 完成的Test bench文件结构

    通常,一个完整的测试文件其结构为

    module Test_bench();//通常无输入无输出

    信号或变量声明定义

    逻辑设计中输入对应reg型

    逻辑设计中输出对应wire型

    使用initial或always语句产生激励

    例化待测试模块

    监控和比较输出响应

    endmodule

    6.2 时钟激励设计

    下面列举出一些常用的封装子程序,这些是常用的写法,在很多应用中都能用到。

    /*----------------------------------------------------------------

    时钟激励产生方法一:50%占空比时钟

    ----------------------------------------------------------------*/

    parameter ClockPeriod=10;

    initial

    begin

    clk_i=0;

    forever

    #(ClockPeriod/2) clk_i=~clk_i;

    end

    /*----------------------------------------------------------------

    时钟激励产生方法二:50%占空比时钟

    ----------------------------------------------------------------*/

    initial

    begin

    clk_i=0;

    always #(ClockPeriod/2) clk_i=~clk_i;

    end

    /*----------------------------------------------------------------

    时钟激励产生方法四:产生固定数量的时钟脉冲

    ----------------------------------------------------------------*/

    initial

    begin

    clk_i=0;

    repeat(6)

    #(ClockPeriod/2) clk_i=~clk_i;

    end

    /*----------------------------------------------------------------

    时钟激励产生方法五:产生非占空比为50%的时钟

    ----------------------------------------------------------------*/

    initial

    begin

    clk_i=0;

    forever

    begin

    #((ClockPeriod/2)-2) clk_i=0;

    #((ClockPeriod/2)+2) clk_i=1;

    end

    end

    6.3 复位信号设计

    /*----------------------------------------------------------------

    复位信号产生方法一:异步复位

    ----------------------------------------------------------------*/

    initial

    begin

    rst_n_i=1;

    #100;

    rst_n_i=0;

    #100;

    rst_n_i=1;

    end

    /*----------------------------------------------------------------

    复位信号产生方法二:同步复位

    ----------------------------------------------------------------*/

    initial

    begin

    rst_n_i=1;

    @(negedge clk_i)

    rst_n_i=0;

    #100; //固定时间复位

    repeat(10) @(negedge clk_i); //固定周期数复位

    @(negedge clk_i)

    rst_n_i=1;

    end

    /*----------------------------------------------------------------

    复位信号产生方法三:复位任务封装

    ----------------------------------------------------------------*/

    task reset;

    input [31:0] reset_time; //复位时间可调,输入复位时间

    RST_ING=0; //复位方式可调,低电平或高电平

    begin

    rst_n=RST_ING; //复位中

    #reset_time; //复位时间

    rst_n_i=~RST_ING; //撤销复位,复位结束

    end

    endtask

    6.4 特殊信号设计

    /*----------------------------------------------------------------

    特殊激励信号产生描述一:输入信号任务封装

    ----------------------------------------------------------------*/

    task i_data;

    input [7:0] dut_data;

    begin

    @(posedge data_en); send_data=0;

    @(posedge data_en); send_data=dut_data[0];

    @(posedge data_en); send_data=dut_data[1];

    @(posedge data_en); send_data=dut_data[2];

    @(posedge data_en); send_data=dut_data[3];

    @(posedge data_en); send_data=dut_data[4];

    @(posedge data_en); send_data=dut_data[5];

    @(posedge data_en); send_data=dut_data[6];

    @(posedge data_en); send_data=dut_data[7];

    @(posedge data_en); send_data=1;

    #100;

    end

    endtask

    //调用方法:i_data(8'hXX);

    /*----------------------------------------------------------------

    特殊激励信号产生描述二:多输入信号任务封装

    ----------------------------------------------------------------*/

    task more_input;

    input [7:0] a;

    input [7:0] b;

    input [31:0] times;

    output [8:0] c;

    begin

    repeat(times) //等待times个时钟上升沿

    @(posedge clk_i)

    c=a+b; //时钟上升沿a,b相加

    end

    endtask

    //调用方法:more_input(x,y,t,z);  //按声明顺序

    /*----------------------------------------------------------------

    双向信号描述一:inout在testbench中定义为wire型变量

    ----------------------------------------------------------------*/

    //为双向端口设置中间变量inout_reg作为inout的输出寄存,其中inout变

    //量定义为wire型,使用输出使能控制传输方向

    //inout bir_port;

    wire bir_port;

    reg bir_port_reg;

    reg bi_port_oe;

    assign bi_port=bi_port_oe ? bir_port_reg : 1'bz;

    /*----------------------------------------------------------------

    双向信号描述二:强制force

    ----------------------------------------------------------------*/

    //当双向端口作为输出口时,不需要对其进行初始化,而只需开通三态门

    //当双向端口作为输入时,只需要对其初始化并关闭三态门,初始化赋值需

    //使用wire型数据,通过force命令来对双向端口进行输入赋值

    //assign dinout=(!en) din :16'hz; 完成双向赋值

    initial

    begin

    force dinout=20;

    #200

    force dinout=dinout-1;

    end

    /*----------------------------------------------------------------

    特殊激励信号产生描述三:输入信号产生,一次SRAM写信号产生

    ----------------------------------------------------------------*/

    initial

    begin

    cs_n=1; //片选无效

    wr_n=1; //写使能无效

    rd_n=1; //读使能无效

    addr=8'hxx; //地址无效

    data=8'hzz; //数据无效

    #100;

    cs_n=0; //片选有效

    wr_n=0; //写使能有效

    addr=8'hF1; //写入地址

    data=8'h2C; //写入数据

    #100;

    cs_n=1;

    wr_n=1;

    #10;

    addr=8'hxx;

    data=8'hzz;

    end

    /*----------------------------------------------------------------

    Testbench中@与wait

    ----------------------------------------------------------------*/

    //@使用沿触发

    //wait语句都是使用电平触发

    initial

    begin

    start=1'b1;

    wait(en=1'b1);

    #10;

    start=1'b0;

    end

    6.5 仿真控制语句及系统任务描述

    /*----------------------------------------------------------------

    仿真控制语句及系统任务描述

    ----------------------------------------------------------------*/

    $stop     //停止运行仿真,modelsim中可继续仿真

    $stop(n) //带参数系统任务,根据参数0,1或2不同,输出仿真信息

    $finish   //结束运行仿真,不可继续仿真

    $finish(n)  //带参数系统任务,根据参数0,1或2不同,输出仿真信息

    //0:不输出任何信息

    //1:输出当前仿真时刻和位置

    //2:输出当前仿真时刻、位置和仿真过程中用到的memory以及CPU时间的统计

    $random //产生随机数

    $random % n //产生范围-n到n之间的随机数

    {$random} % n //产生范围0到n之间的随机数

    /*----------------------------------------------------------------

    仿真终端显示描述

    ----------------------------------------------------------------*/

    $monitor //仿真打印输出,大印出仿真过程中的变量,使其终端显示

    /*

    $monitor($time,,,"clk=%d reset=%d out=%d",clk,reset,out);

    */

    $display //终端打印字符串,显示仿真结果等

    /*

    $display(” Simulation start ! ");

    $display(” At time %t,input is %b%b%b,output is %b",$time,a,b,en,z);

    */

    $time //返回64位整型时间

    $stime //返回32位整型时间

    $realtime //实行实型模拟时间

    /*----------------------------------------------------------------

    文本输入方式:$readmemb/$readmemh

    ----------------------------------------------------------------*/

    //激励具有复杂的数据结构

    //verilog提供了读入文本的系统函数

    $readmemb/$readmemh("<数据文件名>",<存储器名>);

    $readmemb/$readmemh("<数据文件名>",<存储器名>,<起始地址>);

    $readmemb/$readmemh("<数据文件名>",<存储器名>,<起始地址>,<结束地址>);

    $readmemb:/*读取二进制数据,读取文件内容只能包含:空白位置,注释行,二进制数

    数据中不能包含位宽说明和格式说明,每个数字必须是二进制数字。*/

    $readmemh:/*读取十六进制数据,读取文件内容只能包含:空白位置,注释行,十六进制数

    数据中不能包含位宽说明和格式说明,每个数字必须是十六进制数字。*/

          /*当地址出现在数据文件中,格式为@hh...h,地址与数字之间不允许空白位置,

    可出现多个地址*/

    module

    reg [7:0] memory[0:3];//声明8个8位存储单元

    integer i;

    initial

    begin

    $readmemh("mem.dat",memory);//读取系统文件到存储器中的给定地址

    //显示此时存储器内容

    for(i=0;i<4;i=i+1)

    $display("Memory[%d]=%h",i,memory[i]);

    end

    endmodule

    /*mem.dat文件内容

    @001

    AB CD

    @003

    A1

    */

    //仿真输出为

    Memory[0] = xx;

    Memory[1] = AB;

    Memory[2] = CD;

    Memory[3] = A1;

    6.6加法器的仿真测试文件编写

    上面只例举了常用的testbench写法,在工程应用中基本能够满足我们需求,至于其他更为复杂的testbench写法,大家可参考其他书籍或资料。

    这里提出以下几点建议供大家参考:

    • 封装有用且常用的testbench,testbench中可以使用task或function对代码进行封装,下次利用时灵活调用即可;

    • 如果待测试文件中存在双向信号(inout)需要注意,需要一个reg变量来表示输入,一个wire变量表示输出;

    • 单个initial语句不要太复杂,可分开写成多个initial语句,便于阅读和修改;

    • Testbench说到底是依赖PC软件平台,必须与自身设计的硬件功能想搭配。

    下面具体看一段程序:

    module add(a,b,c,d,e);// 模块接口

    input [5:0] a; // 输入信号a

    input [5:0] b; // 输入信号b

    input [5:0] c; // 输入信号a

    input [5:0] d; // 输入信号b

    output[7:0] e; // 求和输出信号

    wire [6:0]outa1,outa2; // 定义输出网线型

    assign e = outa2+outa1; // 把两部分输出结果合并

    /*

    通常,我们模块的调用写法如下:

    被调用的模块名字- 自定义的名字- 括号内信号

    这里比如括号内的信号,.ina(ina1)

    这种写法最常用,信号的顺序可以调换

    另外还有一种写法没可以直接这样写

    adder u1 (ina1,inb1,outa1);

    这种写法必须确保信号的顺序一致,这种写法几乎没有人采用

    */

    adder u1 (.ina(a),.inb(b),.outa(outa1)); // 调用adder 模块,自定义名字为u1

    adder u2 (.ina(c),.inb(d),.outa(outa2)); // 调用adder 模块,自定义名字为u2

    endmodule

    //adder 子模块

    module adder(ina,inb,outa );// 模块接口

    input [5:0] ina; // ina-输入信号

    input [5:0] inb; // inb-输入信号

    output [6:0] outa; // outa-输入信号

    assign outa = ina + inb; // 求和

    endmodule // 模块结束

    仿真文件:

    `timescale 1ns / 1ps

    module add_tb();

    reg [5:0] a;

    reg [5:0] b;

    reg [5:0] c;

    reg [5:0] d;

    wire[7:0] e;

    reg [5:0] i; //中间变量

    // 调用被仿真模块模块

    add uut (.a(a), .b(b),.c(c),.d(d),.e(e));

    initial begin // initial 是仿真用的初始化关键词

    a=0;b=0;c=0;d=0; // 必须初始化输入信号

    for(i=1;i<31;i=i+1) begin

    #10 ;

    a = i;

    b = i;

    c = i;

    d = i;

    end // 给是输入信号a 赋值

    end

    initial begin

    $monitor($time,,,"%d + %d + %d + %d ={%d}",a,b,c,d,e); // 信号打印输出

    #500 $finish;

    end

    endmodule

    wps112C.tmpwps113D.tmp

  • 相关阅读:
    精细化python 类的内置属性
    python操作excel
    ghost linux
    Linux dd 命令
    Ubantu 使用root登陆的方法
    NSIS Error: "Error writing temporary file. Make sure your temp folder is valid
    error writing temporary file。make sure your temp folder is valid 问题已解决
    安卓电池状态监听
    Android源码下载
    vim插件详细安装过程
  • 原文地址:https://www.cnblogs.com/milinker/p/6383658.html
Copyright © 2011-2022 走看看