双端口RAM
分别用于读写数据,同时进行
当读使能端有效时可以读出来数据
当写使能端有效时可以覆写数据
读写并行操作
报错
1.reg必须在always里
这个不能assign,因为reg型不能assign,单端口的那个可以assign是因为其定义为了wire型,就不在always里进行,而是在运算后输出时用的,所以可以assign
这里定义成了reg,那么赋值就一定要写在always里
2.多个else if的操作覆盖
这个不能得到正确输出是因为,一个if里只能执行一次,即即使有多个else if,且都满足,也只进行一个判别条件里的(靠近顶层的),就是说如果读写信号都有效,也只会进行写操作,而屏蔽掉读操作,所以就会出问题,导致一直读不出来
解决方式就是把读和写分在两个always里,保证不会因为else if只会执行一个,而使另一个被屏蔽掉
3.未知情况的完善缺陷
只是这样依然会得不到期望的输出,2的情况是一直读不出来,即输出信号一直是0
3的情况是,由于没有对读使能端情况的完善,即只在读为1时,才会对输出信号做出修改,但是却没有说读为0时该怎么做,就会导致,在读为0时,锁存上一个读为1时读到的信号,这样就会导致在不想读时,一直输出有信号的读信号,就会出错,得不到预期的波形
解决方法就是完善各种使能端的情况,当读使能端为0时,就要把读的输出信号置0
对于写使能,写的功能就是改变RAM内部数据值
当为1时,进行覆写;为0时,保持原状态(可写可不写,但必须明白这种情况的处理是这样的);
对于读使能,功能就是改变读信号,读出信号
当为1时,进行读;为0时,显然不希望之前的信号依然保存,所以要进行置0操作(这时就必须要额外写)
补充:对于2的另一种解决方法
还是在一个always里,只不过不用else if了,就用if,保证每次都会对if里的条件进行判断,简便,但是在互斥事件时,会浪费一定的不必要的判定时间,但是对独立的事件,就是必要的
其他
本例中的阻塞与非阻塞
这里面用阻塞赋值,即=可行,是因为状态的转移不涉及上一个状态,所以可以直接改变
而读写时,也是可以直接读出想写的内容,而不是之前写的内容,即读写和原来写的是什么无关
关于循环
要用循环,就要定义integer量,integer不能综合,只是用来循环,简化代码
这里的循环就是用来初始化,简化代码用的,无法综合,因为没有元件符合,只是用来简化代码长度而诞生的,初始化的本质逻辑还是逐个赋0,即
如果有128个寄存器,就需要128行,而循环无所谓深度多少
另一种实现思路
由于使能端只能是0或1,所以用三目运算符可以简单清晰,明了的表示出各种情况及方法,不重不漏。
读写分离always,两个三目运算符考虑两个使能端的各种情况
同步FIFO
就是说读使能时,返回队头;不读,则返回0;
写使能时,如果不满,就接着写,满了,就让队头出,写在队尾;不写,保持原态;
空信号,是读的时候空,对写没影响,即往后写,但是改变读的结果,为0;
满信号,是写的时候满,对读没影响,即一直读队头,但是改变写的模式;
但是注意,这里的读出,就是取出队列,即读一个取一个;
根据双口RAM实现FIFO
双端口实现
深度即寄存器数量,位宽为WIDTH
对组地址信号的位宽,为深度对2取对数,向上取整
电路功能
FIFO
空满状态的判断
统计队列内部数据数量的计数器CNT,并根据计数器的大小判断空满状况。
如果CNT==0,则空,如果CNT==深度,则满
读写同时进行时,计数器数值不变;写入时,CNT+1;读出时,CNT-1;
这里是声明读写地址,以及队列深度的计数(采用二进制)
关于位宽
常见的两种含义,第一种的位宽用于数据结构的定义,第二种用于数据结构内部数据特性、大小的定义
一是位宽代表个数
即有多少位宽,就意味有几个数据
队列定义,寄存器堆定义,数组定义,都是这个意思,即[n-1:0],从0拉到n-1,表示这个数据结构里有n个数据,一位就代表了一个数据单元
用来统筹管理数据的记录情况
二是代表实际大小
即用于实际计数,内部存储超过2的数据,表示数据,存储大的数据都是这种方式,位宽越大,表示这个数据越大,就代表一个实际的、实在的数据,采用的二进制计数,位宽就是用的几位二进制来记录
地址信号的定义,队列长度计数,分频器信号的计数,都是这个意思,其都是代表的一个数据
读写地址的确定
写功能,得到写的地址,
首先要写使能为1,接着判断是否满了,如果满了,地址就不动了,即指向队尾;不满,就让地址往后+1
读功能,得到读取元素的地址
首先要读使能为1,接着判断是不是为空,为空,则输出'0,即当前队列没有元素可以读出来;不为空,则地址往后+1,表示当前这个地址已经读出来了
注意,这里就是一定要用非阻塞赋值了,即等到这个电路功能都实现了再变化
因为此时后续的操作,都需要当前指针(即此时的waddr,raddr)所指向的地址,如果这时候更新,就访问不到了,所以需要在都结束后再更新
当前队列长度的确定
这个就是只取一个else if,即同一状态向后的不同分支,只取一个
当又读又写的时候,长度不变
读的时候,长度减1;写的时候,长度+1;
未知情况,保持不变
满空判断
实例化,以双端RAM实现FIFO
需要注意,如果能写而且还没满,就可以实际接着往下写,但是如果能写但是已经满了,就不接着往后写了;读也是这样
想的是,先一直写,然后读,这时候队列长度就减小了,还可以接着往里面写,但是写指针却没有动,所以就会导致越界
一种可能的思路是,它要读就一直读,直到读到空;要写就一直写,直到写到满;
主要就是读的时候,如果读,就一直读,全部读完,而不是读一部分,使其有剩余
那么FIFO的使用方式就是
`timescale 1ns/1ns
/**********************************RAM************************************/
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
/**********************************SFIFO************************************/
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;
reg [$clog2(DEPTH) : 0] waddr;
reg [$clog2(DEPTH) : 0] raddr;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wfull <= 'd0;
rempty <= 'd0;
end
else begin
wfull <= waddr == raddr + DEPTH;
rempty <= waddr == raddr;
end
end
assign wenc = winc && !wfull;
assign renc = rinc && !rempty;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
waddr <= 'd0;
else if (wenc)
waddr <= waddr + 'd1;
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
raddr <= 'd0;
else if (renc)
raddr <= raddr + 'd1;
end
dual_port_RAM #(.DEPTH (DEPTH),
.WIDTH (WIDTH))
dual_port_RAM (
.wclk (clk ),
.wenc (wenc ),
.waddr (waddr),
.wdata (wdata),
.rclk (clk ),
.renc (renc ),
.raddr (raddr),
.rdata (rdata)
);
endmodule
输入信号里有时钟信号,复位信号,读写使能信号,输入信号,
输出信号里有满信号,空信号,输出的队头信号
输入的时候,就写使能打开,然后
同步FIFO是说读写的时钟是一致的
异步FIFO
第一部分是双口RAM,用于数据的存储
第二部分是数据写入控制器
第三部分是数据读取控制器
第四部分是读指针同步器
使用写时钟的两级触发器采集读指针,输出到数据写入控制其
第五部分是写指针同步器
比较空满,采用格雷码的比较来产生
格雷码是循环编码,