【FPGA项目】沙盘演练——基础版报文收发

1个虚拟项目

1. 前言

点灯开启了我们的FPGA之路,那么我们来继续沙盘演练。

用一个虚拟项目,来入门练习,以此步入数字逻辑的大门。

Key WordsFIFO SOF EOF、计数器、缓存、时序图、方案设计

2. 项目要求

1) 输入报文长度64~2048字节;

2) 输入报文之间最小间隔为两拍;

3) 输出报文的前两拍添加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

输出报文数据

 

输入接口时序

 

 

输出接口时序

 

 

3. 项目方案设计

3.1. 项目需求

1) 输出报文;

2) 输出报文长度;

3) 报文与报文长度输出满足时序要求;

3.2. 项目方案

1) 要求输出报文,且报文输出在报文长度输出之后,所以需要先对输入报文进行缓存,根据输入报文的位宽和长度范围,此处选择合适的同步FIFO即可;(如果是IC,那么就需要自己写FIFO,可以参考本博客的FIFO介绍)

² 这里项目提出了第1个要求,掌握FIFO的使用。

2) 要求输出报文长度,所以需要对输入报文长度进行计数,并将其缓存;

² 此处有坑,若只用寄存器对长度进行缓存,存在被后续报文长度覆盖的风险,故需要第2FIFO对报文长度进行缓存。

3) 要求先输出报文长度然后紧跟着输出报文,此处需要对时序进行设计,需要掌握FIFO的读写时序,需要理解fpga的时钟沿采样。

² 理解:时钟沿采样及数据下一时钟沿变化。

3.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
    );

 

FPGA项目沙盘演练-基础版报文收发(vivado2017.4)资源-CSDN文库

当然这不是唯一的设计方案,可以先自行考虑设计及验证。

项目重难点:

  1. FIFO的使用及时序的设计
  2. 考虑包间隔2 clk cycle
  3. 考虑长包+超短包的情况

时序设计可以用TimingDesigner软件,简单易用,需要的可以下载。

3.4. 仿真验证

可以使用计数器来产生数据源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

输入:

 

 

输出:

 

 

可以看到,本设计甚至支持背靠背的超短包输入。

4. 项目收获

  1. 方案设计的重要性:任何项目都是始于方案设计,前期需要花大量的功夫去理清思路,方案设计完成,代码实现只不过是水到渠成的事情。
  2. 仿真的学习:通过本项目,完成了testbench的编写,仿真验证,是对自己设计的一次检验,是实际项目缩短调试时间的最佳利器。
  3. xilinx IP的使用,对datasheet的阅读学习。
  4. 对时序的理解,时钟是FPGA的心跳:任何时序操作都是发生在时钟的跳变沿。当采样发生在当前上升沿时刻,数据变化是发生在下一时刻的上升沿。
  5. 绘画时序图,TimingDesigner的使用。有了时序图,代码就很容易实现了。

 

5. 进阶考虑

本次虚拟项目旨在用最简单的例子带大家了解数字逻辑设计的一些基本概念,所以很多东西是没有考虑的。比如:

  1. 如果包间隔小于2个时钟周期怎么办? -----握手与反压
  2. 如果输入数据有错误怎么办? -----CRC校验
  3. 如果需要跨时钟域传输呢? -----CDC处理
  4. 报文只是简单转发,如果需要做处理呢?-----数据处理
  5. ...

 

所以,下一篇将沿着这个思路展开,进阶版的虚拟项目,同样可以作为公司的入职培训。

咱们下期见!

 

 

 

 

热门相关:藏娇记事   重生成偏执霍少的小仙女   网游三国之城市攻略   重生之将门毒后   楚氏赘婿