verilog 中实现 sram 代码
verilog/systemverilog中sram的实现
sram的基本知识
SRAM 即静态随机存取存储器,它是随机存取存储器的一种。所谓的“静态”,是指这种存储器只要保持通电,里面储存的数据就可以一直保持。
在IC设计中,常用SRAM在各个block中实现存储单元,用这些存储单元来缓存数据,交换数据,从而实现block的功能。
verilog/systemverilog中sram的实现
在verilog中,我们可以用变量数组来描述sram,一般每一bit都将综合成一个触发器。比如下面的代码,将定义一个宽度为8,深度为256的sram。
logic [7:0] sram[256]
根据读写控制的不同,我们可以把sram的实现分类为,单口sram,单时钟简双口sram,单时钟真双口sram,双时钟简双口sram,双时钟真双口sram,下面分别来学习这几种sram的verilog实现。
单口SSRAM(同步SRAM)
下面是同步先读模式的单口SRAM代码,如果we=1,会并行读写ram[addr]
中的值,所以这时qout中保存的是写之前的值。
文件名称 code4_18.v
`timescale 1ns/1ps
module TestMem;
logic clk = 1'b0;
initial begin
$display("start a clock pulse");
$dumpfile("testmem.vcd");
$dumpvars(0, TestMem);
#300 $finish;
end
always begin
#5 clk = ~clk;
end
logic [7:0] a=0,d=0,q;
logic we=0;
initial begin
#10 we <=1; a<=8'h01; d<=8'h10;
#10 a<=8'h03; d<=8'h30;
#10 a<=8'h06; d<=8'h60;
#10 a<=8'h0a; d<=8'ha0;
#10 a<=8'h0f; d<=8'hf0;
#10 we<=0;a<=8'h00; d<=8'h00;
forever #10 begin
a++;
end
end
SpRamRf #(.DW(8), .DEPTH(256)) the_spramrf(.clk(clk),.addr(a),.we(we),.din(d),.qout(q));
endmodule
//simple port ram read first
module SpRamRf #(parameter DW=8, DEPTH=256) (
input wire clk,
input wire [$clog2(DEPTH)-1:0] addr,
input wire we,
input wire [DW-1:0] din,
output logic [DW-1:0] qout
);
logic [DW-1:0] ram[DEPTH];
always_ff @(posedge clk) begin
if(we) ram[addr] <= din;
qout <= ram[addr];
end
endmodule
在vscode中,使用下面命令编译,运行代码,然后用gtkwave 打开波形文件:
iverilog -o myrun -g 2012 -s TestMem code4_18.v
vvp myrun
gtkwave testmem.vcd
- 在第1个时钟上升沿,we=0,a=0,会读取ram[0],由于ram初始值为x,所以此时q值为x。
- 在第2个时钟上升沿,we=1,a=1,会并行读写ram[1],ram[1]=0x10,由于读优先,所以此时q值读取的仍是之前ram[1]中之前的值x。
- 在第3个时钟上升沿,we=1,a=3,会并行读写ram[3],ram[3]=0x30,由于读优先,所以此时q值读取的仍是之前ram[3]中之前的值x。
- 在第4个时钟上升沿,we=1,a=6,会并行读写ram[6],ram[6]=0x60,由于读优先,所以此时q值读取的仍是之前ram[6]中之前的值x。
- 在第5个时钟上升沿,we=1,a=10,会并行读写ram[10],ram[10]=0xa0,由于读优先,所以此时q值读取的仍是之前ram[10]中之前的值x。
- 在第6个时钟上升沿,we=1,a=15,会并行读写ram[15],ram[0]=0xf0,由于读优先,所以此时q值读取的仍是之前ram[15]中之前的值x。
- 在第7个时钟上升沿,we=0,a=0,会读取ram[0],ram[0]=x。
- 从第8个时钟上升沿开始,we=0, 地址a累积增加,依次读取ram[1],ram[2],...
我们也可以在同步单口sram读写控制模块中加上复位信号,来初始化ram的值为0。加上复位信号的代码如下:
文件名称 code4_19.v
`timescale 1ns/1ps
module TestMem;
logic clk = 1'b0;
logic rst = 1'b0;
initial begin
$display("start a clock pulse");
$dumpfile("testmem.vcd");
$dumpvars(0, TestMem);
#300 $finish;
end
always begin
#5 clk = ~clk;
end
logic [7:0] a=0,d=0,q;
logic we=0;
initial begin
#10 rst=1'b1;
#30 rst=1'b0;
#10 we <=1; a<=8'h01; d<=8'h10;
#10 a<=8'h03; d<=8'h30;
#10 a<=8'h06; d<=8'h60;
#10 a<=8'h0a; d<=8'ha0;
#10 a<=8'h0f; d<=8'hf0;
#10 we<=0;a<=8'h00; d<=8'h00;
forever #10 begin
a++;
end
end
SpRamRf #(.DW(8), .DEPTH(256)) the_spramrf(.clk(clk),.rst(rst),.addr(a),.we(we),.din(d),.qout(q));
endmodule
//simple port ram read first
module SpRamRf #(parameter DW=8, DEPTH=256) (
input wire clk,
input wire rst,
input wire [$clog2(DEPTH)-1:0] addr,
input wire we,
input wire [DW-1:0] din,
output logic [DW-1:0] qout
);
logic [DW-1:0] ram[DEPTH];
always_ff @(posedge clk) begin
//同步复位
if(rst) begin
for(integer i = 0; i< DEPTH; i=i+1)
ram[i] <= '0;
end
else begin
if(we) ram[addr] <= din;
qout <= ram[addr];
end
end
endmodule
在vscode中,使用下面命令编译,运行代码,然后用gtkwave 打开波形文件:
iverilog -o myrun -g 2012 -s TestMem code4_19.v
vvp myrun
gtkwave testmem.vcd
- 在第1个时钟上升沿,rst=0,we=0,a=0,会读取ram[0],由于ram初始值为x,所以此时q值为x。
- 在第2个时钟上升沿,rst=1, we=0,a=0,此时会初始化ram中的值为0,q保持之前的值x。
- 在第3个时钟上升沿,rst=1, we=0,a=0,此时会初始化ram中的值为0,q保持之前的值x。
- 在第4个时钟上升沿,rst=1, we=0,a=0,此时会初始化ram中的值为0,q保持之前的值x。
- 在第5个时钟上升沿,rst=0, we=0,a=0,会读取ram[0],由于rst信号已经初始化ram,所以此时q值读取的是0。
- 在第6个时钟上升沿,rst=1,we=1,a=15,会并行读写ram[1],ram[1]=0x10,由于读优先,所以此时q值读取的仍是之前ram[1]中之前的值0。
- 在第7个时钟上升沿,rst=1,we=1,a=3,会并行读写ram[3],ram[3]=0x30,由于读优先,所以此时q值读取的仍是之前ram[3]中之前的值0。
- 在第8个时钟上升沿,rst=1,we=1,a=6,会并行读写ram[6],ram[6]=0x60,由于读优先,所以此时q值读取的仍是之前ram[6]中之前的值0。
- 在第9个时钟上升沿,rst=1,we=1,a=10,会并行读写ram[10],ram[10]=0xa0,由于读优先,所以此时q值读取的仍是之前ram[10]中之前的值0。
- 在第10个时钟上升沿,rst=1,we=1,a=15,会并行读写ram[15],ram[15]=0xf0,由于读优先,所以此时q值读取的仍是之前ram[15]中之前的值0。
- 在第11个时钟上升沿,rst=1,we=0,a=0,会读取ram[0],ram[0]=0。
- 从第12个时钟上升沿开始,we=0, 地址a累积增加,依次读取ram[1],ram[2],...
前面的同步单口sram代码中,是读优先的,我们可以对代码做一点改动,成为写优先。对于写优先,如果we=1,则 ram[addr] <= din; qout <= din;
如果we=0,则 qout <= ram[addr];
文件名称code4_20.v
`timescale 1ns/1ps
module TestMem;
logic clk = 1'b0;
initial begin
$display("start a clock pulse");
$dumpfile("testmem.vcd");
$dumpvars(0, TestMem);
#300 $finish;
end
always begin
#5 clk = ~clk;
end
logic [7:0] a=0,d=0,q;
logic we=0;
initial begin
#10 we <=1; a<=8'h01; d<=8'h10;
#10 a<=8'h03; d<=8'h30;
#10 a<=8'h06; d<=8'h60;
#10 a<=8'h0a; d<=8'ha0;
#10 a<=8'h0f; d<=8'hf0;
#10 we<=0;a<=8'h00; d<=8'h00;
forever #10 begin
a++;
end
end
SpRamWf #(.DW(8), .DEPTH(256)) the_spramwf(.clk(clk),.addr(a),.we(we),.din(d),.qout(q));
endmodule
//simple port ram write first
module SpRamWf #(parameter DW=8, DEPTH=256) (
input wire clk,
input wire [$clog2(DEPTH)-1:0] addr,
input wire we,
input wire [DW-1:0] din,
output logic [DW-1:0] qout
);
logic [DW-1:0] ram[DEPTH];
always_ff @(posedge clk) begin
if(we) begin
ram[addr] <= din;
qout <= din;
end
else
qout <= ram[addr];
end
endmodule
在vscode中,使用下面命令编译,运行代码,然后用gtkwave 打开波形文件:
iverilog -o myrun -g 2012 -s TestMem code4_20.v
vvp myrun
gtkwave testmem.vcd
从波形上可以看出,在时钟上升沿,如果we=1,q的输出即为写入的值d。
单时钟简双口SSRAM(同步SRAM)
单时钟同步简双口SRAM有两个输入地址addr_a, addr_b,
但一个时钟周期上升沿只有addr_a
可以写SRAM,而addr_b
用来读SRAM。信号we_a
用来控制addr_a
写SRAM,如果we_a=0
,则地址addr_a
不会写SRAM。
如果读写相同地址,且we_a=0
,则是读优先模式,读取的是ram
中之前保存的值。
文件名称code4_21.v
`timescale 1ns/1ps
module TestMem;
logic clk = 1'b0;
initial begin
$display("start a clock pulse");
$dumpfile("testmem.vcd");
$dumpvars(0, TestMem);
#300 $finish;
end
always begin
#5 clk = ~clk;
end
logic [7:0] a1=0,a2=0,d1=0,q1,d2=0,q2;
logic we_a=0,we_b=0;
initial begin
#10 we_a =1; a1=8'h01; d1=8'h10; a2=8'h00;
#10 a1=8'h03; d1=8'h30; a2=8'h03;
#10 a1=8'h06; d1=8'h60; a2=8'h01;
#10 a1=8'h0a; d1=8'ha0; a2=8'h06;
#10 a1=8'h0f; d1=8'hf0; a2=8'h0a;
#10 we_a=0; a2 = 8'h00;
forever #10 begin
a1++; a2++;
end
end
SdpRamRf #(.DW(8), .DEPTH(256)) the_sdpramrf(.clk(clk),.addr_a(a1),.we_a(we_a),.din_a(d1),.addr_b(a2),.qout(q1));
endmodule
//simple double port ram read first
//单时钟简双口, 一个口读,一个口写
module SdpRamRf #(parameter DW=8, DEPTH=256) (
input wire clk,
input wire [$clog2(DEPTH)-1:0] addr_a,
input wire we_a,
input wire [DW-1:0] din_a,
input wire [$clog2(DEPTH)-1:0] addr_b,
output logic [DW-1:0] qout
);
logic [DW-1:0] ram[DEPTH];
always_ff @(posedge clk) begin
if(we_a) ram[addr_a] <= din_a;
end
always_ff @(posedge clk) begin
qout <= ram[addr_b];
end
endmodule
在vscode中,使用下面命令编译,运行代码,然后用gtkwave 打开波形文件:
iverilog -o myrun -g 2012 -s TestMem code4_21.v
vvp myrun
gtkwave testmem.vcd
- 第1个时钟上升沿,
we_a=0, a1=0, a2=0, d1=0,
读取ram[0],q1=ram[0]=x
- 第2个时钟上升沿,
we_a=1, a1=1, a2=0, d1=0x10,
写ram[1]=0x10,
读取ram[0],q1=ram[0]=x
- 第3个时钟上升沿,
we_a=1, a1=3, a2=3, d1=0x30,
写ram[3]=0x30,
读取ram[3],q1=ram[3]=x
,因为是读优先模式,所以q1
中读取的是ram[3]
中之前的值x
。 - 第4个时钟上升沿,
we_a=1, a1=6, a2=1, d1=0x60,
写ram[6]=0x60,
读取ram[1],q1=ram[1]=0x10
。 - 第5个时钟上升沿,
we_a=1, a1=10, a2=6, d1=0xa0,
写ram[10]=0xa0,
读取ram[6],q1=ram[6]=0x60
。 - 第6个时钟上升沿,
we_a=1, a1=15, a2=10, d1=0xf0,
写ram[15]=0xf0,
读取ram[10],q1=ram[10]=0xa0
。 - 第7个时钟上升沿,
we_a=0, a1=15, a2=0, d1=0xf0,
没有写操作,读取ram[0],q1=ram[0]=x
。 - 第8个时钟上升沿,从这儿开始,
a2
累加,读取q1=ram[a2]
。
单时钟真双口SSRAM(同步SRAM)
单时钟真双口SSRAM verilog实现中,是口内先写,口间先读。如果两口同时写同一个地址不同数据,会产生未知结果。
文件名称code4_22.v
`timescale 1ns/1ps
module TestMem;
logic clk = 1'b0;
initial begin
$display("start a clock pulse");
$dumpfile("testmem.vcd");
$dumpvars(0, TestMem);
#300 $finish;
end
always begin
#5 clk = ~clk;
end
logic [7:0] a1=0,a2=0,d1=0,q1,d2=0,q2;
logic we_a=0,we_b=0;
initial begin
#10 we_a =1; a1=8'h01; d1=8'h10; we_b=1;a2=8'h00;d2=8'hff;
#10 we_a =0; a1=8'h00; a2=8'h02; d2=8'hfe;
#10 we_a =0; a1=8'h02; a2=8'h03; d2=8'hfc;
#10 we_a =1; a1=8'h0a; d1=8'ha0; we_a=0; a2=8'h01;
#10 we_a =0; a1=8'h00; we_a=0; a2=8'h01;
forever #10 begin
a1++; a2++;
end
end
在vscode中,使用下面命令编译,运行代码,然后用gtkwave 打开波形文件:
iverilog -o myrun -g 2012 -s TestMem code4_22.v
vvp myrun
gtkwave testmem.vcd
- 第1个时钟上升沿,
we_a=0, a1=0, d1=0,a
口读取ram[0],q1=ram[0]=x
,we_b=0, a2=0, d2=0,b
口读取ram[0],q2=ram[0]=x
- 第2个时钟上升沿,
we_a=1, a1=1, d1=0x10,a
口写ram[1]=0x10,q1=ram[1]=0x10
,we_b=1, a2=0, d2=0xff,b
口写ram[0]=0xff,q2=ram[0]=0xff
- 第3个时钟上升沿,
we_a=0, a1=0, d1=0x10,a
口读ram[0],q1=ram[0]=0xff
,we_b=1, a2=2, d2=0xfe,b
口写ram[2]=0xfe,q2=ram[2]=0xfe
- 第4个时钟上升沿,
we_a=0, a1=2, d1=0x10,a
口读ram[2],q1=ram[2]=0xfe
,we_b=1, a2=3, d2=0xfc,b
口写ram[3]=0xfc,q2=ram[3]=0xfc
- 第5个时钟上升沿,
we_a=0, a1=10, d1=0xa0,a
口读ram[10],q1=ram[10]=x
,we_b=1, a2=1, d2=0xfc,b
口写ram[1]=0xfc,q2=ram[1]=0xfc
- 第6个时钟上升沿,
we_a=0, a1=0, d1=0xa0,a
口读ram[0],q1=ram[0]=0xff
,we_b=1, a2=1, d2=0xfc,b
口写ram[1]=0xfc,q2=ram[1]=0xfc
- 第7个时钟上升沿,从第7个时钟上升沿开始,
we_a=0, a1累加, d1=0xa0,a
口读ram[a1],q1=ram[a1]=0xfc
,we_b=1, a2累加, d2=0xfc,b
口写ram[a2]=0xfc,q2=ram[a2]=0xfc
双时钟简双口SSRAM(同步SRAM)
双时钟简双口SSRAM和单时钟相似,都是a口写,b口读,唯一不同的是双时钟每个口都有自己的时钟信号驱动,所以需要两个时钟信号clk_a
和clk_b
。clk_a
时钟周期是10ns
,clk_b
时钟周期是16ns
。
文件名称code4_23.v
`timescale 1ns/1ps
module TestMem;
logic clk_a = 1'b0;
logic clk_b = 1'b0;
initial begin
$display("start a clock pulse");
$dumpfile("testmem.vcd");
$dumpvars(0, TestMem);
#300 $finish;
end
always begin
#5 clk_a = ~clk_a;
end
always begin
#8 clk_b = ~clk_b;
end
logic [7:0] a1=0,a2=0,d1=0,q1,d2=0,q2;
logic we_a=0,we_b=0;
initial begin
#10 we_a =1; a1=8'h01; d1=8'h10; a2=8'h00;
#10 a1=8'h03; d1=8'h30; a2=8'h01;
#10 a1=8'h06; d1=8'h60; a2=8'h03;
#10 a1=8'h0a; d1=8'ha0; a2=8'h06;
#10 a1=8'h0f; d1=8'hf0; a2=8'h0a;
#10 we_a=0; a2 = 8'h00;
forever #10 begin
a1++; a2++;
end
end
SdcRam #(.DW(8), .DEPTH(256)) the_sdcram(.clk_a(clk_a),.addr_a(a1),.we_a(we_a),.din_a(d1),.clk_b(clk_b),.addr_b(a2),.qout(q1));
endmodule
//双时钟,简双口,口一写,口二读
module SdcRam #(parameter DW=8, DEPTH=256) (
input wire clk_a,
input wire [$clog2(DEPTH)-1:0] addr_a,
input wire we_a,
input wire [DW-1:0] din_a,
input wire clk_b,
input wire [$clog2(DEPTH)-1:0] addr_b,
output logic [DW-1:0] qout
);
logic [DW-1:0] ram[DEPTH];
always_ff @(posedge clk_a) begin
if(we_a) ram[addr_a] <= din_a;
end
always_ff @(posedge clk_b) begin
qout <= ram[addr_b];
end
endmodule
在vscode中,使用下面命令编译,运行代码,然后用gtkwave 打开波形文件:
iverilog -o myrun -g 2012 -s TestMem code4_23.v
vvp myrun
gtkwave testmem.vcd
- clk_a第1个时钟上升沿,
we_a=0, a1=0, d1=0,a
口没有写操作 - clk_b第1个时钟上升沿,
a2=0,b
口读取ram[0],q1=ram[0]=x
- clk_a第2个时钟上升沿,
we_a=1, a1=1, d1=0x10,a
口写ram[1],ram[1]=0x10
- clk_b第2个时钟上升沿,
a2=1,b
口读取ram[1],q1=ram[1]=0x10
- clk_a第3个时钟上升沿,
we_a=1, a1=3, d1=0x30,a
口写ram[3],ram[3]=0x30
- clk_a第3个钟上升沿,
we_a=1, a1=6, d1=0x60,a
口写ram[6],ram[6]=0x60
- clk_b第3个时钟上升沿,
a2=6,b
口读取ram[6],q1=ram[6]=0x60
- clk_a第5个时钟上升沿,
we_a=1, a1=10, d1=0xa0,a
口写ram[10],ram[10]=0xa0
- clk_a第6个钟上升沿,
we_a=1, a1=15, d1=0xf0,a
口写ram[15],ram[15]=0xf0
- clk_b第4个时钟上升沿,
a2=10,b
口读取ram[10],q1=ram[10]=0xa0
- clk_a第7个时钟上升沿,
we_a=0, a1=15, d1=0xf0,a
口没有写操作,之后由于一直we_a=0
,所以a
口一直没有写操作。 - clk_b第5个时钟上升沿,
a2=1,b
口读取ram[1],q1=ram[1]=0x10
,之后a2
累加,在每个clk_b
的每个时钟上升沿,一次读取ram[a2]
。
双时钟真双口SSRAM(同步SRAM)
双时钟真双口和单时钟真双口相似,都是口内写优先,口间读优先,唯一不同a口和b口都有自己的时钟信号驱动,所以需要两个时钟信号clk_a
和clk_b
。clk_a
时钟周期是10ns
,clk_b
时钟周期是16ns
。
文件名称code4_24.v
`timescale 1ns/1ps
module TestMem;
logic clk_a = 1'b0;
logic clk_b = 1'b0;
initial begin
$display("start a clock pulse");
$dumpfile("testmem.vcd");
$dumpvars(0, TestMem);
#300 $finish;
end
always begin
#5 clk_a = ~clk_a;
end
always begin
#8 clk_b = ~clk_b;
end
logic [7:0] a1=0,a2=0,d1=0,q1,d2=0,q2;
logic we_a=0,we_b=0;
initial begin
#10 we_a =1; a1=8'h01; d1=8'h10; we_b=1;a2=8'h00;d2=8'hff;
#10 we_a =0; a1=8'h00; a2=8'h02; d2=8'hfe;
#10 we_a =0; a1=8'h02; a2=8'h03; d2=8'hfc;
#10 we_a =1; a1=8'h0a; d1=8'ha0; we_a=0; a2=8'h01;
#10 we_a =0; a1=8'h00; we_a=0; a2=8'h01;
forever #10 begin
a1++; a2++;
end
end
DcRam #(.DW(8), .DEPTH(256)) the_dcram(.clk_a(clk_a),.addr_a(a1),.we_a(we_a),.qout_a(q1),
.din_a(d1),.clk_b(clk_b),.addr_b(a2),.we_b(we_b),.qout_b(q2),.din_b(d2));
endmodule
//double port ram
//双时钟真双口, 每个端口都可以独立读写
module DcRam #(parameter DW=8, DEPTH=256) (
input wire clk_a,
input wire [$clog2(DEPTH)-1:0] addr_a,
input wire we_a,
input wire [DW-1:0] din_a,
output logic [DW-1:0] qout_a,
input wire clk_b,
input wire [$clog2(DEPTH)-1:0] addr_b,
input wire we_b,
input wire [DW-1:0] din_b,
output logic [DW-1:0] qout_b
);
logic [DW-1:0] ram[DEPTH];
always_ff @(posedge clk_a) begin
if(we_a) begin
ram[addr_a] <= din_a;
qout_a <= din_a;
end
else qout_a = ram[addr_a];
end
always_ff @(posedge clk_b) begin
if(we_b) begin
ram[addr_b] <= din_b;
qout_b <= din_b;
end
else qout_b = ram[addr_b];
end
endmodule
在vscode中,使用下面命令编译,运行代码,然后用gtkwave 打开波形文件:
iverilog -o myrun -g 2012 -s TestMem code4_24.v
vvp myrun
gtkwave testmem.vcd
- clk_a第1个时钟上升沿,
we_a=0, a1=0, d1=0,a
口读取ram[0],q1=ram[0]=x
。 - clk_b第1个时钟上升沿,
we_b=0, a2=0, d2=0,b
口读取ram[0],q2=ram[0]=x
。 - clk_a第2个时钟上升沿,
we_a=1, a1=1, d1=0x10,a
口写ram[1],ram[1]=0x10,q1=0x10
。 - clk_b第2个时钟上升沿,
we_b=1, a2=2, d2=0xfe,b
口写ram[2],ram[2]=0xfe,q2=ram[2]=0xfe
。 - clk_a第3个时钟上升沿,
we_a=0, a1=0, d1=0x10,a
口读ram[0],q1=x
。 - clk_a第4个时钟上升沿,
we_a=0, a1=2, d1=0x10,a
口读ram[2],q1=ram[2]=0xfe
。 - clk_b第3个时钟上升沿,
we_b=1, a2=1, d2=0xfc,b
口写ram[1],ram[1]=0xfc,q2=ram[1]=0xfc
。之后,a2
累加,在clk_b
的每个时钟上升沿,写ram[a2]=0xfc,q2=0xfc
。 - clk_a第5个时钟上升沿,
we_a=0, a1=10, d1=0x10,a
口读ram[10],q1=ram[10]=x
。 - clk_a第6个时钟上升沿,
we_a=0, a1=0, d1=0x10,a
口读ram[0],q1=ram[0]=x
。之后a1
累加,a
口读取ram[a1],q1=ram[a1]
。
多bank SRAM
热门相关:攻略初汉 游戏开始 独步仙尘 呆萌小青梅:妖孽竹马太腹黑 我的全世界就是你