数字IC经典电路设计
经典电路设计是数字IC设计里基础中的基础,盖大房子的第一部是打造结实可靠的地基,每一篇笔者都会分门别类给出设计原理、设计方法、verilog代码、Testbench、仿真波形。然而实际的数字IC设计过程中考虑的问题远多于此,通过本系列希望大家对数字IC中一些经典电路的设计有初步入门了解。能力有限,纰漏难免,欢迎大家交流指正。快速导航链接如下:
RAM(随机存取存储器)是计算机系统中的一种主要存储器件,用于存储和读取数据。在RAM中,单端口RAM(Single-port RAM)和双端口RAM(Dual-port RAM)是两种常见的类型,双端口RAM又分为真双端口(True dual-port RAM)和伪双端口RAM(Simple dual-port RAM)。
那么什么是单端口和双端口?又该如何区分真双端口和伪双端口?
总的来说:
单端口RAM:A不能同时读写,即A写时不可读,B读时不可写。
伪双端口RAM:AB可同时读写,但仅A写B读。
真双端口RAM:AB可同时读写,A可写可读,B可写可读。
在功能上与伪双端口RAM与FIFO较为相似,两者有何区别?
FIFO也是一个端口只读,另一个端口只写。FIFO与伪双口RAM的区别在于,FIFO为先入先出,没有地址线,不能对存储单元寻址;而伪双口RAM两个端口都有地址线,可以对存储单元寻址。伪双端口RAM主要用于高速数字信号处理,如通讯协议、图像处理等,因为它可以实现非常快速的读/写操作。而FIFO常用于缓冲和转换两个数据流之间的数据,例如音视频捕捉、交换机队列、路由器缓存等应用场景。实际上FIFO可由伪双端口RAM例化而成。
RAM和FIFO中的深度(Depth)和宽度(Width)指的是什么?
除了弄清单端口与双端口的区别,还得理解存储器最重要的两个参数——位宽、深度。存储器深度和位宽都是数字电路中的重要参数,两者具有不同的含义。
存储器深度(Depth)指存储器中可存储数据的位数或存储单元的个数(比如2的n次幂),即存储器的容量大小。而位宽(Width)则指存储器中每个存储单元所能存储的二进制值的位数。
例如,有一个 8 位宽、256 深度的存储器,意味着这个存储器可以存储 256 个 8 位的二进制数据。
本文将会从4位宽、16深度的三种存储器为例展开设计。
输入只有一组数据线和一组地址线,读写共用地址线,输出只有一个端口。这意味着,如果CPU需要读取RAM中的数据并将其写入另一个位置,必须先执行读取操作,然后执行写入操作。
实现一个深度为16、位宽为4的单端口RAM。
//深度为16、位宽为4的端端口RAM
module ram_single_port#(
parameter DATA_WIDTH = 4,//RAM数据位宽
parameter ADDR_WIDTH = 4,//RAM地址位宽
parameter DEPTH = 16 //RAM深度
)(
input clk,
input rst_n,
input wr_en,//写使能
input [ADDR_WIDTH-1:0] addr,//读写共用地址
input [DATA_WIDTH-1:0] wr_data,//写入数据
output reg [DATA_WIDTH-1:0] re_data//读出数据
);
//定义一个深度为16、位宽为4的单端口RAM
reg [DATA_WIDTH-1:0] ram_data [DEPTH-1:0];
//数据写入存储在RAM中
genvar i;
generate
for(i=0;i<DEPTH;i=i+1)
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
ram_data[i] <= 0;
end
else if(wr_en) begin //使能高电平时写入
ram_data[addr] <= wr_data;
end
else begin
ram_data[addr] <= ram_data[addr];
end
end
endgenerate
//读出数据
always@(*) begin
if(wr_en) begin
re_data <= 0;
end
else if(!wr_en) begin
re_data <= ram_data[addr];
end
else begin
re_data <= re_data;
end
end
endmodule
`timescale 1ns/1ps;////仿真时间单位1ns 仿真时间精度1ps
module ram_single_port_tb();
parameter DATA_WIDTH = 4;
parameter ADDR_WIDTH = 4;
parameter DEPTH = 16;
//信号申明
reg clk;
reg rst_n;
reg wr_en;
reg [ADDR_WIDTH-1:0] addr;
reg [DATA_WIDTH-1:0] wr_data;
wire[DATA_WIDTH-1:0] re_data;
//例化
ram_single_port u_ram_single_port(
.clk (clk),
.rst_n (rst_n),
.wr_en (wr_en),
.addr (addr),
.wr_data (wr_data),
.re_data (re_data)
);
//时钟生成
always #5 clk = ~clk;
//信号初始化以及赋值
integer i;
initial begin
clk = 1;
rst_n = 1;
wr_en = 0;
wr_data = 0;
#5;
rst_n = 0;
wr_en = 0;
#5;
rst_n = 1;
wr_en = 1;
for (i = 0; i < DEPTH; i = i + 1) begin//写入数据赋初值
@(posedge clk) begin
addr = i;
wr_data = wr_data + 1;
end
end
#5;wr_en = 0;
for (i = 0; i < 64; i = i + 1) begin//设置读出地址
@(posedge clk) begin
addr = i;
end
end
end
endmodule
以165ns为分界线:左侧为数据的写入,此时无法进行读取数据;右侧为数据的读取,此时无法进行写入数据。RAM存储的数据为0-15总计16个数字,按照为此依次递增。
输入有两组地址线和两组数据线,输出有两个端口。所以双口RAM两个端口都分别带有读写端口,可以在没有干扰的情况下进行读写,彼此互不干扰。
实现一个深度为16、位宽为4的真双端口RAM。
//深度为16、位宽为4的真双端口RAM
module ram_true_dual_port #(
parameter DATA_WIDTH = 4,//RAM数据位宽
parameter ADDR_WIDTH = 4,//RAM地址位宽
parameter DEPTH = 16 //RAM深度
)(
input clk,
input rst_n,
input wr_en_a,//a端写使能
input re_en_a,//a端读使能
input [ADDR_WIDTH-1:0] addr_a,//a端地址
input [DATA_WIDTH-1:0] data_in_a,//a端输入数据
output reg [DATA_WIDTH-1:0] data_out_a,//a端输出数据
input wr_en_b,//b端写使能
input re_en_b,//b端读使能
input [ADDR_WIDTH-1:0] addr_b,//b端地址
input [DATA_WIDTH-1:0] data_in_b,//b端输入数据
output reg [DATA_WIDTH-1:0] data_out_b//b端输出数据
);
//定义一个深度为16、位宽为4的真双端口RAM
reg [DATA_WIDTH-1:0] ram_data [DEPTH-1:0];
//a端、b端数据写入存储在RAM中
genvar i;
generate
for(i = 0; i < DEPTH; i = i + 1)
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
ram_data[i] = 0;
end
else if (wr_en_a) begin//a端使能高电平时写入
ram_data[addr_a] <= data_in_a;
end
else if (wr_en_b) begin //b端使能高电平时写入
ram_data[addr_b] <= data_in_b;
end
else begin
ram_data[addr_a] =ram_data[addr_a];
ram_data[addr_b] =ram_data[addr_b];
end
end
endgenerate
//a端、b端读出数据
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_out_a <= 0;
data_out_b <= 0;
end
else if(re_en_a)begin
data_out_a <= ram_data[addr_a];
data_out_b <= data_out_b;
end
else if(re_en_b)begin
data_out_b <= ram_data[addr_b];
data_out_a <= data_out_a;
end
else begin
data_out_a <= data_out_a;
data_out_b <= data_out_b;
end
end
endmodule
`timescale 1ns/1ps;////仿真时间单位1ns 仿真时间精度1ps
module ram_true_dual_port_tb();
parameter DATA_WIDTH = 4;
parameter ADDR_WIDTH = 4;
parameter DEPTH = 16;
//信号申明
reg clk;
reg rst_n;
reg wr_en_a;
reg re_en_a;
reg [ADDR_WIDTH-1:0] addr_a;
reg [DATA_WIDTH-1:0] data_in_a;
wire [DATA_WIDTH-1:0] data_out_a;
reg wr_en_b;
reg re_en_b;
reg [ADDR_WIDTH-1:0] addr_b;
reg [DATA_WIDTH-1:0] data_in_b;
wire [DATA_WIDTH-1:0] data_out_b;
//例化
ram_true_dual_port u_ram_true_dual_port(
.clk (clk),
.rst_n (rst_n),
.wr_en_a (wr_en_a),
.re_en_a (re_en_a),
.addr_a (addr_a),
.data_in_a (data_in_a),
.data_out_a (data_out_a),
.wr_en_b (wr_en_b),
.re_en_b (re_en_b),
.addr_b (addr_b),
.data_in_b (data_in_b),
.data_out_b (data_out_b)
);
//时钟生成
always #5 clk = ~clk;
//信号初始化以及赋值
integer i;
initial begin
clk = 1'b1;
rst_n = 1'b0;
wr_en_a = 1'b0;
wr_en_b = 1'b0;
re_en_a = 1'b0;
re_en_b = 1'b0;
addr_a = 1'b0;
addr_b = 1'b0;
data_in_a = 1'b0;
data_in_b = 1'b0;
#30
rst_n = 1'b1;
//A写A读
#20;
wr_en_a= 1'b1;
@(posedge clk)
for (i=0;i<4;i=i+1)begin
@(posedge clk) begin
addr_a = i;
data_in_a = data_in_a + 1;
end
end
#20;
wr_en_a = 1'b0;
re_en_a = 1'b1;
@(posedge clk)
for (i=0;i<4;i=i+1)begin
@(posedge clk)begin
addr_a = i;
end
end
//A写B读
#20;
re_en_a = 1'b0;
wr_en_a = 1'b1;
@(posedge clk)
for (i=4;i<8;i=i+1)begin
@(posedge clk) begin
addr_a = i;
data_in_a = data_in_a + 1;
end
end
#20;
wr_en_a = 1'b0;
re_en_b = 1'b1;
@(posedge clk)
for (i=4;i<8;i=i+1)begin
@(posedge clk)begin
addr_b = i;
end
end
//B写A读
#20;
re_en_b = 1'b0;
wr_en_b = 1'b1;
@(posedge clk)
for (i=8;i<12;i=i+1)begin
@(posedge clk) begin
addr_b = i;
data_in_b = data_in_b + 2;
end
end
#20;
wr_en_b = 1'b0;
re_en_a = 1'b1;
@(posedge clk)
for (i=8;i<12;i=i+1)begin
@(posedge clk)begin
addr_a = i;
end
end
//B写B读
#20;
re_en_a = 1'b0;
wr_en_b = 1'b1;
@(posedge clk)
for (i=12;i<16;i=i+1)begin
@(posedge clk) begin
addr_b = i;
data_in_b = data_in_b + 2;
end
end
#20;
wr_en_b = 1'b0;
re_en_b = 1'b1;
@(posedge clk)
for (i=12;i<16;i=i+1)begin
@(posedge clk)begin
addr_b = i;
end
end
end
endmodule
(1)整体
50ns—170ns:A写入数据A读出数据
170ns—290ns:A写入数据B读出数据
290ns—410ns:B写入数据A读出数据
410ns—530ns:B写入数据B读出数据
(2)ram_data
B端写入的数据与A端不同,A是连续的数据输入,B是间隔的数据输入(即是在自身的基础上不断+2得到的),整个RAM写入的数据如上所示。
(3)A写A读
可以看到data_in_a输入数据为1234,data_out_a输入数据为1234,A写A读正常。
(4)A写B读
可以看到data_in_a输入数据为5678,data_out_b输入数据为5678,A写B读正常。
(5)B写A读
可以看到data_in_b输入数据为2468,data_out_a输入数据为2468,B写A读正常。
(6)B写B读
可以看到data_in_b输入数据为ace0,data_out_b输入数据为ace0,B写B读正常。
输入有一组数据线,两组地址线,输出只有一个端口。伪双端口RAM可以提供并行读写操作。
实现一个深度为16、位宽为4的伪双端口RAM。
//深度为16、位宽为4的伪双端口RAM
module ram_simple_dual_port #(
parameter ADDR_WIDTH=4,
parameter DATA_WIDTH=4,
parameter DEPTH=16
)(
input clk,
input rst_n,
input wr_en, //写使能
input [ADDR_WIDTH-1:0] addr_a, //a端写地址
input [DATA_WIDTH-1:0] data_a,//a端写数据
input re_en, //读使能
input [ADDR_WIDTH-1:0] addr_b, //b端读地址
output reg [DATA_WIDTH-1:0] data_b//b端读数据
);
//定义一个深度为16、位宽为4的伪双端口RAM
reg [DATA_WIDTH-1:0] ram_data [DEPTH-1:0];
//a端写入数据存储在RAM中
genvar i;
generate
for(i = 0;i < DEPTH;i = i + 1)
if(!rst_n) begin
ram_data[i] <= 0;
end
else if (wr_en) begin
ram_data[addr_a] <= data_a;
end
else begin
ram_data[addr] <= ram_data[addr];
end
endgenerate
//b端读出数据
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
data_b <= 0;
end
else if(re_en) begin
data_b <= ram_data[addr_b];
end
else begin
data_b <= data_b;
end
end
endmodule
`timescale 1ns/1ps;////仿真时间单位1ns 仿真时间精度1ps
module ram_simpel_dual_port_tb();
parameter DATA_WIDTH = 4;
parameter ADDR_WIDTH = 4;
parameter DEPTH = 16;
//信号申明
reg clk;
reg rst_n;
reg wr_en;
reg re_en;
reg [ADDR_WIDTH-1:0] addr_a;
reg [ADDR_WIDTH-1:0] addr_b;
reg [DATA_WIDTH-1:0] data_a;
wire [DATA_WIDTH-1:0] data_b;
//例化
ram_simple_dual_port u_ram_simple_dual_port(
.clk (clk),
.rst_n (rst_n),
.wr_en (wr_en),
.re_en (re_en),
.addr_a (addr_a),
.addr_b (addr_b),
.data_a (data_a),
.data_b (data_b)
);
//时钟生成
always #5 clk = ~clk;
//信号初始化以及赋值
integer i;
initial begin
clk = 1'b1;
rst_n = 1'b0;
wr_en = 1'b0;
re_en = 1'b0;
addr_a = 1'b0;
addr_b = 1'b0;
data_a = 1'b0;
#30
rst_n = 1'b1;
#5
wr_en = 1'b1;
@(posedge clk)
for (i=0;i<DEPTH;i=i+1)begin
@(posedge clk) begin
addr_a = i;
data_a = data_a + 1;
end
end
#5 wr_en = 1'b0;
re_en = 1'b1;
@(posedge clk)
for (i=0;i<DEPTH;i=i+1)begin
@(posedge clk)begin
addr_b = i;
end
end
end
endmodule
(1)整体波形
205ns前,wr_en = 1,;205ns后,re_en = 1;以205ns为分界线,左右两侧分别是写数据和读数据,当时钟信号处于上升沿时,分别写入和读取当前地址的数据,但是写入数据与读写数据不能同时进行,因为此处设计的是simple_dual_port RAM,即伪双端口RAM。
(2)寄存器数据ram_data
在Testbench中,我们借用for循环,在时钟上升沿时触发使得写入的数据data_a存储到RAM寄存器ram_data中,如上图所示
(3)写数据
在90ns到110ns间是写入数据,此时读出数据停。止可以看到,在前半部分写入的地址addr_a = 4,写入数据data_a =5,所以在下一个上升沿将数据5写入ram_data4中,此时ram_data为12345成功写入。后续的数据同理。
(4)读数据
在205ns后是读出数据,此时写入数据停止。可以看到,在初始读出的地址addr_b = 0,此时ram_data =fedcba987654321,所以在下一个上升沿读出数据ram_data0,此时data_b成功读出1。后续的数据同理。
总的来说:
单端口RAM:只有一个口,此口可读可写,但不能同时读写,即写时不可读,读时不可写。
伪双端口RAM:两个口,每个口只会读(或写),AB可同时读写,但仅A写B读。
真双端口RAM:两个口,每个口都可读可写,AB可同时读写,A可写可读,B可写可读。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。