嘘~ 正在从服务器偷取页面 . . .

SRAM的verilog实现


主包最近在学FIFO,想要写一个用SRAM实现的FIFO,但是竟然忘记SRAM的基本原理,赶紧写一篇博客复习一下,不然全忘光了。

SRAM全称是Static Random-Access Memory,翻译过来就是静态随机存取存储器。这里的静态很好理解,其实就是指这种存储器只要保持通电,里面存储的数据就可以一直保持。但是,一旦切断电力供应,SRAM存储的数据还是会消失的。

接下来对着原理图梳理一下,就知道为什么SRAM不需要刷新电路,就能保存数据。

其实简单来说就是两个首尾相连的反相器。N1、P1、N2 和 P2 晶体管组成交叉耦合反相器对,用来锁存数据。N3 和 N4 为传输管,用来导通要读取和写入的数据。WL 为字线,在读写操作中用来开启 N3 和 N4 。BL 和 BLB 为两条位线,用来读取和写入数据。

(1)读操作

先看看读操作是怎么完成的,我们可以简化电路图如下:

当 SRAM单元处于读状态时,BL 和 BLB 为高 ,WL 为高,N3、N4 导通。假设节点 Q 为 “1”, Q― 为 “0”,电路进行读“1”操作,位线 BL 不会通过晶体管 N1、N3 放电到地,依然保持高电位。由于存储节点 Q― 电压为低,晶体管 P2、N4 导通,通过VDD将位线拉到高电平逻辑 “1”,为位线 BLB提供了放电路径,完成读 “1” 操作。读“0”操作与读“1”操作原理类似。(由于简化了电路图,因此这里特别说明,在原图中Q点其实控制的是N3和N1这条路径,而 Q― 控制的是P2和N4这条路径。)

(2)写操作

当 SRAM单元处于写状态时,如果 SRAM 单元电路处于写 “1” 操作,BL 为高,BLB 为低,WL 为高,N3、N4 导通。QB 通过 N4 放电,BL 通过 N3 对 Q 充电,两个反相器所构成的锁存器的平衡状态被打破,QB 的数据转换为零,完成写“1”操作。位线上相反的电平分别对存储节点Q和QB充放电直到字线电平下拉到低电平,N3 和 N4 两个传输管关断。由于耦合反相器的正反馈原理,Q 与 QB 分别保存 0 和 1,数据成功写入存储单元。写 “0” 操作与写 “1” 操作原理类似。

1.1 SP-SRAM

现在讲完基本的 6T 结构的SRAM,直接看看单端口SRAM**(SP-SRAM)**,端口结构如下图所示:

各个端口的含义如下所示:

信号 含义
clk 时钟
ce Chip Enable,片选使能,整个 SRAM 模块开关
we Write Enable,写使能,读 / 写模式选择
wbe 字节写使能 / 字节掩码
addr 地址
din 写输入数据
dout 读输出数据

根据给出的信号端口含义,我们很快就能写出代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
module sp_sram #(
parameter ADDR_WIDTH = 10, // 地址位宽,2^10=1024深度
parameter DATA_WIDTH = 128, // 数据位宽 128bit
parameter WBE_NUM = 16 // wbe信号位数,对应 128 / 16 = 8-bit 一个选通字节
)(
input wire clk,
input wire ce,
input wire we,
input wire [WBE_NUM-1:0] wbe,
input wire [ADDR_WIDTH-1:0] addr,
input wire [DATA_WIDTH-1:0] din,
output reg [DATA_WIDTH-1:0] dout
);

localparam WBE_GRP_WIDTH = DATA_WIDTH / WBE_NUM;

reg [DATA_WIDTH-1:0] mem [0:((1<<ADDR_WIDTH)-1)]; // 存储器数组

integer id;

always @(posedge clk) begin
if (ce) begin
if (we) begin
for (id = 0; id < WBE_NUM; id = id + 1) begin
if (wbe[id]) begin
mem[addr][id*WBE_GRP_WIDTH +: WBE_GRP_WIDTH] <= din[id*WBE_GRP_WIDTH +: WBE_GRP_WIDTH];
dout[id*WBE_GRP_WIDTH +: WBE_GRP_WIDTH] <= din[id*WBE_GRP_WIDTH +: WBE_GRP_WIDTH];
end else begin
// 显式赋值保持 memory 原值或 dout 明确逻辑,防止综合歧义
dout[id*WBE_GRP_WIDTH +: WBE_GRP_WIDTH] <= mem[addr][id*WBE_GRP_WIDTH +: WBE_GRP_WIDTH];
end
end
end else begin
dout <= mem[addr];
end
end
end

endmodule

1.2 DP-SRAM

双端口SRAM**(DP-SRAM)**,端口结构如下图所示:

其中,

  • “a” 口只写, “b” 口只读;
  • “a” 口的写不会改变 “b” 口的 doutb;
  • 为了兼容性,禁止 “a”/“b” 口同时 r/w 同一个地址。

然后来看一下各个端口定义:

端口 方向 含义
clka 输入 A 端口时钟,A 端口所有读写操作同步于该时钟上升沿
ena 输入 A 端口使能(chip enable),高电平端口工作;低电平端口不动作
wea 输入 A 端口写使能(write enable A):1 = 写操作,0 = 读操作
wbea 输入 A 端口字节写掩码(write byte‑enable A),位选写使能,支持部分 bit / 字节分片写入,1 代表对应分组允许写入
addra 输入 A 端口地址,A 端口读写的存储单元地址
dina 输入 A 端口写输入数据,写操作时送入存储器的数据
clkb 输入 B 端口时钟,B 端口操作同步于该时钟上升沿;双端口可以是异步时钟(真正 DP),clka 与 clkb 可以不同频不同相
enb 输入 B 端口使能,高电平 B 端口工作
addrb 输入 B 端口读地址,B 端口要读取的存储单元地址
doutb 输出 B 端口读出数据,B 端口读操作输出的数据

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
module dp_sram#(
parameter ADDR_WIDTH = 7, // 地址位宽,2^10=1024深度
parameter DATA_WIDTH = 64, // 数据位宽 128bit
parameter WBE_NUM = 4 // wbe信号位数,对应 128 / 16 = 8-bit 一个选通字节
)(
input wire clka,
input wire ena,
input wire wea,
input wire [WBE_NUM-1:0] wbea,
input wire [ADDR_WIDTH-1:0] addra,
input wire [DATA_WIDTH-1:0] dina,

input wire clkb,
input wire enb,
input wire [ADDR_WIDTH-1:0] addrb,
output reg [DATA_WIDTH-1:0] doutb
);

localparam WBE_GRP_WIDTH = DATA_WIDTH / WBE_NUM;

reg [DATA_WIDTH-1:0] mem [0:((1<<ADDR_WIDTH)-1)];

integer id;

always @(posedge clka)begin
if(ena)begin
if(wea)begin
for(id=0; id<WBE_NUM; id=id+1)begin
if(wbea[id])begin
mem[addra][id*WBE_GRP_WIDTH +: WBE_GRP_WIDTH] <= dina[id*WBE_GRP_WIDTH +: WBE_GRP_WIDTH];
end
end
end

end
end

always @(posedge clkb)begin
if(enb)begin
doutb <= mem[addrb];
end
end

endmodule

这里并没有考虑亚稳态,因此代码还有优化空间。


文章作者: Jeremy Yang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Jeremy Yang !
赏
评论
  目录
**