zoukankan      html  css  js  c++  java
  • 【代码更新】同步FIFO design and IP level verification

    一、前言

      应聘IC前端相关岗位时,FIFO是最常考也是最基本的题目。FIFO经常用于数据缓存、位宽转换、异步时钟域处理。随着芯片规模的快速增长,灵活的system verilog成为设计/验证人员的基本功。本文从简易版的同步FIFO开始,熟悉IP设计与验证的基础技能。

    二、IP设计

      FIFO这一IP核已经相当成熟,因此网上资料也是一抓一大把。其中笔者认为较好的一个在文末附录中,需要详细了解FIFO工作原理的朋友可以仔细看看。这里简单介绍下本文设计FIFO的原理与结构。FIFO的内部存储单元是常见的双口RAM,这个IP的精髓在于读写地址的对外屏蔽与自动管理。避免写满、读空至关重要。本文设计的FIFO顶层例化双口RAM和FIFO控制两大模块:前者仅作为存储单元响应读写信号,后者根据读写计数器产生读写指针和重要的空满指示信号。

      代码如下:

    存储模块:

     1 `timescale 1ns/1ps
     2 module dpram
     3 #(parameter D_W=8,
     4             A_W=8)
     5 (
     6   input                   clk,
     7   input                   rst_n,
     8   //write ports
     9   input                   wr_en,
    10   input       [D_W-1:0]   wr_data,
    11   input       [A_W-1:0]   wr_addr,
    12   //read ports
    13   input                   rd_en,
    14   input       [A_W-1:0]   rd_addr,
    15   output reg  [D_W-1:0]   rd_data
    16 );
    17 //RAM
    18 reg [D_W-1:0] memory [0:2**A_W-1];
    19 reg out_start;
    20 //write operation
    21 always@(posedge clk)begin
    22   if(wr_en)begin
    23     memory[wr_addr] <= wr_data;
    24   end
    25 end
    26 
    27 //read operation
    28 always@(posedge clk or negedge rst_n)begin
    29   if(~rst_n)
    30     rd_data <= 0;
    31   //else if(rd_en)begin
    32   //  rd_data <= memory[rd_addr];
    33   //end
    34   //else if(rd_addr == 1)
    35   //  rd_data <= memory[0];
    36   else if(out_start)
    37     rd_data <= memory[0];
    38   else if(rd_en)
    39     rd_data <= memory[rd_addr];
    40 end
    41 
    42 always@(posedge clk or negedge rst_n)begin
    43     if(~rst_n)
    44         out_start <= 0;
    45     else if(wr_en && wr_addr == 'd0)
    46         out_start <= 1;
    47     else 
    48         out_start <= 0;
    49 end
    50 
    51 endmodule
    dpram.v

    FIFO控制模块:

      1 `timescale 1ns/1ps
      2 module fifo_ctrl
      3 #(parameter A_W = 8,
      4   parameter [0:0] MODE = 0//0- standard read 1- first word fall through
      5 )
      6 (
      7   input clk,
      8   input rst_n,
      9 
     10   output [A_W-1:0] wr_addr,
     11   output [A_W-1:0] rd_addr,
     12 
     13   output empty,
     14   output full,
     15   input wr_en,
     16   input rd_en
     17 );
     18 localparam MAX_CNT = 2**A_W;
     19 localparam FD_W = A_W;
     20 
     21 function [FD_W-1:0] abs;
     22   input signed [FD_W-1:0] data;
     23   begin
     24     assign abs = data >= 0 ? data : -data;
     25   end
     26 endfunction
     27 
     28 reg [A_W-1:0] wr_cnt;
     29 wire add_wr_cnt,end_wr_cnt;
     30 reg wr_flag;
     31 reg [A_W-1:0] rd_cnt;
     32 wire add_rd_cnt,end_rd_cnt;
     33 reg rd_flag;
     34 wire [A_W+1-1:0] wr_ptr,rd_ptr;
     35 wire empty_o;
     36 reg empty_r,empty_r0,empty_r1;
     37 
     38 always@(posedge clk or negedge rst_n)begin
     39   if(~rst_n)begin
     40     wr_cnt <= 0;
     41   end
     42   else if(add_wr_cnt)begin
     43     if(end_wr_cnt)
     44       wr_cnt <= 0;
     45     else
     46       wr_cnt <= wr_cnt + 1'b1;
     47   end
     48 end
     49 
     50 assign add_wr_cnt =  wr_en & ~full;
     51 assign end_wr_cnt = add_wr_cnt && wr_cnt == MAX_CNT - 1;
     52 
     53 always@(posedge clk or negedge rst_n)begin
     54   if(~rst_n)begin
     55     wr_flag <= 0;
     56   end
     57   else if(end_wr_cnt)begin
     58     wr_flag <= ~wr_flag;
     59   end
     60 end
     61 
     62 always@(posedge clk or negedge rst_n)begin
     63   if(~rst_n)begin
     64     rd_cnt <= 0;
     65   end
     66   else if(add_rd_cnt)begin
     67     if(end_rd_cnt)
     68       rd_cnt <= 0;
     69     else
     70       rd_cnt <= rd_cnt + 1'b1;
     71   end
     72 end
     73 
     74 assign add_rd_cnt =  rd_en & ~empty;
     75 assign end_rd_cnt = add_rd_cnt && rd_cnt ==  MAX_CNT - 1;
     76 
     77 always@(posedge clk or negedge rst_n)begin
     78   if(~rst_n)begin
     79     rd_flag <= 0;
     80   end
     81   else if(end_rd_cnt)begin
     82     rd_flag <= ~rd_flag;
     83   end
     84 end
     85 
     86 assign wr_ptr = {wr_flag,wr_cnt};
     87 assign rd_ptr = {rd_flag,rd_cnt};
     88 
     89 assign wr_addr = wr_cnt;
     90 assign rd_addr = rd_cnt + MODE;
     91 
     92 assign empty_o = wr_ptr == rd_ptr;
     93 assign full = (abs(wr_ptr[A_W-1:0] - rd_ptr[A_W-1:0]) < 1) && (wr_ptr[A_W] != rd_ptr[A_W]);
     94 
     95 assign empty = (wr_ptr[A_W-1:0] > rd_ptr[A_W-1:0]) ? empty_r : empty_o;
     96 
     97 always@(posedge clk)begin
     98     empty_r0 <= empty_o;
     99     empty_r1 <= empty_r0;
    100     empty_r <= empty_r1;
    101 end
    102 
    103 endmodule
    fifo_ctrl.v

    同步FIFO顶层:

     1 `timescale 1ns/1ps
     2 module fifo_sync
     3 #(parameter D_W = 8,
     4             LOG_2_DEPTH = 8,//2^8 = 256
     5   parameter [0:0] MODE = 0
     6           )
     7 (
     8   input clk,
     9   input rst_n,
    10 
    11   input wr_en,
    12   input [D_W-1:0] wr_data,
    13   input rd_en,
    14   output [D_W-1:0] rd_data,
    15   output wr_full,
    16   output rd_empty
    17 );
    18 wire [LOG_2_DEPTH-1:0] wr_addr,rd_addr;
    19 
    20 dpram #(.D_W(D_W),
    21         .A_W(LOG_2_DEPTH))
    22 dpram
    23 (
    24 .clk  (clk),
    25 .rst_n (rst_n),
    26 .wr_en  (wr_en),
    27 .wr_data  (wr_data),
    28 .wr_addr  (wr_addr),
    29 .rd_en  (rd_en),
    30 .rd_addr  (rd_addr),
    31 .rd_data  (rd_data)
    32 );
    33 
    34 fifo_ctrl #(.A_W(LOG_2_DEPTH),
    35             .MODE(MODE))
    36 fifo_ctrl
    37 (
    38 .clk  (clk),
    39 .rst_n  (rst_n),
    40 .wr_addr (wr_addr),
    41 .rd_addr (rd_addr),
    42 .empty  (rd_empty),
    43 .full  (wr_full),
    44 .wr_en  (wr_en),
    45 .rd_en  (rd_en)
    46 );
    47 
    48 endmodule
    fifo_sync

      之前在使用FPGA做项目时,经常看到厂商提供的FIFO IP提供“首字跌落”模式,故在本设计中也提供了这个模式,即在读信号有效前便送出第一个写入的数据。另外,为提高代码的通用性,在设计中尽量使用parameter而不是固定数值作为信号位宽。

    三、SV搭建testbench

      一般来说使用verilog非综合子集也能编写testbench来验证设计的正确性,但当DUT较为复杂时就显得不够灵活。设计同步FIFO也是为了学习利用system verilog编写testbench的一些技巧。

      首先明确验证方案。同步FIFO无非就是读写操作,只要每次都能将写入的数据读出就认为设计无误。我们可以通过SV的约束性随机特性完成任意长度以及任意间隔的读写操作。数据较多时逐一比较数据困难,testbench也应有自动对比数据并统计错误的机制。

      采用OOP思想,设计descriptor transcation scorebord三个类,因此是随机产生读写操作的访问器,根据访问器信息的读写操作以及自动对比读写数据的计分板。SV语法非常灵活,各个类可以的方法不仅包括function,也支持task,这为时序操作带来了便利。还有一点较为重要的是,选择合适的数据类型。由于待写入数据长度不固定,使用动态数组比较恰当。而不断增加的读取数据信息,放置在队列中会有更高的效率。FIFO是否选择“首字跌落”模式,对读操作时序有直接影响,testbench中采用宏定义方式条件编译参数和读取采集逻辑。

      代码如下:

      1 `timescale 1ns/1ps
      2 `define VERDI
      3 //`define FW
      4 
      5 module testbench();
      6   
      7   parameter CYC = 20,
      8             RST_TIM = 2;
      9   parameter D_W = 8,
     10             LOG_2_DEPTH = 8;
     11 
     12   `ifdef FW
     13     parameter [0:0] MODE = 1'b1;//1'b1 1'b0
     14   `else
     15     parameter [0:0] MODE = 1'b0;
     16   `endif
     17   parameter MAX_LEN = 2**LOG_2_DEPTH;
     18 
     19   typedef int unsigned uint32;
     20   typedef enum {true,false} status_e;
     21   
     22   bit clk,rst_n;
     23   bit wr_en;
     24   bit [D_W-1:0] wr_data;
     25   bit rd_en;
     26   logic [D_W-1:0] rd_data;
     27   logic wr_full;
     28   logic rd_empty;
     29   reg rd_en_t;
     30 
     31   `ifdef VERDI
     32   initial begin
     33     $fsdbDumpfile("wave.fsdb");
     34     $fsdbDumpvars("+all");
     35   end
     36   `endif
     37 
     38   initial begin
     39     clk = 1;
     40     forever #(CYC/2.0) clk= ~clk;
     41   end
     42 
     43   initial begin
     44     rst_n = 1;
     45     #1;
     46     rst_n = 0;
     47     #(RST_TIM*CYC) rst_n = 1;
     48   end
     49 
     50   class Descriptor;
     51     rand bit [16-1:0] len_w,len_r,interval;
     52 
     53     constraint c {
     54                    len_w inside {[1:20]};
     55                    len_r inside {[0:20]};
     56                    interval inside {[2:6]};
     57                   }
     58     function new;
     59       $display("Created a object");
     60     endfunction
     61   endclass:Descriptor
     62 
     63   class Transcation;
     64     bit [D_W-1:0] data_packet[];
     65     static uint32 q_len[$];
     66     static uint32 q_rd_data[$];
     67     uint32 q_ref_data[$];
     68 
     69     Descriptor dp;
     70 
     71     function new();
     72       dp = new();
     73       assert(dp.randomize());
     74       q_len.push_back(dp.len_w);
     75     endfunction
     76 
     77     extern task wri_oper;
     78     extern task rd_oper;
     79     extern task wr_rd_operation;
     80     extern function void ref_gen(ref uint32 q_ref_data[$]);
     81    
     82   endclass:Transcation
     83 
     84   task Transcation::wri_oper;
     85     uint32 wr_num;
     86     $display("Write:%d",$size(tr.data_packet));
     87     @(posedge clk);
     88     #1;
     89     while(wr_num < dp.len_w)begin
     90       if(~wr_full)begin
     91         wr_en = 1;
     92         wr_data = tr.data_packet[wr_num];
     93         wr_num++;
     94       end
     95       else begin
     96         wr_en = 0;
     97         wr_data = tr.data_packet[wr_num];
     98       end
     99       #(CYC*1);
    100     end
    101     wr_en = 0;
    102   endtask
    103  
    104   task Transcation::rd_oper;
    105     uint32 rd_num;
    106     $display("Read: %d",dp.len_r);
    107     @(posedge clk);
    108     #1;
    109     #(dp.interval*CYC);
    110     while(rd_num < dp.len_r)begin
    111       if(~rd_empty)begin
    112         rd_en = 1;
    113         rd_num++;
    114       end
    115       else
    116         rd_en = 0;
    117       #(CYC*1);
    118     end
    119     rd_en = 0;
    120   endtask
    121 
    122   task Transcation::wr_rd_operation;
    123     tr.data_packet = new[dp.len_w];
    124     $display("len_w = %d, len_r = %d, inverval = %d",dp.len_w,dp.len_r,dp.interval);
    125     foreach(tr.data_packet[i])begin
    126       tr.data_packet[i] = i+1;
    127       //$display(tr.data_packet[i]);
    128     end
    129     fork
    130       wri_oper;
    131       rd_oper;
    132     join
    133   endtask
    134 
    135   function void Transcation::ref_gen(ref uint32 q_ref_data[$]);
    136     integer j;
    137     foreach(q_len[i])begin
    138       for(j=0;j<q_len[i];j++)begin
    139         q_ref_data = {q_ref_data,j+1};
    140       end
    141     end
    142   endfunction
    143 
    144   class Scoreboard;
    145     uint32 total_num,error_num = 0;
    146 
    147     function compare(ref uint32 q_data[$],ref uint32 q_ref[$]);
    148       uint32 comp_num;
    149       uint32 i;
    150       uint32 data_len,ref_len;
    151       status_e status;
    152       data_len = $size(q_data);
    153       ref_len = $size(q_ref);
    154       $display("The lengths of q_data and q_ref are %d,%d",$size(q_data),$size(q_ref));
    155       if(data_len >= ref_len)
    156         comp_num = ref_len;
    157       else
    158         comp_num = data_len;
    159       total_num = comp_num;
    160       for(i=0;i<comp_num;i++)begin
    161         if(q_data[i] != q_ref[i])begin
    162           error_num++;
    163           $display("The %dth data is different between the two!",i);
    164           status = false;
    165           return status;
    166         end
    167       end
    168       status = true;
    169       return status;
    170     endfunction
    171   endclass
    172 
    173    //Descriptor dp;
    174    Transcation tr;
    175    Scoreboard sb;
    176 
    177   //main
    178   initial begin
    179     //int status;
    180     status_e status;
    181     wr_en = 0;
    182     rd_en = 0;
    183     wr_data = 0;
    184     #1;
    185     #(2*CYC);
    186     repeat(2)begin
    187       tr = new();
    188       tr.wr_rd_operation;
    189       #(50*CYC);
    190     end
    191     #20;
    192     tr.ref_gen(tr.q_ref_data);
    193 
    194     //soreboard
    195     sb = new();
    196     status = sb.compare(tr.q_rd_data,tr.q_ref_data);
    197     if(status == true)
    198       $display("Simulation success!");
    199     else
    200       $display("Simulation filure!");
    201     $stop;
    202   end
    203  
    204   //save readed data
    205   initial begin
    206     forever begin
    207       @(posedge clk);
    208         `ifdef FW
    209           if(rd_en)
    210         `else
    211           if(rd_en_t)
    212         `endif
    213             tr.q_rd_data = {tr.q_rd_data,rd_data};
    214     end
    215   end
    216 
    217   always@(posedge clk)begin
    218     rd_en_t <= rd_en;
    219   end
    220 
    221   fifo_sync 
    222 #(.D_W(D_W),
    223   .LOG_2_DEPTH(8),//256
    224   .MODE(MODE)
    225   )uut
    226   (
    227   .clk  (clk),
    228   .rst_n  (rst_n),
    229   .wr_en  (wr_en),
    230   .wr_data  (wr_data),
    231   .rd_en  (rd_en),
    232   .rd_data  (rd_data),
    233   .wr_full  (wr_full),
    234   .rd_empty  (rd_empty)
    235   );
    236   
    237 endmodule:testbench
    testbench.sv

    四、VCS+Verdi工具使用

      不得不说大多EDA工具确实没有IT行业的开发工具友好,用起来着实费了一番功夫。VCS这一仿真工具有自己的GUI debug tool,但功能不够强大。这里我们使用Verdi来debug。在上一节的SV代码中有一段fsdb的代码是专门产生Verdi波形文件的。因SV本身并没有这两个system function,使用时需要指定两个库文件路径。笔者直接将冗长的命令和选项定义一个alias:(bash shell)

    alias vcs_verdi="vcs -full64 -sverilog -debug_all -P ${NOVAS_HOME}/share/PLI/VCS/linux64/novas.tab ${NOVAS_HOME}/share/PLI/VCS/linux64/pli.a +define+DUMPFSDB"

    .bashrc file:

       这个路径名好像必须是NOVAS_HOME,否则会报错,也是挺坑。利用上边的指令完成第一步代码编译,之后依次是执行仿真程序和调用Verdi GUI界面观察波形。命令依次是:

    ./simv

    verdi -sv -f filename -ssf wave.fsdb

      执行仿真后会产生testbench中指定的波形文件。第三步命令执行后verdi界面被打开。

       通过波形及执行仿真后的Log可以看出仿真通过,在读写FIFO过程中没有产生错误。

       这里分享一些使用verdi的基本技巧。

      观察指定信号波形:选中代码中变量,ctrl+w添加该变量到波形窗口。

      保存波形配置文件:在波形界面,按下shift+s保存.rc文件。

      调取存储的配置文件:点击r,选中存储的.rc文件并打开。

      笔者第一次利用SV采用OOP思想搭建testbench,也是首次使用VCS+Verdi工具链进行仿真调试。虽然设计验证都非常简单,但还是卡住了很多次。之后会尝试异步FIFO设计,以及基于UVM的可重用testbench编写。

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    附录

    [图文]同步FIFO - 百度文库 https://wenku.baidu.com/view/620e3934a32d7375a4178037.html

    linux下的EDA——VCS与Verdi仿真 - moon9999的博客 - CSDN博客 https://blog.csdn.net/moon9999/article/details/76615869

  • 相关阅读:
    图像相似度
    二维数组 问题 E: 计算鞍点
    Uva
    Uva
    Uva
    Uva
    Uva
    Uva
    Uva
    【转载】2015 Objective-C 三大新特性 | 干货
  • 原文地址:https://www.cnblogs.com/moluoqishi/p/11564367.html
Copyright © 2011-2022 走看看