fpga实操训练(ip ram和ip fifo)
Posted 嵌入式-老费
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了fpga实操训练(ip ram和ip fifo)相关的知识,希望对你有一定的参考价值。
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
所有的fpga ip当中,用的最多的ip一般有pll、rom、ram和fifo。前面,我们讨论过了rom,rom相比较ram和fifo而言,多一个mif文件的配置。而ram、fifo则不需要这样的操作。今天可以继续学习下ram和fifo,这样在后面的开发中就会显得游刃有余。
1、ip ram配置
a)创建ip ram的方法
要创建ip ram,首先需要打开ip catalog,输入ram,选择双端口输入,
接下来,系统会提示输入文件名,不妨命名为ip_ram.v。完成命名之后,就可以开始ip的配置了。这里面有几个重要的对话框需要注意下,首先是设置好bit宽度和word数量,
确认是否共享时钟,
确认是否需要晚一节拍输出数据,
其他部分的页面采用默认的配置就可以了。
b)准备测试代码
有了ip ram核之后,下面就是需要把它实例化,并且添加必要的测试代码。内容如下所示,
module top(clk, rst);
input clk;
input rst;
wire clk;
wire rst;
reg[4:0] read_address;
reg[4:0] write_address;
reg[7:0] write_data;
reg write_en;
wire[7:0] read_data;
always@(posedge clk or negedge rst)
if(!rst)
read_address <= 5'd0;
else
read_address <= read_address + 1'b1;
always@(posedge clk or negedge rst)
if(!rst) begin
write_en <= 1'b0;
write_address <=5'd0;
write_data <= 8'd0;
end else begin
if(write_address != 5'd31) begin
write_en <= 1'b1;
write_address <= write_address + 1'b1;
write_data <= write_data + 8'd2 ;
end else begin
write_en <= 1'b0;
write_address <=5'd0;
write_data <= 8'd0;
end
end
ip_ram ip_ram0(
.clock(clk),
.data(write_data),
.rdaddress(read_address),
.wraddress(write_address),
.wren(write_en),
.q(read_data)
);
endmodule
测试的代码逻辑并不复杂。首先,在fpga复位之后,先对ram进行赋值操作。等所有的地址都写上数据之后,下面就是不停循环读出这些数据。为了验证这些数据是否真的被写入到ram空间,可以通过SignalTap的方法,通过jtag读一下这些数据,观察之前的写操作究竟有没有生效。
c)准备SignalTap,并且开始测试
SignalTap的配置方法前面提过多次,这里直接给出配置的截图即可,
经过输入F6和Esc之后,就可以看到截取的地址和数据了,
前面编写测试代码的时候,写入的数据都是address*2。假设输入的地址是0,那么读取的数据应该是0;如果输入的地址是1,那么读取的数据应该是2,依次类推。通过截图,我们发现当read_address是0x12h的时候,q需要等一个时钟才能输出0x24h。这说明,ram和rom其实是一样的,两者都是等一个时钟才能输出数据的。
2、ip fifo配置
a)和ram一样,首先还是从ip catalog中寻找fifo,
同样,这里需要用户自己输入文件名。不妨命名为ip_fifo.v,接着就可以开始配置。配置的内容很多,最关键的有这么几处。首先,输入write bit长度、read bit长度、总长度。这个非常有用,
确定主要的输出信号,
确认rdreq的用法,
完成主要的这些操作之后,再加上自身的默认配置,就可以完成ip fifo的基本设定了。
b)准备测试代码
module top(clk, rst);
input clk;
input rst;
wire clk;
wire rst;
reg[7:0] write_data;
wire[31:0] read_data;
wire write_full;
wire read_empty;
reg write_req;
reg read_req;
// write state and next_write_state
reg[1:0] write_state;
reg[1:0] next_write_state;
always@(posedge clk or negedge rst)
if(!rst)
write_state <= 2'b00;
else
write_state <= next_write_state;
always@(*)
if(!rst)
next_write_state <= 2'b00;
else begin
case (write_state)
2'b00:
if(!write_full)
next_write_state <= 2'b01;
else
next_write_state <= 2'b00;
2'b01:
if(write_full)
next_write_state <= 2'b00;
else
next_write_state <= 2'b01;
default:
next_write_state <= 2'b00;
endcase
end
always @(posedge clk or negedge rst)
if(!rst)
write_req <= 1'b0;
else if(next_write_state == 2'b01)
write_req <= 1'b1;
else
write_req <= 1'b0;
always @(posedge clk or negedge rst)
if(!rst)
write_data <= 8'd0;
else if(next_write_state == 2'b01)
write_data <= write_data + 1'b1;
// read_state and next_read_state
reg[1:0] read_state;
reg[1:0] next_read_state;
always@(posedge clk or negedge rst)
if(!rst)
read_state <= 2'b00;
else
read_state <= next_read_state;
always@(*)
if(!rst)
next_read_state <= 2'b00;
else begin
case (read_state)
2'b00:
if(!read_empty)
next_read_state <= 2'b01;
else
next_read_state <= 2'b00;
2'b01:
if(read_empty)
next_read_state <= 2'b00;
else
next_read_state <= 2'b01;
default:
next_read_state <= 2'b00;
endcase
end
always @(posedge clk or negedge rst)
if(!rst)
read_req <= 1'b0;
else if(next_read_state == 2'b01)
read_req <= 1'b1;
else
read_req <= 1'b0;
// invoke ip ip_fifo
wire[7:0] rdusedw;
wire[7:0] wrusedw;
ip_fifo ip_fifo0(
.data(write_data),
.rdclk(clk),
.rdreq(read_req),
.wrclk(clk),
.wrreq(write_req),
.q(read_data),
.rdempty(read_empty),
.rdusedw(rdusedw),
.wrfull(write_full),
.wrusedw(wrusedw));
endmodule
测试代码的内容不复杂,主要的工作就是把流程切分成write_state和read_state。对于write_state而言,一旦发现fifo不满,就开始写入数据。而对于read_state而言,每当发现fifo非空,就不停读取数据。这样,一写一读,就构成了fifo的基本操作。如果两者速率不匹配,或者字节长度不同,fifo还可以充当临时buffer的角色。当然,为了验证我们的测试是否正确,最终还得借助于SignalTap这个工具。
c)SignalTap配置和测试
SignalTap中主要检查写入和读取的数据,
烧入sof文件之后,经过F6和Esc之后,就可以看到相关的波形数据了,
补充1:
之前测试的时候一直没有注意时序约束。事实上,在 fpga开发过程中最好做到0 error,0 warning。这是比较合适的。所以,项目中最好添加必要的时序约束文件,告知quartus软件当前fpga希望以什么样的时钟运行,
# Clock constraints
create_clock -name "clk" -period 20.000ns [get_ports clk]
# Automatically constrain PLL and other generated clocks
derive_pll_clocks -create_base_clocks
# Automatically calculate clock uncertainty to jitter and other effects.
derive_clock_uncertainty
# tsu/th constraints
# tco constraints
# tpd constraints
补充2:
另外,如果引脚比较多,pin bind也是一个体力活。这部分可以借助于tcl脚本来完成。编写完tcl之后,就可以通过"Tools"->"Tcl Scripts"运行脚本即可,
package require ::quartus::project
set_location_assignment PIN_E1 -to clk
set_location_assignment PIN_N13 -to rst
set_location_assignment PIN_D9 -to led[3]
set_location_assignment PIN_C9 -to led[2]
set_location_assignment PIN_F9 -to led[1]
set_location_assignment PIN_E10 -to led[0]
以上是关于fpga实操训练(ip ram和ip fifo)的主要内容,如果未能解决你的问题,请参考以下文章