Xilinx-Verilog-学习笔记(18):FIFO

Xilinx-Verilog-学习笔记(18):FIFO

一、调用ISE中的FIFO

1、调用ISE中的IP

(1)选择芯片类型,封装,速度以及仿真器和语言等。
在这里插入图片描述
(2)创建一个新的source文件,并将该文件放到design目录下。
在这里插入图片描述
(3)创建IP核的配置,这里要与创建工程的一致。
在这里插入图片描述
(4)选择创建一个FIFO
在这里插入图片描述
(5)选择FIFO的类型
在这里插入图片描述
(6)选择FIFO的时钟和内存类型
在这里插入图片描述
(7)选择FIFO的位宽和深度
在这里插入图片描述
(8)选择FIFO的一些标志信号
在这里插入图片描述
(9)选择复位方式
在这里插入图片描述
这是配置完成后最终生成的报告。
在这里插入图片描述
(10)将生成的IP核工程添加到ISE工程中,注意:添加的IP核要选择xco文件。
在这里插入图片描述
(11)通过将instantation文件直接复制到design文件中,完成FIFO接口的例化。
在这里插入图片描述

2、design文件
`timescale 1ns / 1ps
module ex_ise_fifo(
	input		wire		sclk,
	input		wire		rst_n,
	input		wire  [7:0] data_in,
	input		wire		data_v,   //数据有效
	input		wire		r_flag,  //读fifo标志
	output		wire  [7:0] dout
    );
wire	w_full,r_emp;//写满信号,读空信号
wire	wr_en;//写使能
wire	rd_en;//读使能
wire	almost_full,wr_ack,overflow,r_empty,almost_empty,valid,underflow;

assign wr_en = (~w_full) & data_v;	//当写有效且没有写满时,写使能拉高
assign rd_en = (~r_empty) & r_flag; //当读有效且没有读空时,读使能拉高
	

sync_fifo_8x256 sync_fifo (
  .clk(sclk), // input clk
  .rst(rst_n), // input rst
  .din(data_in), // input [7 : 0] din
  .wr_en(wr_en), // input wr_en
  .rd_en(rd_en), // input rd_en
  .dout(dout), // output [7 : 0] dout
  .full(w_full), // output full
  .almost_full(almost_full), // output almost_full
  .wr_ack(wr_ack), // output wr_ack
  .overflow(overflow), // output overflow
  .empty(r_empty), // output empty
  .almost_empty(almost_empty), // output almost_empty
  .valid(valid), // output valid
  .underflow(underflow) // output underflow
);
endmodule
3、testbench文件
`timescale 1ns/1ns
module tb_ex_ise_fifo;

reg				sclk,rst_n;
reg				data_v;
reg		[7:0]	data;
reg				r_flag;
wire	[7:0]	dout;
initial begin
	sclk = 0;
	rst_n = 0;
	#100
	rst_n = 1;
end

initial begin
	data_v = 0;
	data = 0;
	r_flag = 0;
	#200
	send_data();
	#60
	read_data();
end

always #10 sclk =~ sclk;

ex_ise_fifo ex_ise_fifo_inst (
    .sclk(sclk), 
    .rst_n(rst_n), 
    .data_in(data), 
    .data_v(data_v), 
    .r_flag(r_flag), 
    .dout(dout)
    );
	
task send_data();
	integer i;
	begin
		for(i=0;i<256;i=i+1)
		begin
			@(posedge sclk);
			data_v = 1'b1;
			data = i;
		end
		@(posedge sclk);
		data_v = 1'b0;
		data = 0;
	end
endtask

task read_data();
	integer i;
	begin
		for(i=0;i<256;i=i+1)
		begin
			@(posedge sclk);
			r_flag = 1'b1;
		end
		r_flag = 1'b0;
	end
endtask

endmodule
4、do文件
quit -sim
.main clear

vlib work

vlog ./tb_ex_ise_fifo.v
vlog ./../design/ex_ise_fifo.v
vlog ./../ise_prj/ipcore_dir/*.v
vlog ./ise_lib/*.v

vsim -voptargs=+acc work.tb_ex_ise_fifo work.glbl

add wave tb_ex_ise_fifo/ex_ise_fifo_inst/*

run 10us
5、仿真

(1)写FIFO
在这里插入图片描述
写应答信号wr_ack比写使能信号wr_en延迟一拍,数据在写使能为高的时候写入到FIFO(RAM)中,其实在下一个上升沿才真正将数据存入到RAM中去,所以ack是标志数据真正被写入RAM中去的标志。
在这里插入图片描述
写入数据从0到255后,写有效信号data_v拉低,wr_en信号拉低标志写完。对于几乎满信号almost_full,当还有一个数据的时候拉高。(这里几乎满信号可以设置成还有X个数据时拉高,在IP核中即可设置)

(2)读FIFO
在这里插入图片描述
当读空信号r_empty为低(标志着FIFO中还有内容),且r_flag拉高后,读使能信号rd_en拉高进行数据的读取,将数据读取到dout中。

在这里插入图片描述
这里几乎空信号almost_empty同样可以实现配置(此处设置为0),当读到最后一个数据拉高。当数据读取完成后,r_flag信号拉低标志读取完成。

(3)FIFO写溢出

写溢出表示写入的数据数量大于FIFO的深度。
在这里插入图片描述
这里多写了3个数据。当写溢出后,写溢出信号overflow被拉高了3个时钟周期,表示这三个时钟周期发送的数据是写溢出的数据。

(4)FIFO读溢出

读溢出表示读取的数据量大于FIFO中的内容。
在这里插入图片描述
这里多读了3个数据。当读溢出后,读溢出信号underflow被拉高了3个时钟周期,表示这三个时钟周期读取的数据是读溢出的数据。

二、自定义异步FIFO

1、基本概念

写满和读空:
写指针赶上读指针,称为写满;读指针赶上写指针,称为读空。

//写满标志
assign w_full=(r_addr[2:0] == w_addr[2:0] && r_addr[3] == (~w_addr[3]));
//读空标志
assign r_empty=(r_addr[3:0] == w_addr[3:0]);

低三位相等,高位取反相等,相差是一个FIFO的深度,就是写满。
读地址和写地址相等时,则为读空。

格雷码:
编码方式:(十进制>>1) ^(十进制) 譬如:(2>>1) ^(2)=0011
特点:挨着的两个数发生跳变时,只需要改变一位。

2、逻辑框图

FIFO逻辑图

整体包含了3个模块:双端口的FIFO存储模块、写FIFO模块、读FIFO模块。其中读写模块有各自的时钟,因此是异步的,这样就构成了2个时钟域。当读模块的信号进入到写模块中,相当于信号跨时钟域,所以需要打两拍处理。

在这里插入图片描述
格雷码控制器
在这里插入图片描述
写满读空逻辑控制器

这里与写满读空时的组合逻辑描述相同,在代码中需要实现。
在这里插入图片描述

3、design文件

此部分包含4个文件,3个模块(写FIFO控制器、读FIFO控制器和FIFO RAM),1个顶层TOP文件。

(1)写FIFO控制器:w_ctrl.v

module	w_ctrl(
	input	wire			w_clk,//写时钟
	input	wire			rst_n,//复位
	input	wire			w_en,//写使能
	input	wire	[8:0]	r_gaddr,//读时钟域过来的格雷码读地址指针
	output	reg				w_full,//写满标志
	output	wire	[8:0]	w_addr,//256深度的FIFO写二进制地址
	output	wire	[8:0]	w_gaddr//写FIFO地址格雷码编码
);
reg		[8:0]	addr;
reg		[8:0]	gaddr;
wire	[8:0]	addr_wire;
wire	[8:0]	gaddr_wire;
reg		[8:0]	r_gaddr_d1,r_gaddr_d2;
wire			w_full_wire;
//打两拍进行时钟同步
always @(posedge w_clk or negedge rst_n)
	if(rst_n == 1'b0)
		{r_gaddr_d2,r_gaddr_d1} <= 18'd0;
	else 
		{r_gaddr_d2,r_gaddr_d1} <= {r_gaddr_d1,r_gaddr};
//产生写ram的地址指针二进制
assign w_addr = addr;
always @(posedge w_clk or negedge rst_n)
	if(rst_n == 1'b0)
		addr <= 9'd0;
	else 
		addr <= addr_wire;

assign addr_wire = addr + ((~w_full)&w_en);
//转换格雷码编码地址
assign gaddr_wire=(addr_wire>>1)^addr_wire;
always @(posedge w_clk or negedge rst_n)
	if(rst_n == 1'b0)
		gaddr<=9'd0;
	else gaddr <= gaddr_wire;
assign w_gaddr = gaddr;

//写满标志产生完成
always @(posedge w_clk or negedge rst_n)
	if(rst_n == 1'b0)
		w_full <= 1'b0;
	
	else if({~gaddr_wire[8:7],gaddr_wire[6:0]}==r_gaddr_d2)//根据仿真验证一下打两拍对空满标志的影响???
		w_full <=1'b1; //w_full_wire;
	else w_full <=1'b0;

endmodule

在这里插入图片描述
在这里插入图片描述
从图中可以看出,写FIFO控制器的输入输出共7个端口,在接口区也已经有了定义。

  • 输入信号w_clk和复位信号rst_n这里不再赘述,一个是用于同步本时钟域的节拍,另一个是用来复位。
  • 写使能信号w_en相当于图中的inc信号
    用于控制写FIFO,这里要设计一个组合逻辑,assign addr_wire = addr +
    ((~w_full)&w_en);
  • addr是addr_wire过了一级寄存器后的结果。
  • 由于最终的输出为格雷码形式,因此要对最后输出的addr进行格雷码的转换,设计一个组合逻辑assign
    gaddr_wire=(addr_wire>>1)^addr_wire;
    ,将地址转换为格雷码版本的gaddr_wire,再经过一级寄存器转换为gaddr
  • 最后生成的addrgaddr都为reg型变量,所以需要wire型的w_addrw_gaddr通过assign语句印出来。
  • 从读端过来的读指针r_gaddr由于需要进行跨时钟域处理,所以进行打两拍,变为r_gaddr_d2
  • 进行是否写满判断时,所用到的信号为打两拍后的读端信号r_gaddr_d2

(2)读FIFO控制器:r_ctrl.v

module	r_ctrl (
	input	wire			r_clk,//读时钟
	input	wire			rst_n,
	input	wire			r_en,//读使能
	input	wire	[8:0]	w_gaddr,//写时钟域中的写地址指针
	output	reg				r_empty,//读空标志
	output	wire	[8:0]	r_addr,//读地址二进制
	output	wire	[8:0]	r_gaddr//读格雷码地址
);
reg		[8:0]	addr;
reg		[8:0]	gaddr;
wire	[8:0]	addr_wire;
wire	[8:0]	gaddr_wire;
reg		[8:0]	w_gaddr_d1,w_gaddr_d2;
wire			r_empty_wire;
//打两拍进行时钟同步
always @(posedge r_clk or negedge rst_n)
	if(rst_n == 1'b0)
		{w_gaddr_d2,w_gaddr_d1} <= 18'd0;
	else 
		{w_gaddr_d2,w_gaddr_d1} <= {w_gaddr_d1,w_gaddr};
//二进制的读地址
assign r_addr = addr;
always @(posedge r_clk  or negedge rst_n)
	if(rst_n == 1'b0)
		addr <=9'd0;
	else 
		addr <= addr_wire;

assign addr_wire = addr + ((~r_empty)&r_en);
//格雷码的读地址
assign r_gaddr = gaddr;
assign	gaddr_wire = (addr_wire >>1 )^ addr_wire;

always @(posedge r_clk or negedge rst_n)
	if(rst_n == 1'b0)
		gaddr <= 9'd0;
	else 
		gaddr <= gaddr_wire;
	
//读空标志的产生
assign r_empty_wire =(gaddr_wire == w_gaddr_d2);
always @(posedge r_clk or negedge rst_n)
	if(rst_n == 1'b0)
		r_empty<=1'b1;
	else //if(gaddr_wire == w_gaddr_d2)//根据仿真验证一下打两拍对空满标志的影响???
		r_empty <= r_empty_wire;
	//else 
		//r_empty <= 1'b0;

endmodule

在这里插入图片描述
从图中可以看出,读FIFO控制器的输入输出共7个端口,在接口区也已经有了定义。
代码中的信号操作方式与写FIFO控制器中基本相同,这里不再赘述。唯一区别是,在进行读空判断时,直接判断打两拍后的w_gaddr_d2gaddr_wire时候是否相等即可判断。

(3)FIFO双端口RAM:fifomem.v

module	fifomem(
	input	wire			w_clk,
	input	wire			r_clk,
	input	wire			w_en,//来自于FIFO的写控制模块
	input	wire			w_full,//来自于FIFO的写控制模块
	input	wire	[7:0]	w_data,//来自于外部数据源
	input	wire	[8:0]	w_addr,//来自于FIFO的写控制模块
	input	wire			r_empty,//来自于FIFO的读控制模块
	input	wire	[8:0]	r_addr,//来自于FIFO的读控制模块
	output	wire	[7:0]	r_data//读数据是从内部ram中读取
);

wire	ram_w_en;


assign	ram_w_en = w_en &(~w_full);
//ipcore 已经改为256深度的,但是名字没有改
dp_ram_512x8_swsr	dp_ram_512x8_swsr_inst (
	//写数据接口
	.wrclock ( w_clk ),
	.wren ( ram_w_en ),
	.wraddress ( w_addr[7:0] ),
	.data ( w_data ),
	//读数据接口
	.rdclock ( r_clk ),
	.rdaddress ( r_addr[7:0] ),
	.q ( r_data )
	);
endmodule

在这里插入图片描述
此部分将读写FIFO控制器与RAM相连,如图中蓝色线。通过生成一个双端口的RAM的IP,其深度为512,位宽为8,用于FIFO的缓存。

(4)整理的顶层文件:ex_fifo.v

module	ex_fifo(
	input	wire			w_clk,
	input	wire			r_clk,
	input	wire			rst_n,
	input	wire			w_en,
	input	wire	[7:0]	w_data,
	output	wire			w_full,
	input	wire			r_en,
	output	wire	[7:0]	r_data,
	output	wire			r_empty
);
wire	[8:0]	r_gaddr;
wire	[8:0]	w_addr; 
wire	[8:0]	w_gaddr;
wire	[8:0]	r_addr;
w_ctrl	w_ctrl_inst(
	.w_clk		(w_clk),//写时钟
	.rst_n		(rst_n),//复位
	.w_en		(w_en),//写使能
	.r_gaddr	(r_gaddr),//读时钟域过来的格雷码读地址指针
	.w_full		(w_full),//写满标志
	.w_addr		(w_addr),//256深度的FIFO写二进制地址
	.w_gaddr	(w_gaddr)//写FIFO地址格雷码编码
);

fifomem	fifomem_inst(
	.w_clk		(w_clk),
	.r_clk		(r_clk),
	.w_en		(w_en),//来自于FIFO的写控制模块
	.w_full		(w_full),//来自于FIFO的写控制模块
	.w_data		(w_data),//来自于外部数据源
	.w_addr		(w_addr),//来自于FIFO的写控制模块
	.r_empty	(r_empty),//来自于FIFO的读控制模块
	.r_addr		(r_addr),//来自于FIFO的读控制模块
	.r_data		(r_data)//读数据是从内部ram中读取
);

r_ctrl r_ctrl_inst(
	.r_clk		(r_clk),//读时钟
	.rst_n		(rst_n),
	.r_en		(r_en),//读使能
	.w_gaddr	(w_gaddr),//写时钟域中的写地址指针
	.r_empty	(r_empty),//读空标志
	.r_addr		(r_addr),//读地址二进制
	.r_gaddr	(r_gaddr)//读格雷码地址
);

endmodule

此部分功能是将三个模块的所有接口都引出来,并将3个模块进行连接。

4、testbench文件
`timescale	1ns/1ns

module	tb_ex_fifo;
reg				r_clk,w_clk,rst_n;
reg				w_en;
reg		[7:0]	w_data;
reg				r_en;
wire			w_full;
wire			r_empty;
wire	[7:0]	r_data;

parameter	CLK_P=20;

initial begin
	rst_n<=0;
	r_clk<=0;
	w_clk<=0;
	
	#200
	rst_n=1;
end
//写的初始化模块
initial	begin
	w_en=0;
	w_data=0;
	#300
	write_data(256);
end
//读的初始化模块
initial begin
	r_en =0;
	@(posedge w_full);
	#40;
	read_data(256);
end

always #(CLK_P/2) r_clk =~r_clk;
always #(CLK_P/2) w_clk =~w_clk;

ex_fifo ex_fifo_inst(
	.w_clk		(w_clk),
	.r_clk		(r_clk),
	.rst_n		(rst_n),
	.w_en		(w_en),
	.w_data		(w_data),
	.w_full		(w_full),
	.r_en		(r_en),
	.r_data		(r_data),
	.r_empty	(r_empty)
);


task write_data(len);
	integer i,len;
	begin
		for (i=0;i<len;i=i+1)
		begin
			@(posedge w_clk);
			w_en=1'b1;
			w_data=i;
		end
		@(posedge w_clk);
		w_en = 1'b0;
		w_data =0;
	end
endtask

task read_data(len);
	integer i,len;
	begin
		for (i=0;i<len;i=i+1)
		begin
			@(posedge r_clk);
			r_en=1'b1;
		end
		@(posedge r_clk);
		r_en = 1'b0;
	end
endtask
endmodule
6、do文件
quit -sim
.main clear

vlib work

vlog	./tb_ex_fifo.v
vlog	./../design/*.v
vlog	./../quartus_prj/ipcore_dir/dp_ram_512x8_swsr.v
vlog	./altera_lib/*.v

vsim	-voptargs=+acc	work.tb_ex_fifo

add wave tb_ex_fifo/*
add wave tb_ex_fifo/ex_fifo_inst/*
add wave -divider {w}
add wave tb_ex_fifo/ex_fifo_inst/w_ctrl_inst/*
add wave -divider {R}
add wave tb_ex_fifo/ex_fifo_inst/r_ctrl_inst/*

run 25us
7、时序仿真

在这里插入图片描述
将256个数据写完之后,写使能信号w_en拉低,同时w_full信号拉高表示已写满。

在这里插入图片描述
将256个数据读完之后,读使能信号r_en拉低,同时r_empty信号拉高表示已读空。

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
DDS(Direct Digital Synthesis)直接数字合成技术是一种数字信号处理技术,用于产生高精度、高稳定度、高分辨率的周期性信号。DDS技术的主要思路是:将一个固定的参考频率信号和一个可变的相位调制信号相乘,从而产生所需频率的输出信号。 在MATLAB中,我们可以通过使用内置函数sin()来生成正弦波信号。例如,我们可以生成一个频率为10 Hz,振幅为1的正弦波信号,并将其绘制成图形: ``` t = 0:0.001:1; x = sin(2*pi*10*t); plot(t,x); ``` 在Verilog中,我们可以使用DDS模块来生成正弦波信号。以下是一个简单的DDS模块: ``` module dds( input clk, //时钟信号 input reset, //复位信号 output reg [7:0] sin_out //正弦波输出信号 ); reg [31:0] phase_acc; //相位累加器 reg [7:0] sin_lut [0:255]; //正弦波查找表 //初始化正弦波查找表 initial begin for (i = 0; i < 256; i = i + 1) begin sin_lut[i] = $signed(127*sin(2*3.14159*i/256)); end end always @(posedge clk) begin if (reset) begin phase_acc <= 0; sin_out <= 0; end else begin phase_acc <= phase_acc + 100; //相位累加器步进为100 sin_out <= sin_lut[phase_acc[31:24]]; //从查找表中读取正弦波值 end end endmodule ``` 在这个DDS模块中,我们使用相位累加器来控制正弦波的频率,使用查找表来存储正弦波的值。在时钟上升沿时,相位累加器步进100,从查找表中读取正弦波值,并将其输出。 需要注意的是,在这个DDS模块中,我们使用了固定的步进值100。如果我们想要生成不同频率的正弦波信号,我们需要改变步进值。例如,如果我们想要生成频率为1 kHz的正弦波信号,我们需要将步进值改为1000*256/时钟频率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值