第1个虚拟项目
前言
点灯开启了我们的FPGA之路,那么我们来继续沙盘演练。
用一个虚拟项目,来入门练习,以此步入数字逻辑的大门。
Key Words:FIFO 、SOF 、EOF、计数器、缓存、时序图、方案设计
一、项目要求
- 输入报文长度64~2048字节;
- 输入报文之间最小间隔为两拍;
- 输出报文的前两拍添加16bit报文长度信息;第1拍为报文长度高8位;第2拍为报文长度低8位;第3拍开始为输入报文;
信号 | I/O | 位宽 | 描述 |
系统接口信号 | |||
i_sys_clk | I | 1 | 系统时钟,125Mhz |
i_rst_n | I | 1 | 硬复位,低有效 |
输入接口信号 | |||
i_sop_in | I | 1 | 输入报文头指示信号,高有效 |
i_eop_in | I | 1 | 输入报文尾指示信号,高有效 |
i_vld_in | I | 1 | 输入报文数据有效信号,高有效 |
i_data_in | I | 8 | 输入报文数据 |
输出接口信号 | |||
o_sop_out | O | 1 | 输出报文头指示信号,高有效 |
o_eop_out | O | 1 | 输出报文尾指示信号,高有效 |
o_vld_out | O | 1 | 输出报文数据有效信号,高有效 |
o_data_out | O | 8 | 输出报文数据 |
输入接口时序
输出接口时序
二、项目方案设计
2.1项目需求
- 输出报文;
- 输出报文长度;
- 报文与报文长度输出满足时序要求;
2.2项目方案
1. 要求输出报文,且报文输出在报文长度输出之后,所以需要先对输入报文进行缓存,根据输入报文的位宽和长度范围,此处选择合适的同步FIFO即可;(如果是IC,那么就需要自己写FIFO,可以参考本博客的FIFO介绍)
这里项目提出了第1个要求,掌握FIFO的使用。
2. 要求输出报文长度,所以需要对输入报文长度进行计数,并将其缓存;
此处有坑,若只用寄存器对长度进行缓存,存在被后续报文长度覆盖的风险,故需要第2个FIFO对报文长度进行缓存。
3. 要求先输出报文长度然后紧跟着输出报文,此处需要对时序进行设计,需要掌握FIFO的读写时序,需要理解fpga的时钟沿采样。
理解:时钟沿采样及数据下一时钟沿变化。
2.3项目代码
module zmj0001(
input sys_clk,
input rst_n,
input sop_in,
input eop_in,
input vld_in,
input [7:0] data_in,
output sop_out,
output eop_out,
output vld_out,
output [7:0] data_out
);
当然这不是唯一的设计方案,可以先自行考虑设计及验证。
若需完整代码工程,🐟搜索“zmj0001”
项目重难点:
- FIFO的使用及时序的设计
- 考虑包间隔2 clk cycle
- 考虑长包+超短包的情况
时序设计可以用TimingDesigner软件,简单易用,需要的可以下载。
三、仿真验证
可以使用计数器来产生数据源data_in;
`timescale 1ns / 1ps
module zmj0001_tb();
reg sys_clk ;
reg rst_n ;
reg [7 :0] data_in ;
reg vld_in ;
reg sop_in ;
reg eop_in ;
reg [11 :0] cnt ;
wire sop_out ;
wire eop_out ;
wire vld_out ;
wire [7:0] data_out ;
initial
begin
sys_clk = 0;
rst_n = 0;
#100
rst_n = 1;
end
always #5 sys_clk = ~sys_clk; //100Mhz
//用计数器来产生data_in
always @(posedge sys_clk or negedge rst_n)begin
if(~rst_n)
cnt <= 12'b0;
else if(cnt > 2048)
cnt <= cnt;
else
cnt <= cnt + 12'b1;
end
always @(posedge sys_clk or negedge rst_n)begin
if(~rst_n)begin
data_in <= 8'b0;
sop_in <= 1'b0;
eop_in <= 1'b0;
vld_in <= 1'b0;
end
else begin
data_in <= 8'b0;
sop_in <= 1'b0;
eop_in <= 1'b0;
vld_in <= 1'b0;
if((cnt > 'd10 && cnt <= 'd60)|(cnt > 'd68 && cnt <= 'd668))begin
data_in <= data_in + 1'b1;
vld_in <= 1'b1;
end
if((cnt == 'd11)|(cnt == 'd69))
sop_in <= 1'b1;
if((cnt == 'd60)|(cnt == 'd668))
eop_in <= 1'b1;
if((cnt == 'd62) | (cnt == 'd63))begin //63 66
data_in <= data_in + 1'b1;
vld_in <= 1'b1;
sop_in <= 1'b1;
eop_in <= 1'b1;
end
end
end
zmj0001 u_zmj0001(
.sys_clk (sys_clk ),
.rst_n (rst_n ),
.sop_in (sop_in ),
.eop_in (eop_in ),
.vld_in (vld_in ),
.data_in (data_in ),
.sop_out (sop_out ),
.eop_out (eop_out ),
.vld_out (vld_out ),
.data_out (data_out )
);
endmodule
具体modelsim使用及与vivado的联合仿真,脚本编写请参考其他博文,后续FPGA其他专栏再考虑写相关内容。
验证时重点关注边界情况。
输入:
共4包数据,长包+超短包+超短包+长包,包间隔均为2clk cycle
data_in : 第1包:1-50的累加数;第2包:1;第3包:1;
输出:
若包间隔<2 clk?
输入:
输出:
可以看到,本设计甚至支持背靠背的超短包输入。
四、项目收获
- 方案设计的重要性:任何项目都是始于方案设计,前期需要花大量的功夫去理清思路,方案设计完成,代码实现只不过是水到渠成的事情。
- 仿真的学习:通过本项目,完成了testbench的编写,仿真验证,是对自己设计的一次检验,是实际项目缩短调试时间的最佳利器。
- 对xilinx IP的使用,对datasheet的阅读学习。
- 对时序的理解,时钟是FPGA的心跳:任何时序操作都是发生在时钟的跳变沿。当采样发生在当前上升沿时刻,数据变化是发生在下一时刻的上升沿。
- 绘画时序图,TimingDesigner的使用。有了时序图,代码就很容易实现了。
五、进阶考虑
本次虚拟项目旨在用最简单的例子带大家了解数字逻辑设计的一些基本概念,所以很多东西是没有考虑的。比如:
- 如果包间隔小于2个时钟周期怎么办? -----握手与反压
- 如果输入数据有错误怎么办? -----CRC校验
- 如果需要跨时钟域传输呢? -----CDC处理
- 报文只是简单转发,如果需要做处理呢?-----数据处理
- ...
所以,下一篇将沿着这个思路展开,进阶版的虚拟项目,同样可以作为公司的入职培训。
咱们下期见!