资源描述
资料内容仅供您学习参考,如有不当或者侵权,请联系改正或者删除。
同步缓冲器(FIFO)的设计与实现
姓名:
学号: 01 022102
班级: 级测控1班
院系: 控制系
专业: 测控技术与仪器
同组人姓名:
( 说明: 我们三个人前面的报告部分是一样的, 因为课设基本是三个人商议完成, 因此就感觉报告部分没什么不同的就只写了一份报告)
目 录
1原理与系统设计 3
2设计思想 4
3源码与注释 5
4仿真 12
5综合 15
6心得体会与建议 19
1 原理与系统设计
FIFO( First In First Out) ——是一种能够实现数据先入先出的存储器件。FIFO就像一个单向管道, 数据只能按固定的方向从管道一头进来, 再按相同的顺序从管道另一头出去, 最先进来的数据必定是最先出去。FIFO被普遍用作数据缓冲器。
FIFO的基本单元是寄存器, 作为存储器件, FIFO的存储能力是由其内部定义的存储寄存器的数量决定的。本题中所设计的是同步FIFO( 即输出输入端时钟频率一致) , 异步复位, 其存储能力为( 16x8) , 输出两个状态信号: full与empty, 以供后继电路使用。
根据系统要求, 画出的系统框图, 如图1所示
图1同步FIFO框图
端口说明:
输入: in_data: 输入数据端口, 位宽为8位;
read_n: 读使能端, 当read_n=0时, 能够读出数据;
write_n: 写使能端, 当write_n=0时, 能够写入数据;
clock: 时钟信号, 在时钟的正边沿进行采样;
reset_n: 复位信号, 当reset_n=0时, 计数器及读写都被清零( 即: 读写地址指针都指向0)
输出: out_data: 输出数据端口, 位宽为8位;;
full: FIFO状态信号, 当full=1时, 表明该FIFO存储器已经写满;
empty: FIFO状态信号, 当empty=1时, 表明该FIFO存储器已经读空;
FIFO满的情况下, 不能再写, 写指针不能加1;
FIFO空的情况下, 不能再读, 读指针不能加1;
2 设计思想
由以上的系统框图和端口分析, 我们将设计的重点定在了解决以下三个核心问题上:
1. FIFO的存储体如何表示?
2. 如何实现”先进先出”的逻辑功能?
3. 如何知道FIFO内部使用了多少, 是满是空?
针对以上三个问题, 我们所采取的方法是:
1. 定义一个16×8的二维数组来表示FIFO的存储体。
2. 为了实现”先进先出”的逻辑功能, 我们定义了”读指针”及”写指针”, 分别用来指示读操作与写操作的位置。
3. 为了表示FIFO是满还是空, 我们定义了一个计数器, 用以标志FIFO已使用了多少空间。
在解决了以上三个重点问题以后, 针对同步FIFO的逻辑功能, 我们拟定了以下一个结构图, 如图2所示:
图2 FIFO设计结构图
3.源码与注释
3.1源代码
我们在完成了之前两步的准备工作之后, 进行了源码的设计, 具体的代码如下:
`define DEL 1 //为了使仿真接近真实情形, 我们定义了从时钟到输出的延时
module sfifo(clock,reset_n,in_data,read_n,write_n,out_data,full,empty);
//输入信号
input clock; //输入时钟
input reset_n; //复位信号, 低有效
input[7:0] in_data; //输入的数据
input read_n; //读控制信号, 低有效
input write_n; //写控制信号, 低有效
//输出信号
output[7:0] out_data; //FIFO的输出数据
output full; //FIFO满标志信号
output empty; //FIFO空标志信号
//信号声明
reg [7:0] out_data;
reg [7:0] fifo_mem[15:0]; //FIFO存储体即8*16存储器, 用数组表示
reg [4:0] counter; //计数器表示FIFO中已用了多少
reg [3:0] rd_pointer; //FIFO读指针, 指向下次读操作的地址
reg [3:0] wr_pointer; //FIFO读指针, 指向下次读操作的地址
//赋值声明, 给出满标志与空标志的实现
assign #`DEL full=(counter==16)?1'b1:1'b0;
assign #`DEL empty=(counter==0)?1'b1:1'b0;
//本模块实现读指针、 写指针和计数器的功能
always@(posedge clock or negedge reset_n)
begin
if(~reset_n)
begin //计数器及读、 写指针清零
rd_pointer<=#`DEL 4'b0;
wr_pointer<=#`DEL 4'b0;
counter<=#`DEL 5'b0;
end
else
begin
if(~read_n)
begin
//如果FIFO为空, 不能再读, 并报错
if(counter==0)//检查fifo是否溢出(empty)
begin
$display("\nERROR at time %0t:",$time);
$display("FIFO Underflow\n");
$stop; //终止系统任务, 用于调试
end
//读有效, 写无效时, 计数器减1
if(write_n)
begin
counter<=#`DEL counter-1;
end
//如果读指针已指到最后一个位置, 则返回起始位置
if(rd_pointer == 15)
rd_pointer <= #`DEL 4'b0;
else
rd_pointer <= #`DEL rd_pointer + 1;
end
if(~write_n)//检查fifo是否溢出(full)
begin
if(counter>=16)
begin
$display("\n ERROR at time %0t:", $time);
$display("FIFO overflow\n");
$stop;
end
if(read_n)//写有效, 读无效时, 计数器加1
begin
counter <= #`DEL counter + 1;
end
if(wr_pointer == 15)//如果写指针已指到最后一位, 则返回起始位置
wr_pointer <= #`DEL 4'b0;
else
wr_pointer <= #`DEL wr_pointer + 1;
end
end
end
always@(posedge clock)//本模块实现数据的读写功能
begin
if(~write_n)
begin
fifo_mem[wr_pointer] <= #`DEL in_data;
end
if(~read_n)
begin
out_data <= #`DEL fifo_mem[rd_pointer];//读取数据
end
end
endmodule
3.2测试文件
本设计中为了让输入激励能够完整地测试出设计的功能, 以证明FIFO确实能起到数据缓冲的作用, 因而要测试当读写速度不一致的情况, 即要仿真写速度大于读速度的情形以及读速度大于写速度的情形。
测试文件中:
异步复位如下进行: reset_n=1; #20 reset_n=0; #20 reset_n=1;
时钟信号如下产生: always #100 clock<=~clock;
写入数据递增加1产生: in_data <= in_data + 1;
而编写测试文件的核心问题在于: 在同一文件中如何既能仿真写快与读的情形又能仿真读快于写的情形?
对此, 我们的想法: 先让写快于读以达到满的状态( full=1) , 而后让读快于写以达到排空的状态( empty=1)
由此我们定义了两个状态信号: fast_read: fast_read=1时以高速度进行读操作
fast_write:fast_write=1时以高速度进行写操作
又为了让读和写的速度产生差异, 我们定义了一个周期计数信号cycle_count(其周期计数的值为......01010101......),它用来控制生成读写使能信号, 控制方式为: 当fast_write=1时, 只要FIFO 非满就使写入, 得到写使能信号;
在非空的情况下, 当cycle_count==1时才产生读使能信号;
当fast_read=1时, 只要FIFO非空就使读取, 得到读使能信号;
在非满的情况下, 当cycle_count==1时才产生写使能信号;
从而达到了让快的一方速度是慢的一方速度2倍的效果。
在解决了以上核心问题之后, 具体的测试代码如下:
//DEFINES
`define DEL 1 //时钟到输出的延时
module test_sfifo(clock,reset_n,in_data,read_n,write_n,out_data,full,empty);
//INPUTS
input [7:0]out_data;
input empty,full;
//OUTPUTS
output clock,reset_n,read_n,write_n;
output [7:0]in_data;
//信号声明, 这些信号应与测试模块中的端口信号一一对应
reg clock;
reg reset_n;
reg[7:0] in_data; //输入到端口in_data的激励信号
reg read_n;
reg write_n;
wire[7:0] out_data; //从端口out_data输出的信号
wire full;
wire empty ;
//定义需要的一些信号
integer fifo_count; //记录FIFO中的字节数, 定义为实型整数
reg[7:0] exp_data; //期望从FIFO输出的数据
reg fast_read; //标志以高速度进行读操作
reg fast_write; //标志以高速度进行写操作
reg filled_flag; //标志FIFO已填满
reg cycle_count; //周期计数, 用来生成读写控制信号
//对FIFO进行实例化
sfifo Sfifo(
.clock(clock),
.reset_n(reset_n),
.in_data(in_data),
.read_n(read_n),
.write_n(write_n),
.out_data(out_data),
.full(full),
.empty(empty));
initial begin
in_data=0;
exp_data=0;
fifo_count=0;
read_n=1;
write_n=1;
filled_flag=0;
cycle_count=0;
clock=1;
//写速度大于读速度
fast_write=1;
fast_read=0;
//复位
reset_n=1;
#20 reset_n=0;
#20 reset_n=1;
//初始情况下, FIFO应该为空, 即empty==1且full==0
if(empty!==1)
begin
$display("\nERROR at time %0t:",$time);
$display("After reset,empty status not asserted\n");
//报错
$stop;
end
if(full!==0)
begin
$display("\nERROR at time %0t:",$time);
$display("After reset,empty status not asserted\n");
$stop;
end
end
//生成时钟信号
always #100 clock<=~clock;
//对FIFO中的字节数进行计数。每次写操作时, 计数加1; 读操作时计数减1
always@(posedge clock)
begin
if(~write_n&&read_n) //写但不读
fifo_count<= fifo_count + 1;
else if(~read_n && write_n) //读但不写
fifo_count<= fifo_count - 1;
end
//检查输出数据是否是期望的数据。如果不是, 则报错
always@(negedge clock) //读写控制信号和输入信号在下降沿产生, 而采样在上升沿完成, 如此可防止锁错数据
begin
if(~read_n && (out_data !== exp_data))begin
$display("\nERROR at time %0t:",$time); //$time为当前仿真时间
$display(" Expected data out = %h",exp_data);
$display(" Actual data out = %h\n",out_data);
$stop;
end
if((fast_write || (cycle_count & 1'b1)) && ~full)
begin//只要非满就使写入, 得到写控制信号
write_n <= 0;
//生成输入数据
in_data <= in_data + 1;
end
else
write_n <= 1;
//得到读控制信号, 并给出期望的数据
if((fast_read || (cycle_count & 1'b1)) && ~empty)
begin//在非空的情况下, 当cycle==1时才产生读控制信号
read_n <= 0;
exp_data <= exp_data + 1;//由于写入数据为递增加1的, 因此exp_data同意也是递增加1的
end
else
read_n <= 1;
if(full)
begin//如果fifo已满, 则使读速度大于写速度
fast_read <= 1;
fast_write <= 0;
//设置写满标志
filled_flag <= 1;
end
if(filled_flag && empty)//如果已清空fifo, 则结束
begin
$display("\nSimulation complete - no errors");
$finish;//仿真结束
end
//对计算周期进行计数
cycle_count <= cycle_count + 1;//由于默认cycle_count为1位变量, 因此cycle的值实质为'……010101……’
end
//check all of the status signals with each change of fifo_count
always@(fifo_count)
begin//wait a moment to evaluate everything
#`DEL;
#`DEL;
#`DEL;
case(fifo_count)
0:begin
if((empty !== 1) || (full !== 0))
begin
$display("\nERROR at time %0t:",$time);
$display(" fifo_count = %h",fifo_count);
$display(" empty = %b",empty);
$display(" full = %b\n",full);
//Use $stop for debugging
$stop;
end
if(filled_flag == 1)
begin//the fifo has filled and emptied
$display("\nSimulation complete - no errors\n");
$finish;
end
end
16:begin
if((empty !== 0) || (full !== 1))
begin
$display("\nERROR at time %0t:",$time);
$display(" fifo_count = %h",fifo_count);
$display(" empty = %b",empty);
$display(" full = %b\n",full);
//Use $stop for debugging
$stop;
end
//如果fifo写满, 则给出写满标志, 此时, 读速度大于写速度
filled_flag <= 1;
fast_write <= 0;
fast_read <= 1;
end
default:begin
if((empty !== 0) || (full !== 0))
begin
$display("\nERROR at time %0t:",$time);
$display(" fifo_count = %h",fifo_count);
$display(" empty = %b",empty);
$display(" full = %b\n",full);
//Use $stop for debugging
$stop;
end
end
endcase
end
endmodule
.4 仿真
4.1波形仿真(仿真过程使用ModlSim SE 6.0进行)
波形图中端口信号说明:
empty: FIFO状态信号, 当empty=1时, 表明该FIFO存储器已经读空;
full: FIFO状态信号, 当full=1时, 表明该FIFO存储器已经写满;
clock: 时钟信号, 在时钟的正边沿进行采样;
reset_n: 复位信号, 当reset_n=0时, 计数器及读写都被清零( 即: 读写地址指针都指向0)
read_n: 读使能端, 当read_n=0时, 能够读出数据;
write_n: 写使能端, 当write_n=0时, 能够写入数据;
in_data: 输入数据端口, 位宽为8位;
out_data: 输出数据端口, 位宽为8位;
fifo_count: 记录FIFO中的字节数, 定义为实型整数
exp_data: 期望从FIFO输出的数据
fast_read: 标志以高速度进行读操作
fast_write: 标志以高速度进行写操作
filled_flag: 标志FIFO已填满
cycle_count: 周期计数, 用来生成读写控制信号
图1 写快于读直至写满的波形图
图2 读快于写由满直至读空的波形图
图3 仿真全过程波形图
4.2 时序, 逻辑分析
图1 写快于读直至写满的波形图的分析:
仿真开始时, 由于fast_write=1, fast_read=0,因此: 只要FIFO 非满就使写入, 得到写使能信号/在非空的情况下, 当cycle_count==1时才产生读使能信号.因此写速度是读速度的2倍( 即写入2个数据, 只读取一个数据) , 当FIFO写满后, 这时full=1, 填满信号filled_flag由0变为了1.
图2 读快于写由满直至读空的波形图的分析:
由于FIFO在图1中所示过程已填满, 因此接着必须使读速度大于写速度, 即fast_read=1,fast_write=0,此时: 只要FIFO非空就使读取, 得到读使能信号/在非满的情况下, 当cycle_count==1时才产生写使能信号.因此读速度是写速度的2倍( 即读取2个数据, 只写入一个数据) , 在此过程中full=0, filled_flag保持为1, 最终FIFO排空时, empty=1.
图3 仿真全过程波形图分析:
由仿真全过程的波形图来看: 在reset_n=0后, 计数器及读写地址指针都被清0, 说明达到了异步复位的要求; 由read_n及write_n值的变化与读写数据的波形来看, read_n及write_n起到了读写使能控制的作用; 由波形可知读写操作都是在时钟clock的上升沿完成, 说明FIFO是同步的; 对比full与empty的值与FIFO的满空状况来看, full与empty能正确反映FIFO的实际状态。
由上述分析能够验证: 本次设计的器件能满足同步FIFO的逻辑功能。
.5 综合(综合过程使用LeonardoSpectrum .1b进行)
(考虑到使用不同的器件库,综合出来的电路图不同,说明一下:以下综合使用的器件库为MAX3000A)
5.1顶层综合电路示意图
图1
图2
注: 顶层综合电路图由图1与图2两部分构成, 其由三个端口相连, 它们依次是: Not_write_n , Not_reset_n , Not_read_n
5.2门级综合电路示意图
图1
注: 此为顶层图中读写地址指针产生模块
注: 此为顶层图中的ram存储器部分的门级图
5.3输入输出描述
输入: in_data: 输入数据端口, 位宽为8位;
read_n: 读使能端, 当read_n=0时, 能够读出数据;
write_n: 写使能端, 当write_n=0时, 能够写入数据;
clock: 时钟信号, 在时钟的正边沿进行采样;
reset_n: 复位信号, 当reset_n=0时, 计数器及读写都被清零( 即: 读写地址指针都指向0)
输出: out_data: 输出数据端口, 位宽为8位;;
full: FIFO状态信号, 当full=1时, 表明该FIFO存储器已经写满;
empty: FIFO状态信号, 当empty=1时, 表明该FIFO存储器已经读空;
6. 心得体会与建议( 小组成员: 刘鑫)
经过这次课程设计, 确实学到了不少东西! 不光是知识上的长进, 其它方面也得到了提高!
上完第一节介绍课, 在BBS上看了学长的建议说是要选一个模拟的题就比较有挑战性, 能够学不少东西。就有一点想挑个模电的题, 不过后来还是抽中了数电的题。后来跟同学们一样都被认为抽到数电是很幸运的! 因为确实不是一件简单的事情!
我们组的课题是”FIFO同步缓冲器的设计”, 过了一个暑假都不记得缓冲器是什么了, 于是又拉出厚厚的数电的书开始翻, 看到了寄存器, 移位寄存器, 计数器这些东西, 觉得有点熟悉了, 可是到具体的这个16*8的缓存器是个什么概念, 我们的意见还是有点分歧, 于是去网上找这些概念, 弄明白这意思。就看到有的说同步就是同一个时钟脉冲, 有的又说同步不是这个概念, 后来我们就一起商讨了一下, 统一了题目的意思: 同步FIFO( 即输出输入端时钟频率一致) , 异步复位, 其存储能力为( 16x8) , 输出两个状态信号: full与empty, 以供后继电路使用。
弄清了题目的意思之后, 就开始按照设计的程序来完成各部分的内容。首先我们在网上找到了一些关于IC课程设计的流程介绍, 我们也就使根据那些介绍的流程来一步步完成我们的任务。在查资料的同时才发现, 因特网真的能够帮我们不少的忙, 其实我们平时的好多资料都能够借助网络来查询, 因此这次还下载了不少这方面的知识以备后用。
我们设计的这个缓冲器是以寄存器为基本单位, 其存储能力是由其内部定义的存储寄存器决定的。这样我们经过这个课设就把数点的内容再复习了一下, 这学期就要开设书店的电工实验, 这也给我们带来了一点知识方面的准备!
在明确了设计思想, 画完结构图后, 就要开始写代码了。虽然在课堂上, 郑老师给我们讲了不少verilog语言的基本用法, 但涉及到自己来写代码还是不太在行, 因此第一步还是找来些资料学习verilog。因此说经过这次可设掌握verilog的用法, 进一步熟悉verilog,对以后的学习和工作都有不少帮助! 这中间当然也会遇到不少自己不懂的东西啦, 也锻炼了自己自学的能力和钻研的精神。
写完代码后当然就是经过软件来验证我们的思想啦。这当然少不了要熟悉软件啦, 以前还没有接触这些软件, 因此这又是一大收获, 熟悉了软件的使用这对以后的工作和学习当然有不少好处了。
不过由于我们的思路还比较清晰, 编译的时候就没有发现什么错误, 只是少部分的语法错误, 不过那也害惨我们了。也是找了好久才找出来啊! 当时大家都讨论得很积极, 考虑了好多方面, 后来当我们发现那个错误是因为我们输代码的时候输错了一个字母, 虽然有点内疚因为是自己太粗心打错了, 但全组人当时都叫了起来, 很是高兴, 旁边的人还用异样的眼光看着我们, 似乎觉得我们不正常。那时候真的体会到成功的甘甜, 更觉得集体的智慧真是伟大!
在课设之前我们就明确了这是一个集体的任务, 不能单独的靠哪一个人去完成。也不能说是哪一个人的任务。虽然整个的课设步骤有点杂, 我们没有细化到谁负责哪一块, 可是每个步骤都是经过我们三个人的商讨, 包括纠错都是在一起议论, 每遇到一个问题都是我们三人在一起琢磨, 都发表自己的意见, 最后才达成了许多共识, 共同完成了这份课程设计! 这也就是我觉得经过这次的课程设计体会到的最大的一点心得!
总的来说, 这次的课程设计并没有开始想象的那么难, 但确实能够从中学到不少东西, 对自己动手能力的训练还有独立处理问题的能力以及集体合作精神的培养, 都是极好的一次机会。对个人自学能力的培养更有意义。因此我觉得这种设计性的课程有必要一直开下去, 不过我觉得应该再多讲一些关于设计流程方面的问题, 像初次接触的确实不知道怎么动手去做, 而且在开始动手上面花费太多时间觉得有点浪费, 应该把重点放在功能的实现以及代码的编写上。能够鼓励多种实现方法, 就是出一些比较简单的题, 可是能够有几种不同的实现方法, 这样也能够弥补一下难易的梯度!
以上就是我经过这次课程设计所体会到的一点收获, 以及个人的一点意见。
展开阅读全文