2023.4.6 把之前学习的内容汇总整理
大多数数据具有连续性,背靠背传输;需要较快的速度传输
两种解决方法:SRAM(两个时钟域通过其缓冲,这里不讲)、FIFO
同步FIFO
:读写时钟为同一时钟,时钟沿同时进行读写,内部所有逻辑为同步逻辑,常用于交互数据缓冲
异步FIFO
:读写时钟不同,相互独立
FIFO宽度
:一次读写数据的数据位
FIFO深度
:可以存储多少个N位的数据
空标志
:FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出(underflow
)。读写指针相等时,表示空,发生在复位或者读指针读完最后一个数追上写指针的时候。
满标志
:FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出(overflow
)。当读写指针再次相等,表示满。
读时钟
:读操作所遵循的时钟,在每个时钟沿来临时读数据。
写时钟
:写操作所遵循的时钟,在每个时钟沿来临时写数据。
写指针
:总是指向下一个将要被写入的单元,复位时,指向0
读指针
:总是指向当前要被读出的数据,复位时,指向第 1 个单元(编号为 0)
计数器复位时初始化为0,写操作计数器+1,读操作计数器-1,计数器为0表示空,计数器为深度时表示满。
缺点:读写不能同时操作,且计数器会占用额外的资源,当FIFO较大时,可能会降低读写的速度。
关键: 读写指针的变化、空满信号的产生
module sync_fifo
#(parameter DEPTH = 'd16, parameter WIDTH = 'd8)(
input clk,
input rst_n,
input wr_en,
input rd_en,
input [WIDTH-1:0] data_in,
output [WIDTH-1:0] data_out,
output empty,
output full
);
reg [$clog2(DEPTH):0] cnt; //计数器范围为0-16
reg [WIDTH-1:0] mem [DEPTH-1:0];
reg [$clog2(DEPTH)-1:0] waddr, raddr;
//写操作
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
waddr <= 0;
else if(wr_en & !full)begin
mem[waddr] <= data_in;
waddr <= waddr+1;
end
end
//读操作
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
raddr <= 0;
else if(rd_en & !empty)begin
data_out <= mem[raddr];
raddr <= raddr+1; //注意这里读地址也是+1
end
end
//更新计数器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 0;
else begin
case({wr_en, rd_en})
2'b00: cnt <= cnt;
2'b01: if(cnt!=0) cnt <= cnt - 1;
2'b10: if(cnt!=DEPTH) cnt <= cnt + 1;
2'b11: cnt <= cnt;
endcase
end
end
assign full = (cnt==DEPTH);
assign empty = (cnt==0);
endmodule
没有扩展的时候,空和满的时候,读写指针是完全相同的,无法进行判断
2^n
的FIFO,需要的读/写指针位宽为(n+1)
位,如对于深度为8的FIFO,需要采用4bit的计数器,0000 ~1000、1001~1111,MSB作为折回标志位,而低3位作为地址指针。module sync_fifo
#(parameter DEPTH = 'd16, parameter WIDTH = 'd8)(
input clk,
input rst_n,
input wr_en,
input rd_en,
input [WIDTH-1:0] data_in,
output [WIDTH-1:0] data_out,
output empty,
output full
);
reg [WIDTH-1:0] mem [DEPTH-1:0];
reg [$clog2(DEPTH):0] waddr, raddr;
//写操作
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
waddr <= 0;
else if(wr_en & !full)begin
mem[waddr[$clog2(DEPTH)-1:0]] <= data_in;
waddr <= waddr+1;
end
end
//读操作
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
raddr <= 0;
else if(rd_en & !empty)begin
data_out <= mem[raddr[$clog2(DEPTH)-1:0]]; //这里也可以用组合逻辑输出,实现read和data out同步输出
raddr <= raddr+1; //注意这里读地址也是+1
end
end
assign empty = (raddr == waddr);
assign full = (raddr == {~waddr[$clog2(DEPTH)], waddr[$clog2(DEPTH)-1:0]}); //最高位相反
endmodule
module ram_mod(
input clk,
input rst_n,
input write_en,
input [7:0]write_addr,
input [3:0]write_data,
input read_en,
input [7:0]read_addr,
output reg [3:0]read_data
);
reg [3:0] mem [7:0];
integer i;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
for(i=0;i<8;i=i+1)
mem[i]<=0;
end
else if(write_en)
mem[write_addr]<=write_data;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
read_data<=0;
end
else if(read_en)
read_data<=mem[read_addr];
end
endmodule
module dual_port_RAM #(parameter DEPTH = 16,
parameter WIDTH = 8)(
input wclk
,input wenc
,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
,input [WIDTH-1:0] wdata //数据写入
,input rclk
,input renc
,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
,output reg [WIDTH-1:0] rdata //数据输出
);
reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
always @(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata;
end
always @(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr];
end
endmodule
//这里用的是高位扩展法
module sfifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input clk ,
input rst_n ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output reg wfull ,
output reg rempty ,
output wire [WIDTH-1:0] rdata
);
wire wenc,renc;
assign wenc = winc && !wfull;
assign renc = rinc && !rempty;
reg [$clog2(DEPTH):0] waddr;
reg [$clog2(DEPTH):0] raddr;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
wfull<=0;
rempty<=0;
end
else begin
rempty<=(raddr==waddr);
wfull<=(waddr==raddr+DEPTH);
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
raddr<=0;
end
else if(renc)
raddr<=raddr+1;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
waddr<=0;
end
else if(wenc)
waddr<=waddr+1;
end
//例化双端口RAM
dual_port_RAM #(.DEPTH(DEPTH),.WIDTH(WIDTH))
ram(
.wclk(clk),
.wenc(wenc),
.waddr(waddr[DEPTH-1:0]), //注意这里地址位宽,最高位不是有效地址位
.wdata(wdata),
.rclk(clk),
.renc(renc),
.raddr(raddr[DEPTH-1:0]),
.rdata(rdata)
);
endmodule
格雷码:循环码,相邻两个数值之间只有一位发生改变,发生亚稳态的概率大大降低
二进制码 | 格雷码 |
---|---|
000 | 000 |
001 | 001 |
010 | 011 |
011 | 010 |
100 | 110 |
101 | 111 |
110 | 101 |
111 | 100 |
二进制码转化为格雷码:从最右边第一位开始,依次将每一位与左邻一位异或(XOR),作为对应格雷码该位的值,最左边一位不变;
gray = bin ^ (bin>>1);
格雷码转化为二进制码:从左边第二位起,将每位与左边一位解码后的值异或(XOR),作为该位解码后的值(最左边一位依然不变)。
reg [3:0] gray, bin;
bin[3] = gray[3];
bin[2] = bin[3] ^ gray[2];
bin[1] = bin[2] ^ gray[1];
bin[0] = bin[1] ^ gray[0];
或者在偶数个的时候,去掉头和尾的数字,中间部分也是满足要求
异步FIFO在2N个数据类就可以循环
空读满写
的错误状态,当然同步后的读写地址出错总是存在的。地址总线bus skew
一定不能超过一个周期,否则可能出现gray码多位数据跳变的情况,这个时候gray码就失去了作用,因为这时候同步后的地址已经不能保证只有1位跳变了。写指针000 -> 001,假设同步过去变成了000 -> 000,这样会判断为空,但实际上并不为空,此时不能读操作,还是可以写。不会对FIFO的功能造成影响。
module asyn_fifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input wclk ,
input rclk ,
input wrstn ,
input rrstn ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output wire wfull ,
output wire rempty ,
output wire [WIDTH-1:0] rdata
);
//双端口RAM的读写使能信号
wire wenc = winc && !wfull;
wire renc = rinc && !rempty;
parameter ADD_WIDTH = $clog2(DEPTH); //地址线宽度
//二进制地址加减
reg [ADD_WIDTH:0] wptr_bin,rptr_bin;
wire [ADD_WIDTH:0] wptr_bin_next,rptr_bin_next;
assign wptr_bin_next = wptr_bin + (winc&!wfull);
assign rptr_bin_next = rptr_bin + (rinc&!rempty);
//二进制码转换为格雷码
reg [ADD_WIDTH:0] wptr_gray,rptr_gray;
wire [ADD_WIDTH:0] wptr_gray_next,rptr_gray_next;
assign wptr_gray_next = wptr_bin ^ (wptr_bin>>1);
assign rptr_gray_next = rptr_bin ^ (rptr_bin>>1);
//更新二进制码和格雷码
always@(posedge wclk or negedge wrstn)begin
if(!wrstn)
{wptr_bin,wptr_gray} <=0;
else
{wptr_bin,wptr_gray}<={wptr_bin_next,wptr_gray_next};
end
always @ (posedge rclk or negedge rrstn) begin
if (!rrstn) begin
{rptr_bin,rptr_gray} <= 'd0;
end
else
{rptr_bin,rptr_gray} <= {rptr_bin_next, rptr_gray_next};
end
//同步格雷码
reg [ADD_WIDTH:0] wptr_gray_r,wptr_gray_rr;
always@(posedge rclk or negedge rrstn)begin //读时钟域同步写格雷码
if(!rrstn)
{wptr_gray_r,wptr_gray_rr} <=0;
else
{wptr_gray_rr,wptr_gray_r}<={wptr_gray_r,wptr_gray};
end
reg [ADD_WIDTH:0] rptr_gray_r,rptr_gray_rr;
always@(posedge wclk or negedge wrstn)begin //写时钟域同步读格雷码
if(!wrstn)
{rptr_gray_rr,rptr_gray_r} <=0;
else
{rptr_gray_rr,rptr_gray_r}<={rptr_gray_r,rptr_gray};
end
//空满标志的产生,利用格雷码判断
assign rempty = rptr_gray==wptr_gray_rr;
assign wfull = wptr_gray=={~rptr_gray_rr[ADD_WIDTH:ADD_WIDTH-1],rptr_gray_rr[ADD_WIDTH-2:0]};
//例化双端口RAM
dual_port_RAM #(.DEPTH(DEPTH),.WIDTH(WIDTH))
one(
.wclk(wclk),
.wenc(wenc),
.waddr(wptr_bin[ADD_WIDTH-1:0]), //注意这里地址
.wdata(wdata),
.rclk(rclk),
.renc(renc),
.raddr(rptr_bin[ADD_WIDTH-1:0]),
.rdata(rdata)
);
endmodule
二进制码和格雷码更新这段也可以按照下面这样写,但是不知道和分开定义一个wptr_gray_next,再寄存到wptr_gray有什么区别。这样写的话看上去简单一些。
//二进制地址加减
always@(posedge wclk or negedge wrstn)begin
if(!wrstn)
wptr_bin <= 0;
else if(wenc)
wptr_bin <= wptr + 1;
end
always@(posedge rclk or negedge wrstn)begin
if(!rrstn)
rptr_bin <= 0;
else if(renc)
rptr_bin <= rptr + 1;
end
//二进制码转格雷码
always@(posedge wclk or negedge wrstn)begin
if(!wrstn)
wptr_gray <= 0;
else
wptr_gray <= wptr_bin ^ (wptr_bin>>1);
end
always@(posedge rclk or negedge wrstn)begin
if(!wrstn)
rptr_bin <= 0;
else if(renc)
rptr_bin <= rptr_bin ^ (rptr_bin>>1);
end
一般来说,在不连续传输的写入情况下,才考虑FIFO深度问题,因为假设是连续写入,FIFO总是会满的
在正常情况下,应该是这样的:空闲—Burst突发—空闲—Burst突发—空闲—Burst突发
背靠背传输:空闲—Burst突发—Burst突发—空闲—Burst突发—空闲。
如果接收方没法接收所有数据,那么剩余的数据可以被存储在FIFO内部且不会溢出,也就是我们要求的FIFO深度的意思。
在SDRAM的应用中,我们通常使用的读写FIFO是突发长度的2倍,比如突发长度为256,那FIFO的深度设置为512,使得FIFO始终保持半满的状态。可以保证数据的传输。
burst_length
:表示这段时间写入的数据量
burst_length/wclk
:写入数据的时间
(X/Y)*rclk
:每Y个时钟读出X个数据,说明读出效率不是100%,要在rclk基础上打折扣,速度没有rclk
depth
:写入和读出两者之差为FIFO中残留的数据,这个也就是理论上的FIFO的最小深度。
depth = burst_length - (burst_length/wclk) * ((X/Y)*rclk)
传输数据个数 = 时间 * 速度(频率)
深度 = (读写速率差) * (写入数据需要的时间)
传输数据个数:4Kbit/8bit =500 word
写入500个word所需时间:500/100
该时间内读取的个数:500/100*95
(1)深度 = 写个数 - 读个数 = 500 - 500/100 * 95 = 25
(2)深度 = (100-95) * (500/100) = 25
FIFO用于缓冲块数据流,一般用在快时钟域写慢时钟域读
FIFO深度 /(写入速率 - 读出速率)= FIFO被填满时间 > 数据包传送时间= 数据量 / 写入速率
FIFO写满的时间 > 数据包传输的时间
FIFO深度 = (写入速率 - 读出速率)* 数据包传送时间
100000 / 50MHz = 1/ 500 s = 2ms
(50MHz - 40MHz) * 1/500 = 20k
写速率fA=80MHz,读速率fB=50MHz,突发长度Burst Length = 120,读写之间没有空闲周期,是连续读写一个突发长度。
120-120/80*50 = 120 - 75 = 45
同case1,两次连续读写之间通常有延迟
写速率fA=80MHz,读速率fB=50MHz,突发长度Burst Length = 120
两个连续写入之间的空闲周期为=1,两个连续读取之间的空闲周期为=3
写使能占得百分比为=50%=1/2,读使能占得百分比为=25%=1/4
答: 两个连续写入之间的空闲周期为1的意思是,每写入一个数据,要等待一个周期,再写入下一个数据。这也可以理解为每两个周期,写入一个数据。同理每四个周期,读取一个数据。相当于读写频率降低了。
120-120/40*12.5=82.5=83
写进去的数据总是被读走了,所以FIFO深度为1即可
写速率fA=30MHz,读速率fB=50MHz,突发长度Burst Length = 120
两个连续写入之间的空闲周期为=1,两个连续读取之间的空闲周期为=3
120-120/30*12.5=20(实际上这道题目写速率比读快)
读写为同一时钟,可以不需要FIFO,直接写入的值给输出。如果读写时钟存在相位差,那么FIFO深度为1即可。
写速率fA= 50MHz,读速率fB=50MHz,突发长度Burst Length = 120
两个连续写入之间的空闲周期为=1,两个连续读取之间的空闲周期为=3
120-120/25*12.5=60(实际上这道题目写速率比读快)
考虑背靠背情况:160-160*8/10=32
直接根据地址是否相等不能判断空或者满。
对指针增加一位最高有效位MSB,n位指针,n-1位表示访问FIFO所需的地址位数。n位完全相同表示“空”;MSB不同,n-1位完全相同表示“满”