本项目用Verilog HDL语言设计了AHB总线上的SRAM控制器,SRAM存储器在AHB总线上作为AHB slave存在,该SRAM控制器具有以下特性:
支持单周期的SRAM读写操作
支持低功耗工作
SRAM存储体由两个Bank组成,系统根据地址选中一块/多块Bank,未被选中的Bank将处于low-power standby模式以降低功耗
支持DFT功能
DFT(Design for Test,可测性设计),指通过在芯片原始设计中插入各种用于提高芯片可测试性(包括可控制性和可观测性)的硬件逻辑,从而使芯片变得容易测试,大幅度节省芯片测试的成本。
本项目中,DFT功能通过BIST(Build-in Self Test,内建自测试)实现,采用March C-作为检测算法
最后,在Vivado平台上对本项目进行了逻辑仿真与验证
下面给出本项目的顶层设计架构,其中sram_top为顶层模块,其下包含sram_interface模块以及SRAM_core两个子模块

sram_interface模块:本质是AHB总线上的slave接口,起到连接总线与SRAM存储体的作用,具体来说:
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测试。在本项目中,BIST功能将基于March C-算法设计,具体将在本文的第二章中介绍。在第一章中,我们将每个sram_bist模块视为8k×8的单端口SRAM即可
在上图中标注出了模块的主要信号,其中红色、蓝色的信号分别代表了两个不同的数据通路
红色数据通路:正常使用SRAM读写功能时的信号,interface接收来自于AHB总线的信号,并将其转化为SRAM所需要的控制信号和写数据,然后再由interface将SRAM的读数据整理后返回AHB总线
蓝色数据通路:使用DFT功能时的信号。BIST_en = 1时,DFT功能被使能,此时红色信号全部被屏蔽。该功能用于芯片生产完毕之后,对每块芯片进行DFT测试以检测是否有生产故障,该数据通路对于SRAM的逻辑功能来说,属于冗余的部分,但是在实际芯片生产中却是必不可少的
在本章中,我们关注红色数据通路的电路设计,而DFT功能设计将在第二章中进行介绍
AHB总线时序:

其中来自AHB总线的control信号包括HTRANS,HBURST,HWRITE
SRAM接口时序:
写时序:

读时序:

读时序与写时序的区别主要在于SRAM_ADDR的处理上:
对于写操作,为了将地址与数据对齐,sram_interface模块会将总线上的地址与控制信号写入寄存器,
而对于读操作,为了实现总线上的单周期读出,会直接将地址送到SRAM端口(注意:SRAM的时钟为AHB总线时钟信号HCLK)。这样,在数据周期刚开始时,读数据就可以返回HRDATA,
这样的设计具有一些局限性:由于SRAM端口上的读地址相比于写地址要滞后一个周期,因此当写操作的下一个周期切换为读操作时,会产生地址冲突,
于是,SRAM控制器会将HREADY拉低一个周期,进行缓冲,下一个周期才会重新拉高HREADY并且返回相应的读数据,
在连续执行多个读操作/连续执行多个写操作时,则不会有这样的问题,可以以AHB总线所允许的最高的速度进行SRAM读写访问,
由于在实际应用中,存储器访问一般不会频繁地在读与写之间切换,因此这样设计对于访问速度带来的代价并不大,
而这样设计的好处,则在于可以避免为SRAM引入额外的时钟源
在明确了AHB SRAM读写的设计需求和读写时序后,我们来看看具体的硬件电路是怎样用Verilog实现的:
首先是顶层模块,主要内容是对两个子模块进行了例化,其中涉及到的信号均已经在架构图中标明,这里不再赘述
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模块,该模块是本项目中重点模块之一,负责寄存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将32位的HWDATA按照下图顺序分配给8位SRAM,作为SRAM的写数据SRAM_WDATA
q:SRAM读数据,每个被选中的sram_bist将返回一个8位数据,sram_interface会将每个Bank中的4个单Byte读数据,组合为一个完整的32位读数据,返回到总线上的HRDATA
HWDATA与SRAM_WDATA的对应关系,
HRDATA与SRAM_q的对应关系,
如下图所示(以Bank0为例):

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_bist模块进行了8次例化,
因此,sram_core实际上是将这8个SRAM拼成了一个16k×32的SRAM,
sram_core的地址共15位,地址范围为0x0000-0xFFFFF,
其中,Bank0对应0x0000-0x7FFFF;Bank1对应0x8000~0xFFFFF,
而每个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;
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
在完成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控制器的设计逻辑是正确的
在设计中,SRAM读写模式和DFT模式的选择通过2选1选择器实现,当DFT模式的使能信号BIST_en = 1时,来自于sram_interface的所有信号逻辑将被忽略,
SRAM端口上的输入信号将全部来自于sram_bist内部的BIST控制电路生成的Pattern,
对于March C-算法,BIST控制电路会对SRAM每个地址进行“写 → 读 → 比较”的操作,
若所有的读数据结果与写入的数据是一致的,BIST_done最终将被拉高,说明该电路生产过程中没有出现生产故障,
反之,如果比较发现有错误,BIST_fail最终将被拉高,该块芯片将被报废,
在模块内加入DFT测试电路,虽然会增加系统的数字面积,但同时也极大地降低了产品在测试环节所花费的开销成本

首先,介绍一些常见的存储器(比如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):
一个存储单元的值发生改变,导致另一个存储单元的值发生改变,可以通过先升序对所有存储单元进行写读操作,然后再降序对所有存储单元进行写读操作的方法进行故障检测
March C算法是目前应用最为广泛的MBIST(Memory Built-In-Self-Test,存储器内建自测试)算法,
March C算法对上文提到的SAF故障,TF故障,CF故障的故障覆盖率均达到100%,
March C算法的具体检测流程如下:
从最低地址开始,在整个存储器中依次写入0(升序)
读出最低地址,结果应为0,然后把1写入该存储单元。完成后地址+1,再次执行该操作,直至对整个存储器执行该操作(升序)
读出最高地址,结果应为1,然后把0写入该存储单元,再次读该单元,结果应为0。完成后地址-1,再次执行该操作,直至对整个存储器执行该操作(降序)
读出最高地址,结果应为0,然后把1写入该存储单元。完成后地址-1,再次执行该操作,直至对整个存储器执行该操作(降序)
读出最低地址,结果应为1,然后把0写入该存储单元,再次读该单元,结果应为0。完成后地址+1,再次执行该操作,直至对整个存储器执行该操作(升序)
由于步骤4,步骤5中,加粗字体所描述的操作实际上是重复的,
因此后来有了改进的March C-算法, 将步骤3中的加粗部分删除,如下:

图中,March C-所执行的全套操作被分成了5个部分,也就是MARCH_0~MARCH_ 4
这也是本项目的BIST功能在RTL设计中,March C-状态机所用到的5个状态的名称
本项目中,内建自测试逻辑电路位于每个sram_bist模块中,
BIST_en作为顶层模块的BIST使能信号,被直接接到每块bist的BIST_en输入使能端口,
依据March C-算法发起的SRAM读写操作,是由有限状态机控制的,该状态机示意图如下:

下面让我们来看看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
最后,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结束。
以上,就是关于AHB-SRAN的BIST模式下的逻辑仿真解读。
最后附上两种工作模式下的Testbench:
点击查看代码
`timescale 1ns / 1ps
module sram_tb #(
//HRESP
parameter OKAY = 2'b00 ,
parameter ERROR = 2'b01 ,
parameter SPLIT = 2'b10 ,
parameter RETRY = 2'b11 ,
//HTRANS
parameter IDLE = 2'b00 ,
parameter BUSY = 2'b01 ,
parameter SEQ = 2'b10 ,
parameter NONSEQ = 2'b11 ,
//HSIZE
parameter BYTE = 3'b000 ,
parameter WORD = 3'b001 ,
parameter DWORD = 3'b010 ,
//HBURST
parameter SINGLE = 3'b000 ,
parameter INCR = 3'b001 ,
parameter WRAP4 = 3'b010 ,
parameter INCR4 = 3'b011 ,
parameter WARP8 = 3'b100 ,
parameter INCR8 = 3'b101 ,
parameter WARP16 = 3'b110 ,
parameter INCR16 = 3'b111
)();
// input output declaration
reg HCLK;
reg HRESETn;
reg HSEL ;
reg [1:0] HTRANS;
reg [2:0] HBURST;
reg [2:0] HSIZE ;
reg HWRITE;
reg [15:0] HADDR ;
reg [31:0] HWDATA;
wire HREADY;
wire [1:0] HRESP ;
wire [31:0] HRDATA;
// BIST IO
reg BIST_en;
wire BIST_done;
wire BIST_fail;
// top module instantion
sram_top u_test(
.HCLK (HCLK ),
.HRESETn(HRESETn),
.HSEL (HSEL ),
.HTRANS (HTRANS ),
.HBURST (HBURST ),
.HSIZE (HSIZE ),
.HWRITE (HWRITE ),
.HADDR (HADDR ),
.HWDATA (HWDATA ),
.HREADY (HREADY ),
.HRESP (HRESP ),
.HRDATA (HRDATA ),
.BIST_en (BIST_en ),
.BIST_done(BIST_done),
.BIST_fail(BIST_fail)
);
// Excitation Vector Generation
initial begin
forever #10 HCLK = ~HCLK; //50Mhz
end
initial begin
HCLK = 0;
HRESETn = 0;
// Choose BIST Mode/ FUNC Mode to run simulation
/*————————————————————————————————————————————————————————————————————————*\
/ Testbench for BIST Mode \
\*————————————————————————————————————————————————————————————————————————*/
/*
BIST_en = 1;
end
*/
/*————————————————————————————————————————————————————————————————————————*\
/ Testbench for FUNC Mode \
\*————————————————————————————————————————————————————————————————————————*/
HRESETn = 0;
BIST_en = 0;
HSEL = 0;
HTRANS = 3'b111;
HBURST = 2'b11;
HSIZE = 3'b111;
HWRITE = 1'b0;
HADDR = 16'h0000; //HADDR[1:0] have to be 2'b00 ( 4n )
HWDATA = 32'h0;
#65 HRESETn = 1;
//-------------------------WRITE MEM---------------
#20
// write control0
HSEL = 1;
HTRANS = NONSEQ;
HBURST = SINGLE;
HSIZE = WORD;
HWRITE = 1'b1;
HADDR = 16'h0000;
#20
// data0 → mem(0x0000)
HWDATA = 32'h11223344;
// write control 1
HWRITE = 1'b1;
HADDR = 16'h0004;
#20
// data1 → mem(0x0004)
HWDATA = 32'h55667788;
// write control 2
HWRITE = 1'b1;
HADDR = 16'h0008;
#20
// data2 → mem(0x0008)
HWDATA = 32'h99AABBCC;
//// write control 3
//HWRITE = 1'b1;
//HADDR = 16'h000C;
#20
// data3 → mem(0x000C)
HWDATA = 32'hAAAAAAAA;
// write control 4
HWRITE = 1'b1;
HADDR = 16'h0010;
#20
// data4 → mem(0x0010)
HWDATA = 32'hBBBBBBBB;
// write control 5
HWRITE = 1'b1;
HADDR = 16'h0014;
#20
// data5 → mem(0x0014)
HWDATA = 32'hCCCCCCCC;
// write control 6
HWRITE = 1'b1;
HADDR = 16'h8000;
#20
// data6 → mem(0x8000)
HWDATA = 32'hDDDDDDDD;
// write control 7
HWRITE = 1'b1;
HADDR = 16'h8004;
#20
// data7 → mem(0x8004)
HWDATA = 32'hEEEEEEEE;
// write control 8
HWRITE = 1'b1;
HADDR = 16'h8008;
#20
// data8 → mem(0x8008)
HWDATA = 32'hFFFFFFFF;
// read control 0
HWRITE = 1'b0;
HADDR = 16'h0000;
//--------------READ MEM----------------
#20
// MASTER FINDS HREADY=0, SO MASTER WAITS
#20
// read control 1
HWRITE = 1'b0;
HADDR = 16'h0004;
HWDATA = 32'h00000000; // to test if hwdata will work when HWRTIE = 0
#20
// read control 2
HWRITE = 1'b0;
HADDR = 16'h0008;
#20
// read control 3
HWRITE = 1'b0;
HADDR = 16'h000C;
#20
// read control 4
HWRITE = 1'b0;
HADDR = 16'h0010;
#20
// read control 5
HWRITE = 1'b0;
HADDR = 16'h0014;
#20
// read control 6
HWRITE = 1'b0;
HADDR = 16'h8000;
#20
// read control 7
HWRITE = 1'b0;
HADDR = 16'h8004;
#20
// read control 8
HWRITE = 1'b0;
HADDR = 16'h8008;
#100
$finish();
end
endmodule
至此,本项目的所有内容已介绍完毕。
我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou
我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为
为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer
我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server
在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',
我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby1.9+ 关于ruby-主要:Objectwhenrun