SoC设计项目 —— AHB SRAM控制器的设计 & March C-算法内建自测试的实现

发布时间 2023-03-24 00:27:47作者: 赵思健

绪论

本项目用Verilog HDL语言设计了AHB总线上的SRAM控制器接口IP,SRAM存储器在AHB总线上作为AHB slave存在,该SRAM控制器具有以下特性:

  1. 支持单周期的SRAM读写操作

  2. 支持低功耗工作
    SRAM存储体由两个Bank组成,系统根据地址选中一块/多块Bank,未被选中的Bank将处于low-power standby模式以降低功耗

  3. 支持DFT功能
    DFT(Design for Test,可测性设计),指通过在芯片原始设计中插入各种用于提高芯片可测试性(包括可控制性和可观测性)的硬件逻辑,从而使芯片变得容易测试,大幅度节省芯片测试的成本。
    本项目中,DFT功能通过BIST(Build-in Self Test,内建自测试)实现,采用March C-作为检测算法

最后,在Vivado平台上对本项目进行了逻辑仿真与验证

1. SRAM数据读写功能的实现

1.1 顶层设计架构

下面给出本项目的顶层设计架构,其中sram_top为顶层模块,其下包含sram_interface模块以及SRAM_core两个子模块

sram_interface模块:本质是AHB总线上的slave接口,起到连接总线与SRAM存储体的作用,具体来说:

  1. 将HCLK,HRESETn,HTRANS,HBURST,HWRITE,HWDATA这些来自于AHB总线的信号转化为存储器接口信号
  2. 接收存储器8位读数据,并根据总线给出的地址,转化为32位HRDATA,然后返回给AHB总线

sram_core模块:包含两块32位SRAM存储体Bank,其中每块Bank包含4个8k×8的单端口SRAM,本项目中通过例化Vivado中的IP核生成,实际芯片生产应用中常通过Memory Compiler生成

sram_bist模块:使用SRAM读写功能时,可看做8k×8的单端口SRAM,当BIST功能被使能时,将会由sram_bist内部的内建自测试电路生成Pattern对SRAM进行DFT验证,本项目中Pattern基于March C-算法设计,具体将在本文的第二章中介绍。在第一章中,我们将每个sram_bist模块视为8k×8的单端口SRAM即可

在上图中标注出了模块的主要信号,其中红色、蓝色的信号分别代表了两个不同的数据通路

红色数据通路:正常使用SRAM读写功能时的信号,interface接收来自于AHB总线的信号,并将其转化为SRAM所需要的控制信号和写数据,然后再由interface将SRAM的读数据整理后返回AHB总线

蓝色数据通路:使用DFT功能时的信号。BIST_en = 1时,DFT功能被使能,此时红色信号全部被屏蔽。该功能用于芯片生产完毕之后,对每块芯片进行DFT测试以检测是否有生产故障,该数据通路对于SRAM的逻辑功能来说,属于冗余的部分,但是在实际芯片生产中却是必不可少的

在本章中,我们关注红色数据通路的电路设计,而DFT功能设计将在第二章中进行介绍

1.2 AHB SRAM读写时序

AHB总线时序

其中来自AHB总线的control信号包括HTRANS,HBURST,HWRITE

SRAM接口时序
写时序

读时序

读时序与写时序的区别主要在于SRAM_ADDR的处理上:

对于写操作,为了将地址与数据对齐,sram_interface模块会将总线上的地址与控制信号写入寄存器,

而对于读操作,注意SRAM的时钟为AHB总线时钟信号HCLK,因此为了实现总线上的单周期读出,会直接将地址送到SRAM端口。这样,在数据周期刚开始时,读数据就可以返回HRDATA,

注意,这样的设计具有一些局限性:由于SRAM端口上的读地址相比于写地址要滞后一个周期,因此当写操作的下一个周期切换为读操作时,会产生地址冲突

于是,SRAM控制器会将HREADY拉低一个周期,进行缓冲,下一个周期才会重新拉高HREADY并且返回相应的读数据,

在多个读操作连续执行/多个写操作连续执行时,则不会有这样的问题,可以AHB总线最高的速度进行SRAM读写访问,

由于在实际应用中,存储器访问一般不会频繁地在读与写之间切换,因此这样设计对于访问速度带来的代价是可以忽略掉的,

而这样设计的好处则,在于可以避免为SRAM引入额外的时钟源

1.3 RTL代码设计

在明确了AHB SRAM读写的设计需求和读写时序后,我们来看看具体的硬件电路如何设计:

sram_top

首先是顶层模块,主要内容是对两个子模块进行了例化,其中涉及到的信号均已经在架构图中标明,这里不再赘述

`timescale 1ns / 1ps
module sram_top (
  
  // AHB Signal
  input HCLK, 
  input HRESETn,
  input           HSEL  ,
  input  [1:0]    HTRANS,    
  input  [2:0]    HBURST,
  input  [2:0]    HSIZE ,
  input           HWRITE,
  input  [15:0]   HADDR ,
  input  [31:0]   HWDATA,
  output          HREADY,
  output [1:0]    HRESP ,
  output [31:0]   HRDATA,
  
  // DFT Signal
  input  BIST_en   ,     
  output BIST_done ,
  output BIST_fail
  
  );

  // Wires Between SRAM_interface and SRAM_core
  wire        SRAM_WEN_BANK0;
  wire        SRAM_WEN_BANK1;
  wire [12:0] SRAM_ADDR     ;
  wire [3:0]  SRAM_CSN_BANK0;  
  wire [3:0]  SRAM_CSN_BANK1;  
  wire [31:0] SRAM_WDATA    ;
  
  wire [7:0] SRAM0_q;
  wire [7:0] SRAM1_q;
  wire [7:0] SRAM2_q;
  wire [7:0] SRAM3_q;
  wire [7:0] SRAM4_q;
  wire [7:0] SRAM5_q;
  wire [7:0] SRAM6_q;
  wire [7:0] SRAM7_q;

  /*————————————————————————————————————————————————————————————————————————*\
  /                    SRAM Interface Instantiation                          \
  \*————————————————————————————————————————————————————————————————————————*/
  sram_interface u_interface(
    //---------------AHB SIGNAL--------------
            //in
    .iHCLK   (HCLK   ),
    .iHRESETn(HRESETn),
    .iHSEL   (HSEL   ),
    .iHBURST (HBURST ),
    .iHWRITE (HWRITE ),
    .iHTRANS (HTRANS ),
    .iHWDATA (HWDATA ),
    .iHADDR  (HADDR  ),
            //out
    .oHRESP  (HRESP  ),
    .oHREADY (HREADY ),
    .oHRDATA (HRDATA ),
    //--------------SRAM SIGNAL--------------
            //in
    .iSRAM0_q(SRAM0_q),
    .iSRAM1_q(SRAM1_q),
    .iSRAM2_q(SRAM2_q),
    .iSRAM3_q(SRAM3_q),
    .iSRAM4_q(SRAM4_q),
    .iSRAM5_q(SRAM5_q),
    .iSRAM6_q(SRAM6_q),
    .iSRAM7_q(SRAM7_q),
            //out
    .oSRAM_WEN_BANK0(SRAM_WEN_BANK0),
    .oSRAM_WEN_BANK1(SRAM_WEN_BANK1),
    .oSRAM_CSN_BANK0(SRAM_CSN_BANK0),
    .oSRAM_CSN_BANK1(SRAM_CSN_BANK1),
    .oSRAM_ADDR     (SRAM_ADDR),
    .oSRAM_WDATA    (SRAM_WDATA)  
  ); 
  /*————————————————————————————————————————————————————————————————————————*\
  /                        SRAM Core Instantiation                           \
  \*————————————————————————————————————————————————————————————————————————*/
  sram_core u_core(
    //-----------  From AHB  ------------
    .iHCLK          (HCLK   ),
    .iHRESETn       (HRESETn),  
    //--------- From Interface  ---------
    .iSRAM_WEN_BANK0 (SRAM_WEN_BANK0),
    .iSRAM_WEN_BANK1(SRAM_WEN_BANK1),
    .iSRAM_ADDR     (SRAM_ADDR     ),
    .iSRAM_CSN_BANK0(SRAM_CSN_BANK0),  
    .iSRAM_CSN_BANK1(SRAM_CSN_BANK1),  
    .iSRAM_WDATA    (SRAM_WDATA    ),
    //----------  To Interface  ---------
    .oSRAM0_q       (SRAM0_q),
    .oSRAM1_q       (SRAM1_q),
    .oSRAM2_q       (SRAM2_q),
    .oSRAM3_q       (SRAM3_q),
    .oSRAM4_q       (SRAM4_q),
    .oSRAM5_q       (SRAM5_q),
    .oSRAM6_q       (SRAM6_q),
    .oSRAM7_q       (SRAM7_q),
    //--------------  DFT  --------------
    .iBIST_en       (BIST_en  ),
    .oBIST_done     (BIST_done),
    .oBIST_fail     (BIST_fail)
  );

endmodule

sram_interface

其次是sram_interface模块,该模块是本项目中重点模块之一,负责寄存AHB总线控制信号、AHB总线地址信号,

然后根据AHB信号对SRAM存储体进行读写访问,

那么SRAM的接口信号是如何生成的呢?

CLK:直接采用AHB总线时钟HCLK作为存储器时钟

CSN:片选,当地址对应BANK0时,sram0、sram1、sram2、sram3被选中,而sram3~sram7则对应BANK1
具体来说,当总线地址HADDR的值在0x0000—0x7FFF之间时地址指向BANK0,而在0x8000—0xFFFF之间时地址指向BANK1

WEN:写使能,当HWRITE = 1时,总线对SRAM发起write操作,WEN将被拉高;
当HWRITE = 0时,总线对SRAM发起read操作,WEN将被拉低,以保证读地址的数据不会被改写

ADDR:地址。根据AHB SRAM读写时序中所介绍,
当执行SRAM写操作时,interface模块会将存储器地址通过寄存器打一拍,然后在下一个周期和写数据一起送到相应的存储器端口上
而执行SRAM读操作时,我们为了实现单周期读写,会直接将地址送到存储器端口。这样,就可以在下个时钟上升沿顺利地拿到存储器返回的读数据,并送回AHB总线

WDATA:SRAM写数据,来自于总线上的HWDATA[31:0],sram_interface将HWDATA按照下图顺序作为每个8位SRAM的写数据

q:SRAM读数据,每个被选中的sram_bist将返回一个8位数据,sram_interface会将每个Bank中的4个单Byte读数据,组合为一个完整的32位读数据,返回到总线上的HRDATA

HWDATA与SRAM_WDATA的对应关系,
HRDATA与SRAM_q的对应关系,如下图所示:

sram_interface模块的RTL代码如下:

module  sram_interface (
    //---------------AHB SIGNAL--------------
    //in
    input        iHCLK   ,
    input        iHRESETn,
    input        iHSEL   ,
    input        iHWRITE ,
    input [2:0]  iHBURST ,
    input [1:0]  iHTRANS ,
    input [31:0] iHWDATA ,
    input [15:0] iHADDR  ,
    //out 
    output [1:0]  oHRESP  ,
    output        oHREADY ,
    output [31:0] oHRDATA ,

    //--------------SRAM SIGNAL--------------
    //in
    input [7:0] iSRAM0_q,
    input [7:0] iSRAM1_q,
    input [7:0] iSRAM2_q,
    input [7:0] iSRAM3_q,
    input [7:0] iSRAM4_q,
    input [7:0] iSRAM5_q,
    input [7:0] iSRAM6_q,
    input [7:0] iSRAM7_q,
    //out
    output        oSRAM_WEN_BANK0,
    output        oSRAM_WEN_BANK1,
    output [3:0]  oSRAM_CSN_BANK0,
    output [3:0]  oSRAM_CSN_BANK1,
    output [12:0] oSRAM_ADDR,
    output [31:0] oSRAM_WDATA    
    
  ); 

  /*————————————————————————————————————————————————————————————————————————*\
  /                         Register for AHB Signal                          \
  \*————————————————————————————————————————————————————————————————————————*/
  reg         iHSEL_r   ;
  reg         iHWRITE_r ;
  reg         iHWRITE_2r;
  reg  [2:0]  iHBURST_r ;
  reg  [1:0]  iHTRANS_r ;
  reg  [31:0] iHWDATA_r ;
  reg  [15:0] iHADDR_r  ;
  reg  [15:0] iHADDR_2r ;
  
  always@( posedge iHCLK)  begin
    if(!iHRESETn) begin
      iHSEL_r    <= 1'b0; 
      iHWRITE_r  <= 1'b0; 
      iHWRITE_2r <= 1'b0;
      iHBURST_r  <= 3'b0; 
      iHTRANS_r  <= 2'b0; 
      iHWDATA_r  <= 32'b0; 
      iHADDR_r   <= 16'b0; 
      iHADDR_2r  <= 16'b0;
    end
    else begin
      iHSEL_r    <= iHSEL; 
      iHWRITE_r  <= iHWRITE; 
      iHWRITE_2r <= iHWRITE_r; 
      iHBURST_r  <= iHBURST; 
      iHTRANS_r  <= iHTRANS; 
      iHWDATA_r  <= iHWDATA; 
      iHADDR_r   <= iHADDR; 
      iHADDR_2r  <= iHADDR_r;
    end
  end

  /*————————————————————————————————————————————————————————————————————————*\
  /                    AHB BUS  →  Interface  →  SRAM Core                   \
  \*————————————————————————————————————————————————————————————————————————*/
  // SRAM Write Enable
  assign oSRAM_WEN_BANK0  = ( iHWRITE_r == 1'b1 &&  iHADDR_r[15] == 1'b0) ? 1'b1 : 1'b0;
  assign oSRAM_WEN_BANK1  = ( iHWRITE_r == 1'b1 &&  iHADDR_r[15] == 1'b1) ? 1'b1 : 1'b0;

  // SRAM Bank CSN            select for read ↓                    select for write ↓
  assign oSRAM_CSN_BANK0 =  ( iHADDR_r[15] == 1'b0    ||   (iHADDR[15] == 1'b0 && iHWRITE == 1'b0) ) ? 4'b1111 : 4'b0000;
  assign oSRAM_CSN_BANK1 =  ( iHADDR_r[15] == 1'b1    ||   (iHADDR[15] == 1'b1 && iHWRITE == 1'b0) ) ? 4'b1111 : 4'b0000;

  // SRAM Addr
  wire [12:0] SRAM_WRITE_ADDR;
  wire [12:0] SRAM_READ_ADDR; 
  assign SRAM_WRITE_ADDR      = iHADDR_r[14:2];   // WRITE:addr have to wait a T , sent together with data to SRAM_CORE
  assign SRAM_READ_ADDR       = iHADDR  [14:2];   // READ :addr send to MEM at once 
  assign oSRAM_ADDR = (iHWRITE_r == 1'b1) ? SRAM_WRITE_ADDR : SRAM_READ_ADDR;

  // SRAM Write Data
  assign oSRAM_WDATA = iHWDATA;

  /*————————————————————————————————————————————————————————————————————————*\
  /                    AHB BUS  ←  Interface  ←  SRAM Core                   \
  \*————————————————————————————————————————————————————————————————————————*/
  // response to AHB MASTER
  assign oHREADY = (iHSEL_r == 1'b1 && (iHWRITE_r == 1'b1 || iHWRITE_2r == 1'b0)) ? 1'b1 : 1'b0 ;
  assign oHRESP  = (iHSEL_r == 1'b1) ? 2'b00 : 2'b00; //OKAY = 2'b00
  
  // sram read data
  assign oHRDATA = (iHSEL_r == 1'b1 && iHWRITE_r == 1'b0 && iHADDR_r[15] == 1'b0) ? {iSRAM3_q, iSRAM2_q, iSRAM1_q, iSRAM0_q}: 
                   (iHSEL_r == 1'b1 && iHWRITE_r == 1'b0 && iHADDR_r[15] == 1'b1) ? {iSRAM7_q, iSRAM6_q, iSRAM5_q, iSRAM4_q}: 
                   32'bz;

endmodule

sram_core

接下来是顶层模块下的sram_core,主要内容是将sram_bist模块进行了8次例化,

因此,sram_core实际上是将这8个SRAM拼成了一个16k×32的SRAM,

sram_core的地址共15位,为0000~0xFFFFF,

其中,Bank0对应0x00000x7FFFF;Bank1对应0x80000xFFFFF,

而每个sram_bist端口上的地址为sram_core上的地址右移两位得到,共13位,地址范围为0x0000~0x1FFF,

除此之外,sram_core将每个8k×8 SRAM的内建自测试的输出结果BIST_done_x,BIST_fail_x(x=0~7)进行了逻辑与/或以得到整块sram_core存储体的DFT测试结果,

在执行SRAM数据读写功能的时候,sram_bist可以看做8k×8的单端口SRAM。

sram_core模块的RTL代码如下:

module sram_core (

  // From AHB 
  input iHCLK   , 
  input iHRESETn,

  // From sram_interface
  input        iSRAM_WEN_BANK0,
  input        iSRAM_WEN_BANK1,
  input [12:0] iSRAM_ADDR     ,
  input [3:0]  iSRAM_CSN_BANK0,  
  input [3:0]  iSRAM_CSN_BANK1,  
  input [31:0] iSRAM_WDATA    ,
  
  // To sram_interface 
  output [7:0] oSRAM0_q,
  output [7:0] oSRAM1_q,
  output [7:0] oSRAM2_q,
  output [7:0] oSRAM3_q,
  output [7:0] oSRAM4_q,
  output [7:0] oSRAM5_q,
  output [7:0] oSRAM6_q,
  output [7:0] oSRAM7_q,
  
  // BIST Signals
  input   iBIST_en,
  output  oBIST_done,
  output  oBIST_fail

);

  /*————————————————————————————————————————————————————————————————————————*\
  /                          BIST Ouput Logic                                \
  \*————————————————————————————————————————————————————————————————————————*/
  wire BIST_done_0, BIST_done_1, BIST_done_2, BIST_done_3;
  wire BIST_done_4, BIST_done_5, BIST_done_6, BIST_done_7;
  wire BIST_fail_0, BIST_fail_1, BIST_fail_2, BIST_fail_3;
  wire BIST_fail_4, BIST_fail_5, BIST_fail_6, BIST_fail_7;

  assign oBIST_done = BIST_done_0 && BIST_done_1 && BIST_done_2 && BIST_done_3
                   && BIST_done_4 && BIST_done_5 && BIST_done_6 && BIST_done_7; // done if every sram_bist dones

  assign oBIST_fail = BIST_done_0 || BIST_done_1 || BIST_done_2 || BIST_done_3
                   || BIST_done_4 || BIST_done_5 || BIST_done_6 || BIST_done_7; // fail if any sram_bist fails

  /*————————————————————————————————————————————————————————————————————————*\
  /                        BANK 0 Instantiation                              \
  \*————————————————————————————————————————————————————————————————————————*/
  sram_bist u_bank0_sram0 (
    // Function Mode IO
    .iSRAM_CLK  (iHCLK             ),
    .iSRAM_CSN  (iSRAM_CSN_BANK0[0]),
    .iSRAM_WEN  (iSRAM_WEN_BANK0   ),
    .iSRAM_ADDR (iSRAM_ADDR        ), //13 bits SRAM ADDR
    .iSRAM_WDATA(iSRAM_WDATA[7:0]  ), 
    .oSRAM_RDATA(oSRAM0_q          ),
    // Test Mode IO
    .iBIST_en   (iBIST_en          ),     
    .oBIST_done (BIST_done_0       ),
    .oBIST_fail (BIST_fail_0       )
  );
                                    sram_bist u_bank0_sram1 (
                                      // Function Mode IO
                                      .iSRAM_CLK  (iHCLK             ),
                                      .iSRAM_CSN  (iSRAM_CSN_BANK0[1]),
                                      .iSRAM_WEN  (iSRAM_WEN_BANK0   ),
                                      .iSRAM_ADDR (iSRAM_ADDR        ), //13 bits SRAM ADDR
                                      .iSRAM_WDATA(iSRAM_WDATA[15:8] ), 
                                      .oSRAM_RDATA(oSRAM1_q          ),
                                      // Test Mode IO
                                      .iBIST_en   (iBIST_en          ),     
                                      .oBIST_done (BIST_done_1       ),
                                      .oBIST_fail (BIST_fail_1       )
                                    );

  sram_bist u_bank0_sram2 (
    // Function Mode IO
    .iSRAM_CLK  (iHCLK             ),
    .iSRAM_CSN  (iSRAM_CSN_BANK0[2]),
    .iSRAM_WEN  (iSRAM_WEN_BANK0   ),
    .iSRAM_ADDR (iSRAM_ADDR        ), //13 bits SRAM ADDR
    .iSRAM_WDATA(iSRAM_WDATA[23:16]), 
    .oSRAM_RDATA(oSRAM2_q          ),
    // Test Mode IO
    .iBIST_en   (iBIST_en          ),     
    .oBIST_done (BIST_done_2       ),
    .oBIST_fail (BIST_fail_2       )
  );
                                    sram_bist u_bank0_sram3 (
                                      // Function Mode IO
                                      .iSRAM_CLK  (iHCLK             ),
                                      .iSRAM_CSN  (iSRAM_CSN_BANK0[3]),
                                      .iSRAM_WEN  (iSRAM_WEN_BANK0   ),
                                      .iSRAM_ADDR (iSRAM_ADDR        ), //13 bits SRAM ADDR
                                      .iSRAM_WDATA(iSRAM_WDATA[31:24]), 
                                      .oSRAM_RDATA(oSRAM3_q          ),
                                      // Test Mode IO
                                      .iBIST_en   (iBIST_en          ),     
                                      .oBIST_done (BIST_done_3       ),
                                      .oBIST_fail (BIST_fail_3       )
                                    );
  
  /*————————————————————————————————————————————————————————————————————————*\
  /                        BANK 1 Instantiation                              \
  \*————————————————————————————————————————————————————————————————————————*/
  sram_bist u_bank1_sram4 (
    // Function Mode IO
    .iSRAM_CLK  (iHCLK             ),
    .iSRAM_CSN  (iSRAM_CSN_BANK1[0]),
    .iSRAM_WEN  (iSRAM_WEN_BANK1   ),
    .iSRAM_ADDR (iSRAM_ADDR        ), //13 bits SRAM ADDR
    .iSRAM_WDATA(iSRAM_WDATA[7:0]  ), 
    .oSRAM_RDATA(oSRAM4_q          ),
    // Test Mode IO
    .iBIST_en   (iBIST_en          ),     
    .oBIST_done (BIST_done_4       ),
    .oBIST_fail (BIST_fail_4       )
  );
                                    sram_bist u_bank1_sram5 (
                                      // Function Mode IO
                                      .iSRAM_CLK  (iHCLK             ),
                                      .iSRAM_CSN  (iSRAM_CSN_BANK1[1]),
                                      .iSRAM_WEN  (iSRAM_WEN_BANK1   ),
                                      .iSRAM_ADDR (iSRAM_ADDR        ), //13 bits SRAM ADDR
                                      .iSRAM_WDATA(iSRAM_WDATA[15:8] ), 
                                      .oSRAM_RDATA(oSRAM5_q          ),
                                      // Test Mode IO
                                      .iBIST_en   (iBIST_en          ),     
                                      .oBIST_done (BIST_done_5       ),
                                      .oBIST_fail (BIST_fail_5       )
                                    );

  sram_bist u_bank1_sram6 (
    // Function Mode IO
    .iSRAM_CLK  (iHCLK             ),
    .iSRAM_CSN  (iSRAM_CSN_BANK1[2]),
    .iSRAM_WEN  (iSRAM_WEN_BANK1   ),
    .iSRAM_ADDR (iSRAM_ADDR        ), //13 bits SRAM ADDR
    .iSRAM_WDATA(iSRAM_WDATA[23:16]), 
    .oSRAM_RDATA(oSRAM6_q          ),
    // Test Mode IO
    .iBIST_en   (iBIST_en          ),     
    .oBIST_done (BIST_done_6       ),
    .oBIST_fail (BIST_fail_6       )
  );
                                    sram_bist u_bank1_sram7 (
                                      // Function Mode IO
                                      .iSRAM_CLK  (iHCLK             ),
                                      .iSRAM_CSN  (iSRAM_CSN_BANK1[3]),
                                      .iSRAM_WEN  (iSRAM_WEN_BANK1   ),
                                      .iSRAM_ADDR (iSRAM_ADDR        ), //13 bits SRAM ADDR
                                      .iSRAM_WDATA(iSRAM_WDATA[31:24]), 
                                      .oSRAM_RDATA(oSRAM7_q          ),
                                      // Test Mode IO
                                      .iBIST_en   (iBIST_en          ),     
                                      .oBIST_done (BIST_done_7       ),
                                      .oBIST_fail (BIST_fail_7       )
                                    );

endmodule

1.4 SRAM读写仿真验证

在完成RTL设计后,我们编写了Testbench,
并在Vivado平台上进行了简单的读写仿真验证,波形如下:

分析一下Testbench具体对SRAM发起了什么操作:

首先,T1-T7进行了六次写操作,将6个数据依次写入SRAM的0x0000,0x0001,0x0002,0x8000,0x8001,0x8002六个地址当中

其中前三个地址对应Bank0,后三个地址对应Bank1,

因此在T2-T4期间 SRAM_CSN_BANK0 和 SRAM_WEN_BANK0 被拉高,

在T5-T7期间 SRAM_CSN_BANK1 和 SRAM_WEN_BANK1 被拉高,

从上图中可以看出,T7除了是Data 6的写数据周期,也是Data 1 读地址周期,

但是由于SRAM端口上,该周期需要执行写Data 6的操作。

于是发生了地址冲突,无法在该周期同时进行读Data 1

因此,在T8并没有返回Data 1的读数据,HREADY被拉低,

随后,在T9-T14,总线上HRDATA依次拿到了六个SRAM读数据,读出的data与T1-T7写入的data完全一致,证明了以上SRAM控制器的设计逻辑是正确的

2. 基于March C-算法的DFT功能

2.1 BIST架构

在设计中,SRAM读写模式和DFT模式的选择通过2选1选择器实现,当DFT模式的使能信号BIST_en = 1时,来自于sram_interface的所有信号逻辑将被忽略,

SRAM端口上的输入信号将全部来自于sram_bist内部的BIST控制电路生成的Pattern,

对于March C-算法,BIST控制电路会对SRAM每个地址进行“写 → 读 → 比较”的操作,

若所有的读数据结果与写入的数据是一致的,BIST_done最终将被拉高,说明该电路生产过程中没有出现生产故障,

反之,如果比较发现有错误,BIST_fail最终将被拉高,该块芯片将被报废,

在模块内加入DFT测试电路,虽然会增加系统的数字面积,但同时也极大地降低了产品在测试环节所花费的开销成本

2.2 SRAM常见故障

首先,介绍一些常见的存储器(比如SRAM)故障模型:

固定型故障(Stuck-At Faults,SAF):
存储单元中的值固定为0/1(简记为SA0/SA1),无法发生改变。固定型故障可以通过对待测单元写入0再读出0,然后写入1再读出1来进行检测。

跳变故障(Transition Faults,TF):
存储单元中的值无法从0跳变到1(简记为TF(0→1)),或者从1跳变到0(简记为TF(1→0)),需要通过写入1到0的跳变再读出0,然后写入0到1的跳变再读出1来进行检测

耦合故障(Coupling Faults,CF):
一个存储单元的值发生改变,导致另一个存储单元的值发生改变,可以通过先升序对所有存储单元进行写读操作,然后再降序对所有存储单元进行写读操作的方法进行故障检测

2.3 March C-算法

March C算法是目前应用最为广泛的MBIST(Memory Built-In-Self-Test,存储器内建自测试)算法,

March C算法对上文提到的SAF故障,TF故障,CF故障的故障覆盖率均达到100%,

March C算法的具体检测流程如下:

  1. 从最低地址开始,在整个存储器中依次写入0(升序)

  2. 读出最低地址,结果应为0,然后把1写入该存储单元。完成后地址+1,再次执行该操作,直至对整个存储器执行该操作(升序)

  3. 读出最高地址,结果应为1,然后把0写入该存储单元,再次读该单元,结果应为0。完成后地址-1,再次执行该操作,直至对整个存储器执行该操作(降序)

  4. 读出最高地址,结果应为0,然后把1写入该存储单元。完成后地址-1,再次执行该操作,直至对整个存储器执行该操作(降序)

  5. 读出最低地址,结果应为1,然后把0写入该存储单元,再次读该单元,结果应为0。完成后地址+1,再次执行该操作,直至对整个存储器执行该操作(升序)

由于步骤4,步骤5中,加粗字体所描述的操作实际上是重复的,

因此后来有了改进的March C-算法, 将步骤3中的加粗部分删除,如下:

图中,March C-所执行的全套操作被分成了5个部分,也就是MARCH_0~MARCH_ 4

这也是BIST功能的RTL设计中March C-状态机所用到的5个状态的名称

2.4 Verilog实现March C-算法的BIST

本项目中,内建自测试逻辑电路位于每个sram_bist模块中,

BIST_en作为顶层模块的BIST使能信号,被直接接到每块bist的BIST_en输入使能端口,

March C-算法的读写操作,是由一个状态机控制的,状态机示意图如下:

下面让我们来看看sram_bist模块的RTL代码:

module sram_bist #(

    //---------------    MARCH C-  --------------------//
    //   STATE            ACTION           DIRECTION
    // MARCH 0            write 0              ↑  
    // MARCH 1         read 0, write 1         ↑
    // MARCH 2         read 1, write 0         ↓
    // MARCH 3         read 0, write 1         ↓
    // MARCH 4         read 1, write 0         ↑        
    //-------------------------------------------------//
    
            // TEST_state parameters //            
        parameter MARCH_0 = 3'b000,        // 0
        parameter MARCH_1 = 3'b001,        // 1
        parameter MARCH_2 = 3'b010,        // 2
        parameter MARCH_3 = 3'b011,        // 3
        parameter MARCH_4 = 3'b100,        // 4
        parameter MARCH_finished = 3'b101, // 5

            // TEST_action parameters //
              
        parameter WRITE_0 = 2'b00,
        parameter READ_0  = 2'b01,
        parameter WRITE_1 = 2'b10,
        parameter READ_1  = 2'b11,

            // TEST_compare_result parameters //
                      
        parameter COMPARE_RIGHT  = 1'b1,
        parameter COMPARE_ERROR  = 1'b0

)(

    // Function Mode IO
    input         iSRAM_CLK  ,
    input         iSRAM_CSN  ,
    input         iSRAM_WEN  ,
    input  [12:0] iSRAM_ADDR ,
    input  [7:0]  iSRAM_WDATA,    
    output [7:0]  oSRAM_RDATA,

    // Test Mode IO
    input  iBIST_en   ,     
    output oBIST_done ,
    output oBIST_fail
);

    /*————————————————————————————————————————————————————————————————————————*\
    /                                                                          \
    /                     SRAM Normal Function Mode                            \
    /                                                                          \
    \*————————————————————————————————————————————————————————————————————————*/

    // wire connected to sram's port
    wire SRAM_CLK  ;  
    wire SRAM_CSN  ;  
    wire SRAM_WEN  ;  
    wire [12:0] SRAM_ADDR ;  
    wire [7:0]  SRAM_WDATA;  
    wire [7:0]  SRAM_RDATA;  
    
    // TEST-FUN MUX
    assign SRAM_ADDR  = (iBIST_en == 1'b1) ? TEST_ADDR   :
                        (iBIST_en == 1'b0) ? iSRAM_ADDR  : 13'bz;
    
    assign SRAM_CSN   = (iBIST_en == 1'b1) ? 1'b1        :
                        (iBIST_en == 1'b0) ? iSRAM_CSN   : 1'bz;  
    
    
    assign SRAM_WDATA = (iBIST_en == 1'b1) ? TEST_WDATA     :
                        (iBIST_en == 1'b0) ? iSRAM_WDATA    : 8'bz;  

    assign SRAM_WEN   = (iBIST_en == 1'b1) ? TEST_SRAM_WEN  :
                        (iBIST_en == 1'b0) ? iSRAM_WEN      : 1'bz;                    
    
    assign oSRAM_RDATA = SRAM_RDATA;
       
    // IP instantiation
    RAM_8K_8 u_bt_sram (
        .clka  (iSRAM_CLK   ),// HCLK → SRAM_CLK
        .ena   (SRAM_CSN    ),// csn → ena
        .wea   (SRAM_WEN    ),// 
        .addra (SRAM_ADDR   ),// unite addr
        .dina  (SRAM_WDATA  ),// input data
        .douta (SRAM_RDATA  ) // output data
    );
    
    /*————————————————————————————————————————————————————————————————————————*\
    /                                                                          \
    /                   BIST (Build-in Self Test)                            \
    /                                                                          \
    \*————————————————————————————————————————————————————————————————————————*/

    // BIST CLOCK Generation   
    wire   BIST_CLK;
    assign BIST_CLK = ( iBIST_en == 1'b1) ? iSRAM_CLK : 1'b0;

    // BIST RESET Generation
    reg  iBIST_en_r;
    reg  iBIST_en_2r;
    wire TEST_RESET;    
    always @( posedge BIST_CLK) begin
        if(iBIST_en && iBIST_en_r) begin
            iBIST_en_r  <= 1'b1;
            iBIST_en_2r <= 1'b1;
        end
        else if ( iBIST_en ) begin
            iBIST_en_r  <= 1'b1;
            iBIST_en_2r <= 1'b0;
        end
        else begin
            iBIST_en_r  <= 1'b0;
            iBIST_en_2r <= 1'b0;
        end
    end
    assign TEST_RESET = iBIST_en_2r ^ iBIST_en;


    // BIST Controller (March C)
    reg        TEST_flag_finish;
    reg [2:0]  TEST_state;
    reg [1:0]  TEST_action;
    reg        TEST_SRAM_WEN;
    reg [31:0] TEST_ADDR;
    reg [7:0]  TEST_WDATA;    
    
    always@( posedge BIST_CLK ) begin 
    if ( TEST_RESET ) begin                                    //Synchronous Reset
        TEST_flag_finish <= 1'b0;
        TEST_state       <= MARCH_0;
        TEST_action      <= WRITE_0;
        TEST_SRAM_WEN    <= 1'b1;
        TEST_ADDR        <= 13'h0000;
        TEST_WDATA       <= 8'b0000_0000;
    end
    else begin       
        case ( TEST_state )
            //---------------    MARCH 0  ↑   -----------------//
            MARCH_0 : begin                  
                if ( TEST_ADDR == 13'h1FFF ) begin
                    TEST_state       <= MARCH_1;               // 
                    TEST_action      <= READ_0;                // 
                    TEST_SRAM_WEN    <= 1'b0;                  // jump to MARCH_1 to read 0
                    TEST_ADDR        <= 13'h0000;              // 
                    TEST_WDATA       <= 8'bz;                  // 
                end 
                else if ( TEST_action == WRITE_0 ) begin
                    TEST_state       <= TEST_state;
                    TEST_action      <= TEST_action;
                    TEST_SRAM_WEN    <= 1'b1;
                    TEST_ADDR        <= TEST_ADDR + 1'b1;     // addr ++   
                    TEST_WDATA       <= 8'b0000_0000;         // write 0
                end          
            end 

            //---------------    MARCH 1  ↑   ----------------//
            MARCH_1 : begin
                if ( TEST_action == WRITE_1 && TEST_ADDR == 13'h1FFF ) begin
                    TEST_state       <= MARCH_2;              // 
                    TEST_action      <= READ_1;               //
                    TEST_SRAM_WEN    <= 1'b0;                 // jump to MARCH_2 to read 1
                    TEST_ADDR        <= 13'h1FFF;             // 
                    TEST_WDATA       <= 8'bz;                 //
                end 
                else if ( TEST_action == READ_0 ) begin
                    TEST_state       <= TEST_state;
                    TEST_action      <= WRITE_1;              // write 1 in next clk
                    TEST_SRAM_WEN    <= 1'b1;                 // write 1 in next clk
                    TEST_ADDR        <= TEST_ADDR;            // addr kept for write 1  
                    TEST_WDATA       <= 8'b1111_1111;         // write 1 in next clk
                end 
                else if ( TEST_action == WRITE_1 )begin
                    TEST_state       <= TEST_state;
                    TEST_action      <= READ_0;               // read 0 in next clk 
                    TEST_SRAM_WEN    <= 1'b0;                 // read 0 in next clk 
                    TEST_ADDR        <= TEST_ADDR + 1'b1;     // addr++ 
                    TEST_WDATA       <= 8'bz;                 // read 0 in next clk 
                end
            end

            //---------------    MARCH 2  ↓   ----------------//
            MARCH_2 : begin
                if ( TEST_action == WRITE_0 && TEST_ADDR == 13'h0000 ) begin
                    TEST_state       <= MARCH_3;              //                     
                    TEST_action      <= READ_0;               //                             
                    TEST_SRAM_WEN    <= 1'b0;                 // jump to MARCH_3 to read 0  
                    TEST_ADDR        <= 13'h1FFF;             //      
                    TEST_WDATA       <= 8'bz;                 //                              
                end                                            
                else if ( TEST_action == READ_1 ) begin             
                    TEST_state       <= TEST_state;               
                    TEST_action      <= WRITE_0;              // write 0 in next clk               
                    TEST_SRAM_WEN    <= 1'b1;                 // write 0 in next clk        
                    TEST_ADDR        <= TEST_ADDR;            // addr kept for write 0         
                    TEST_WDATA       <= 8'b0000_0000;         // write 0 in next clk                                  
                end                                             
                else if ( TEST_action == WRITE_0 )begin       //      
                    TEST_state       <= TEST_state;           //    
                    TEST_action      <= READ_1;               // read 1 in next clk             
                    TEST_SRAM_WEN    <= 1'b0;                 // read 1 in next clk   
                    TEST_ADDR        <= TEST_ADDR - 1'b1;     // addr-- 
                    TEST_WDATA       <= 8'bz;                 // read 1 in next clk
                end    
            end

            //---------------    MARCH 3  ↓   ----------------//
            MARCH_3 : begin
                if ( TEST_action == WRITE_1 && TEST_ADDR == 13'h0000 ) begin
                    TEST_state       <= MARCH_4;
                    TEST_action      <= READ_1;               // jump to MARCH_4 to read 1
                    TEST_SRAM_WEN    <= 1'b0;
                    TEST_ADDR        <= 13'h0000;
                    TEST_WDATA       <= 8'bz;
                end 
                else if ( TEST_action == READ_0 ) begin
                    TEST_state       <= TEST_state;           // write 1 in next clk
                    TEST_action      <= WRITE_1;              // write 1 in next clk
                    TEST_SRAM_WEN    <= 1'b1;                 // write 1 in next clk
                    TEST_ADDR        <= TEST_ADDR;            // addr kept for write 1   
                    TEST_WDATA       <= 8'b1111_1111;         // write 1 in next clk
                end 
                else if ( TEST_action == WRITE_1 )begin
                    TEST_state       <= TEST_state;           // read 0 in next clk
                    TEST_action      <= READ_0;               // read 0 in next clk
                    TEST_SRAM_WEN    <= 1'b0;                 // read 0 in next clk
                    TEST_ADDR        <= TEST_ADDR - 1'b1;     // addr-- 
                    TEST_WDATA       <= 8'bz;                 // read 0 in next clk
                end
            end

            //---------------    MARCH 4  ↑   ----------------//
            MARCH_4 : begin
                if ( TEST_action == READ_0 && TEST_ADDR == 13'h1FFF ) begin
                    TEST_flag_finish <= 1'b1;
                    TEST_state       <= MARCH_finished;
                    TEST_action      <= 2'bz;          
                    TEST_SRAM_WEN    <= 1'bz;
                    TEST_ADDR        <= 13'hz;
                    TEST_WDATA       <= 8'bz;
                end 
                else if ( TEST_action == READ_1 ) begin
                    TEST_state       <= TEST_state;     
                    TEST_action      <= WRITE_0;              // write 0 in next clk
                    TEST_SRAM_WEN    <= 1'b1;                 // write 0 in next clk
                    TEST_ADDR        <= TEST_ADDR;            // addr kept for write 0   
                    TEST_WDATA       <= 8'b0000_0000;         // write 0 in next clk
                end 
                else if ( TEST_action == WRITE_0 )begin
                    TEST_state       <= TEST_state; 
                    TEST_action      <= READ_0;               // read 0 in next clk
                    TEST_SRAM_WEN    <= 1'b0;                 // read 0 in next clk
                    TEST_ADDR        <= TEST_ADDR;            // addr kept for read 0 
                    TEST_WDATA       <= 8'bz;                 // read 0 in next clk
                end
                else if ( TEST_action == READ_0 )begin
                    TEST_state       <= TEST_state;
                    TEST_action      <= READ_1;               // read 1 in next clk
                    TEST_SRAM_WEN    <= 1'b0;                 // read 1 in next clk
                    TEST_ADDR        <= TEST_ADDR + 1'b1;     // addr++ 
                    TEST_WDATA       <= 8'bz;                 // read 1 in next clk
                end
            end
            MARCH_finished : begin
                TEST_flag_finish <= 1'b1;
                TEST_state       <= TEST_state;
            end
            default: begin
                TEST_flag_finish <= 1'b0;
                TEST_state       <= MARCH_0;
                TEST_action      <= WRITE_0;
                TEST_SRAM_WEN    <= 1'b1;
                TEST_ADDR        <= 13'h0000;
                TEST_WDATA       <= 8'b0000_0000;
            end 
        endcase        
    end
    end

    // Compare SRAM_RDATA with Ideal Result 
    reg TEST_compare_result;

    always@( posedge BIST_CLK ) begin     

        // Reset the Comparsion Result
        if ( TEST_RESET ) begin
            TEST_compare_result <= COMPARE_RIGHT; // COMPARE_RIGHT = 1'b1
        end

        // Read 0 in March_1 
        else if ( TEST_state == MARCH_1 && TEST_action == WRITE_1 ) begin
            if ( SRAM_RDATA == 8'b0000_0000)   TEST_compare_result <= TEST_compare_result && 1'b1; 
            else                               TEST_compare_result <= TEST_compare_result && 1'b0; 
        end

        // Read 1 in March_2 
        else if ( TEST_state == MARCH_2 && TEST_action == WRITE_0 ) begin
            if ( SRAM_RDATA == 8'b1111_1111)   TEST_compare_result <= TEST_compare_result && 1'b1;
            else                               TEST_compare_result <= TEST_compare_result && 1'b0; 
        end

        // Read 0 in March_3 
        else if ( TEST_state == MARCH_3 && TEST_action == WRITE_1 ) begin
            if ( SRAM_RDATA == 8'b0000_0000)   TEST_compare_result <= TEST_compare_result && 1'b1; 
            else                               TEST_compare_result <= TEST_compare_result && 1'b0; 
        end

        // Read 1 in March_4 
        else if ( TEST_state == MARCH_4 && TEST_action == WRITE_0 ) begin
            if ( SRAM_RDATA == 8'b1111_1111)   TEST_compare_result <= TEST_compare_result && 1'b1; 
            else                               TEST_compare_result <= TEST_compare_result && 1'b0; 
        end

        // Read 0 in March_4    
        else if ( TEST_state == MARCH_4 && TEST_action == READ_1 && TEST_ADDR != 13'h0000) begin
            if ( SRAM_RDATA == 8'b0000_0000)   TEST_compare_result <= TEST_compare_result && 1'b1; 
            else                               TEST_compare_result <= TEST_compare_result && 1'b0; 
        end

        else begin
            TEST_compare_result <= TEST_compare_result ; 
        end

    end

    assign oBIST_done = ( TEST_flag_finish && TEST_compare_result  ) ? 1'b1 : 1'b0; 
    assign oBIST_fail = ( TEST_flag_finish && !TEST_compare_result ) ? 1'b1 : 1'b0; 

endmodule

2.4 BIST的仿真验证

最后,BIST功能同样在Vivado平台上进行了逻辑仿真,

整个BIST过程共BIST_en = 1开始,一共花费了约1600μs完成,

最后BIST_done被拉高,这是必然的 ,因为逻辑仿真中不涉及实际芯片制造中的各种故障,

我们先从宏观上看仿真波形:

从波形中可以看到,MARCH_0状态持续时间最短,这是因为MARCH_0对于每个地址操作仅为WRITE_0,

而MARCH_1,MARCH_2,MARCH_3状态分别要进行读和写两个操作,因此每个状态下的总周期数均为MARCH_0状态的2倍,

MARCH_4则更长,共READ_1,WRITE_0,READ_0三个操作,总周期数均为MARCH_0状态的3倍,

接下来看看MARCH_0和MARCH_1状态之间的转换波形:

其他几段状态转化处的波形也是同理,在此不在一一标注解释了,具体如下:

MARCH_1状态和MARCH_2状态之间的波形:

MARCH_2状态和MARCH_3状态之间的波形:

MARCH_3状态和MARCH_4状态之间的波形:

MARCH_4状态最后一段波形:

完成MARCH_4后,BIST_done被拉高,代表BIST结束。

以上,就是BIST的逻辑仿真解读。

至此,本项目的所有内容已介绍完毕。