串行数据添加帧头以及帧头检测

这篇具有很好参考价值的文章主要介绍了串行数据添加帧头以及帧头检测。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一 串行数据添加帧头

添加帧头的思路参考以下两个链接,将帧头数据保存在ROM中,将串行数据保存在FIFO中,当检测到FIFO中有数据后,先将帧头输出,等帧头输出完毕再将FIFO中串行数据输出,实现添加帧头的功能。

科学网—利用Verilog将CEO文件中的帧头识别出来,并发送一帧完整的数据 - 符加乐的博文 (sciencenet.cn)

加帧头帧尾怎么用verilog实现 - 数字IC设计讨论(IC前端|FPGA|ASIC) - EETOP 创芯网论坛 (原名:电子顶级开发网) -

1.先配置ROM IP核

将Block Memory Generator IP核配置为单端口ROM,位宽为1,深度为13,输出端不使用寄存器,ROM内存储的数据由coe文件初始化,coe文件的配置可以参考

23_实战篇:RAM IP 核实验(第二讲:程序设计)_哔哩哔哩_bilibili

vivado ROM IP核简单使用_小李干净又卫生的博客-CSDN博客_vivadorom

帧头使用13位巴克码组,1 1 1 1 1 0 0 1 1 0 1 0 1,则coe文件的编写如下

memory_initialization_radix=2;  //数据的格式,可以是2进制,10进制,16进制数据
memory_initialization_vector=
1,
1,
1,
1,
1,
0,
0,
1,
1,
0,
1,
0,
1;       //每个数据后面用逗号或者空格或者换行符隔开,最后一个数据后面加分号
//使用13位巴克码组作为帧头1 1 1 1 1 0 0 1 1 0 1 0 1

2.编写顶层模块

模块的编写思路如下:将FIFO的empty信号作为开始添加帧头的使能信号,当FIFO非空时表示FIFO中已经存在串行数据,此时开始输出帧头,并通过判断ROM的地址给出帧头输出结束的信号,帧头输出结束信号再给到FIFO的读使能,让模块输出FIFO中的数据。代码如下:

`timescale 1ns / 1ps

module add_frame_header(
    input clk,  //时钟
    input rst_n,    //低使能复位
    input fifo_data,  //FIFO的输出的1位数据
    input fifo_empty,   //将FIFO的非空信号作为开始插入帧头的使能信号
    
    output reg [3:0]bram_addr,  //bram的地址
    output reg [3:0]bram_addr_temp, //bram_addr_temp延迟一个时钟后得到bram_addr,为了让ram使能后数据输出和地址增加能分开
    output reg bram_douta_temp, //ram输出数据缓存,bram_douta延迟一个时钟后得到bram_douta_temp。如果直接读取bram数据发现数据输出不能与时钟对齐,存在100ps延时,所以增加一级寄存器进行缓冲
    output wire header_end, //帧头结束信号,也是fifo的读使能信号
    output wire out_data //添加帧头后的输出数据
    );

//reg [3:0]bram_addr;
//reg [3:0]bram_addr_temp;
//reg bram_douta_temp;
reg bram_en;    //bram的使能信号
wire bram_douta;   //bram输出数据
reg header_end_temp;

//控制bram的使能信号
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        bram_en <= 1'b0;
    else if(bram_addr_temp == 4'd15) //输出完关闭bram使能
        bram_en <= 1'b0;
    else if(!header_end  && !fifo_empty)  //接收到开始插入帧头的使能信号,开启bram使能
        bram_en <= 1'b1;
    else
        bram_en <= bram_en;
end

//控制bram的地址信号
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        bram_addr_temp <= 4'd0;
    else if(bram_addr_temp == 4'd15) //输出完关闭bram地址保持在15
        bram_addr_temp <= 4'd15;
    else if(!header_end  && !fifo_empty)
        bram_addr_temp <= bram_addr_temp + 4'd1;  //接收到开始插入帧头的使能信号,bram地址开始递增
    else
        bram_addr_temp <= bram_addr_temp;
end
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        bram_addr <= 4'd0;
    else if(bram_addr == 4'd12)
        bram_addr <= 4'd12;
    else
        bram_addr <= bram_addr_temp;    //bram_addr_temp延迟一个时钟后得到bram_addr,为了让ram使能后数据输出和地址增加能分开
end

//控制帧头结束信号
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        header_end_temp <= 1'b0;
    else if(bram_addr_temp == 4'd15)    //为什么输出完的标志是15,bram本身有一个延迟,bram地址有一个缓冲,dram输出又增加了一个缓冲
        header_end_temp <= 1'b1;
    else
        header_end_temp <= header_end_temp;
end
assign header_end = ~fifo_empty && header_end_temp; //防止读空

//控制输出帧头还是输出fifo数据
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        bram_douta_temp <= 1'b0;
    else
        bram_douta_temp <= bram_douta;  //ram输出数据缓存,bram_douta延迟一个时钟后得到bram_douta_temp。如果直接读取bram数据发现数据输出不能与时钟对齐,存在100ps延时,所以增加一级寄存器进行缓冲
end
assign out_data = (header_end)?fifo_data:bram_douta_temp;

//例化bram模块
blk_mem_gen_0 bram_inst (
  .clka(clk),    // input wire clka
  .ena(bram_en),      // input wire ena
  .addra(bram_addr),  // input wire [3 : 0] addra
  .douta(bram_douta)  // output wire [0 : 0] douta
);
endmodule

 注意,因为帧头结束信号header_end要作为FIFO的读使能信号,为了防止读空的情况,需要和和~empty信号进行与操作,且必须是组合逻辑,参考如下链接

Xilinx IP解析之FIFO Generator v13.2_徐晓康的博客的博客-CSDN博客_fifo generator

3.仿真结果

首先编写testbanch,设置复位信号、FIFO的empty信号和FIFO中的数据。为了简单起见,设置了一个16位数0x1234作为FIFO中的数据,并在FIFO读使能的情况下,将数据从高位到低位依次输出,代码如下:

`timescale 1ns / 1ps

module tb_add_frame_header();
    //输入
    reg clk;  //时钟
    reg rst_n;    //低使能复位
    wire fifo_data;  //FIFO的输出的1位数据
    reg fifo_empty;   //将FIFO的非空信号作为开始插入帧头的使能信号
    //输出
    wire [3:0]bram_addr;
    wire [3:0]bram_addr_temp;
    wire bram_douta_temp;
    wire header_end; //帧头结束信号,也是fifo的读使能信号
    wire out_data; //添加帧头后的输出数据
    
    reg [15:0] num;
    reg [15:0] fifo_data_store;
    //信号初始化
    initial begin
        clk = 1'b0;
        rst_n = 1'b0;
        fifo_empty = 1'b1;
        num=16'd15;
        fifo_data_store = 16'h1234;
        #10
        rst_n = 1'b1;
        #40
        fifo_empty = 1'b0;
        
        #700
        rst_n = 1'b0;
        fifo_empty = 1'b1;
        num=16'd16;
        #20
        rst_n = 1'b1;
        #40
        fifo_empty = 1'b0;
    end
    
    //生成时钟
    always #10 clk = ~clk;
    
    //fifo的输出数据
    always@(posedge clk) begin
        if(header_end) begin
            num <= num - 16'd1;
        end
    end
    assign fifo_data = (!fifo_empty)?fifo_data_store[num]:1'b0;
    
    always@(posedge clk) begin
        if(num == 16'd0) begin
            fifo_empty <= 1'b1;
        end
    end
    
    //例化
    add_frame_header add_frame_header_inst(
    .clk(clk),  //时钟
    .rst_n(rst_n),    //低使能复位
    .fifo_data(fifo_data),  //FIFO的输出的1位数据
    .fifo_empty(fifo_empty),   //将FIFO的非空信号作为开始插入帧头的使能信号
    
    .bram_addr(bram_addr),
    .bram_addr_temp(bram_addr_temp),
    .bram_douta_temp(bram_douta_temp),
    .header_end(header_end), //帧头结束信号,也是fifo的读使能信号
    .out_data(out_data) //添加帧头后的输出数据
    );
endmodule

仿真结果如下,在FIFO的empty信号为零时,隔两个时钟周期开始输出帧头数据,当帧头数据输出结束后header_end信号拉高,然后开始输出FIFO中的数据。

串行数据添加帧头以及帧头检测

 一开始在仿真的时候还遇到一个问题,如果直接输出BRAM中的数据,发现输出数据与时钟没有对齐,数据延迟了100ps,可以看以下两个链接的描述:

FPGA设计block ram中,读数据存在100ps的延时,如何解释-CSDN社区

100 ps delay for Block Memory Generator 6.3 (xilinx.com)

解决方法就是将BRAM的输出数据缓冲一级后再输出。

二 帧头检测

帧头检测的思路主要参考下面这篇论文,首先将串行输入数据存放到寄存器中,然后将输入数据于目标帧头数据做做相关运算,判断运算结果是否符合条件,若符合则输出帧头后面的有效数据。

基于AD9361的无线收发系统设计与实现 - 中国知网 (cnki.net)

 1. 模块代码

需要检测的帧头为13位巴克码组,1 1 1 1 1 0 0 1 1 0 1 0 1,首先将串行输入数据移位存放到一个13位寄存器input_buffer中。然后计算每一位加权的结果,例如input_buffer寄存器的第0位,对应的是巴克码组的第0位—1,那么如果输入的数据为1,则加权结果为1,如果为0,则加权结果为0。再例如input_buffer寄存器的第1位,对应的是巴克码组的第1位—0,那么如果输入的数据为1,则加权结果为0,如果为0,则加权结果为1。总之,如果对应输入数据与巴克码组相同,则加权结果为1,不同则为0。因此如果找到对应帧头数据,则加权求和sum的结果为13。最后根据加权求和的结果判断是否找到帧头,如果找到帧头,则将有效数据使能信号置为1,该信号会作为后级FIFO的写使能信号,并将帧头后面的有效数据写入到FIFO中。

`timescale 1ns / 1ps

module detect_frame_header(
    input clk,  //时钟
    input rst_n,    //低使能复位
    input data_in,  //输入串行数据
    input axis_last,    //将counter_tlast输入的last信号作为fifo的写不使能信号
    
    output reg [12:0]input_buffer, //输入缓存寄存器
    output wire [3:0]sum, //加权求和结果
    output reg valid_en,   //有效数据使能
    output reg data_out //输出串行数据
    );
reg [3:0]temp[12:0];    //加权结果
//reg signed[4:0]temp0; //加权结果,对应1
//reg signed[4:0]temp1; //加权结果,对应0
//reg signed[4:0]temp2; //加权结果,对应1
//reg signed[4:0]temp3; //加权结果,对应0
//reg signed[4:0]temp4; //加权结果,对应1
//reg signed[4:0]temp[5]; //加权结果,对应1
//reg signed[4:0]temp[6]; //加权结果,对应0
//reg signed[4:0]temp[7]; //加权结果,对应0
//reg signed[4:0]temp[8]; //加权结果,对应1
//reg signed[4:0]temp[9]; //加权结果,对应1
//reg signed[4:0]temp[10]; //加权结果,对应1
//reg signed[4:0]temp[11]; //加权结果,对应1
//reg signed[4:0]temp[12]; //加权结果,对应1
//reg [12:0]input_buffer; //输入缓存寄存器
//wire [3:0]sum; //加权求和结果
reg data_temp;  //串行数据延迟

//控制串行输入数据进入缓存寄存器
always@(posedge clk or negedge rst_n) begin
    if(!rst_n)
        input_buffer <= 13'd0;
    else
        input_buffer <= {input_buffer[11:0],data_in};
end

//加权结果,对应1
always@(posedge clk or negedge rst_n) begin
    if(!rst_n)
        temp[0] <= 4'd0;
    else if(input_buffer[0])
        temp[0] <= 4'd1;
    else
        temp[0] <= -4'd0;
end
//加权结果,对应0
always@(posedge clk or negedge rst_n) begin
    if(!rst_n)
        temp[1] <= 4'd0;
    else if(input_buffer[1])
        temp[1] <= -4'd0;
    else
        temp[1] <= 4'd1;
end
//加权结果,对应1
always@(posedge clk or negedge rst_n) begin
    if(!rst_n)
        temp[2] <= 4'd0;
    else if(input_buffer[2])
        temp[2] <= 4'd1;
    else
        temp[2] <= -4'd0;
end
//加权结果,对应0
always@(posedge clk or negedge rst_n) begin
    if(!rst_n)
        temp[3] <= 4'd0;
    else if(input_buffer[3])
        temp[3] <= -4'd0;
    else
        temp[3] <= 4'd1;
end
//加权结果,对应1
always@(posedge clk or negedge rst_n) begin
    if(!rst_n)
        temp[4] <= 4'd0;
    else if(input_buffer[4])
        temp[4] <= 4'd1;
    else
        temp[4] <= -4'd0;
end
//加权结果,对应1
always@(posedge clk or negedge rst_n) begin
    if(!rst_n)
        temp[5] <= 4'd0;
    else if(input_buffer[5])
        temp[5] <= 4'd1;
    else
        temp[5] <= -4'd1;
end
//加权结果,对应0
always@(posedge clk or negedge rst_n) begin
    if(!rst_n)
        temp[6] <= 4'd0;
    else if(input_buffer[6])
        temp[6] <= -4'd0;
    else
        temp[6] <= 4'd1;
end
//加权结果,对应0
always@(posedge clk or negedge rst_n) begin
    if(!rst_n)
        temp[7] <= 4'd0;
    else if(input_buffer[7])
        temp[7] <= -4'd0;
    else
        temp[7] <= 4'd1;
end
//加权结果,对应1
always@(posedge clk or negedge rst_n) begin
    if(!rst_n)
        temp[8] <= 4'd0;
    else if(input_buffer[8])
        temp[8] <= 4'd1;
    else
        temp[8] <= -4'd0;
end
//加权结果,对应1
always@(posedge clk or negedge rst_n) begin
    if(!rst_n)
        temp[9] <= 4'd0;
    else if(input_buffer[9])
        temp[9] <= 4'd1;
    else
        temp[9] <= -4'd0;
end
//加权结果,对应1
always@(posedge clk or negedge rst_n) begin
    if(!rst_n)
        temp[10] <= 4'd0;
    else if(input_buffer[10])
        temp[10] <= 4'd1;
    else
        temp[10] <= -4'd0;
end
//加权结果,对应1
always@(posedge clk or negedge rst_n) begin
    if(!rst_n)
        temp[11] <= 4'd0;
    else if(input_buffer[11])
        temp[11] <= 4'd1;
    else
        temp[11] <= -4'd0;
end
//加权结果,对应1
always@(posedge clk or negedge rst_n) begin
    if(!rst_n)
        temp[12] <= 4'd0;
    else if(input_buffer[12])
        temp[12] <= 4'd1;
    else
        temp[12] <= -4'd0;
end

//计算加权求和结果
assign sum=temp[0]+temp[1]+temp[2]+temp[3]+temp[4]+temp[5]+temp[6]+temp[7]+temp[8]+temp[9]+temp[10]+temp[11]+temp[12];

//控制输出串行数据,需要将数据延迟一拍,目的是与有效数据使能信号valid_en对齐
always@(posedge clk or negedge rst_n) begin
    if(!rst_n)
        data_temp <= 1'b0;
    else if(axis_last)
        data_temp <= 1'b0;
    else
        data_temp <= data_in;
end
always@(posedge clk or negedge rst_n) begin
    if(!rst_n)
        data_out <= 1'b0;
    else if(axis_last)
        data_out <= 1'b0;
    else
        data_out <= data_temp;
end

//控制有效数据使能valid_en
always@(posedge clk or negedge rst_n) begin
    if(!rst_n)
        valid_en <= 1'b0;
    else if(axis_last)
        valid_en <= 1'b0;
    else if(sum == 4'd13)
        valid_en <= 1'b1;
    else
        valid_en <= valid_en;
end

endmodule

需要注意的是,当帧头输入到帧头检测模块中,需要延迟两拍后才会产生有效数据使能信号,因此有效数据输出的时候才能与使能信号对齐。其次是将axis_last信号作为使能信号结束的判断标志,产生last信号表示一帧传输完成,则可以拉低有效数据使能信号,将FIFO的写使能失效。

2. 模块仿真

测试代码如下,与实验一的写法相同,产生一个包含帧头的串行数据

`timescale 1ns / 1ps

module tb_detect_frame_header();

//输入
reg clk;  //时钟
reg rst_n;    //低使能复位
wire data_in;    //输入串行数据
reg axis_last;    //将counter_tlast输入的last信号作为fifo的写不使能信号
//输出
wire [12:0]input_buffer; //输入缓存寄存器
wire [3:0]sum; //加权求和结果 五位有符号数表示范围-15~+15
wire valid_en;    //有效数据使能
wire data_out; //输出串行数据

reg [31:0]temp_data;    //模拟输入数据
reg [15:0] num; //计数

//信号初始化
initial begin
    clk = 1'b0;
    rst_n = 1'b0;
    axis_last = 1'b0;
    temp_data=32'b0001_1111_0011_0101_1001_0010_0011_0100;
    #30
    rst_n = 1'b1;
    #660
    axis_last = 1'b1;
end

//生成时钟
always #10 clk = ~clk;

//输出数据
always@(posedge clk) begin
    if(!rst_n)
        num <= 16'd31;
    else
        num <= num - 16'd1;
end
assign data_in = temp_data[num];

detect_frame_header detect_frame_header_inst(
    .clk(clk),
    .rst_n(rst_n),
    .data_in(data_in),
    .axis_last(axis_last),
    
    .input_buffer(input_buffer),
    .sum(sum),
    .valid_en(valid_en),
    .data_out(data_out)
);
endmodule

仿真结果如下,从图中可以看出,当帧头输入到检测模块后,延迟两拍使能信号置为1,并且开始输出有效数据,当axis_last信号被置为1后,使能信号置为0,功能与预期相符。

串行数据添加帧头以及帧头检测

 三 帧头环路测试

该实验在基于FIFO的串并转换环路实验_yoga1020的博客-CSDN博客

的基础上进行改进,1.FIFO设置为读写时钟独立的模式;2.添加Clocking Wizard模块产生慢时钟;3.添加ind_clk_fifo_rst模块产生FIFO的复位信号;4.添加add_frame_header和detect_frame_header模块,给串行数据添加帧头并检测帧头,将有用的数据转换为并行数据,通过DMA保存到内存DDR3中。

1. 硬件设计

Block Design如下图所示,数据环路用橘黄色线标记出来,

串行数据添加帧头以及帧头检测

 关于时钟IP核的使用可以参考:

Chapter004-FPGA学习之IP核相关功能【时钟、RAM、FIFO】_ASWaterbenben的博客-CSDN博客

Vivado 时钟IP核的使用_海盐nnn的博客-CSDN博客_vivado 时钟ip

 关于FIFO的异步复位问题,可以参考官方文档pg057以及下面的链接

Xilinx FIFO Generator 需要注意RST复位_ShareWow丶的博客-CSDN博客_fifo复位需要几个时钟周期

串行数据添加帧头以及帧头检测 如上图所示,使用安全电路的FIFO在异步复位的时候,复位信号需要维持至少8个慢时钟周期,并且复位开始后的60个慢时钟周期内都不能进行读写操作。因此设计产生FIFO复位信号的模块ind_clk_fifo_rst,该模块将DMA产生的复位信号作为输入,和FIFO同用慢时钟,产生可以维持8个周期的复位信号,并且在60个慢时钟周期后产生复位结束信号rst_down。

rst_done和header_end信号(包含not_empty)相与后作为FIFO_8to1的读使能信号;

rst_done和axis_converter的tvalid以及not_full信号相与后作为FIFO_8to1的写使能信号;

rst_done和axis_converter的tready以及not_empty信号相与后作为FIFO_1to8的读使能信号;

rst_done和有效数据使能valid_en以及not_full信号相与后作为FIFO_1to8的写使能信号;

这样可以保证在复位结束后FIFO才开始读写操作,并且也不会读空以及写满。

ind_clk_fifo_rst的代码如下:

module ind_clk_fifo_rst(
    input clk,  //FIFO的慢时钟
    input sys_rst_n,    //系统复位
    input dma_rst_n,  //DMA产生的复位信号
    
//    output reg [7:0]cnt,   //计数器
//    output reg rst_flag,  //DMA产生复位信号标志
    output reg fifo_rst,   //给FIFO的复位信号
    output reg rst_done    //复位完成信号
    );

reg [7:0]cnt;   //计数器
reg rst_flag;  //DMA产生复位信号标志

//检测DMA是否产生复位信号
always@(posedge clk or negedge sys_rst_n or negedge dma_rst_n) begin
    if(!sys_rst_n)
        rst_flag <= 1'b1;
    else if(!dma_rst_n)
        rst_flag <= 1'b1;
    else if(cnt == 8'd63)
        rst_flag <= 1'b0;
    else
        rst_flag <= rst_flag;
end

//控制计数器
always@(posedge clk or negedge sys_rst_n or negedge dma_rst_n) begin
    if(!sys_rst_n || !dma_rst_n || cnt == 8'd63)
        cnt <= 8'd0;
    else if(rst_flag)
        cnt <= cnt + 8'd1;
end

//控制FIFO的复位信号
always@(posedge clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        fifo_rst <= 1'b0;
    else if(rst_flag && cnt <= 8'd9 && cnt >8'd0)
        fifo_rst <= 1'b1;
    else
        fifo_rst <= 1'b0; 
end

//控制复位完成信号
always@(posedge clk or negedge sys_rst_n or negedge dma_rst_n) begin
    if(!sys_rst_n || !dma_rst_n)
        rst_done <= 1'b0;
    else if(cnt == 8'd63)
        rst_done <= 1'b1;
    else
        rst_done <= rst_done; 
end

endmodule

 2.软件设计

代码部分做了部分修改,因为还是遇到了单步调试没有问题,但是直接运行就会数据不符。

首先如果禁用cache,则在接收完成后打印一遍内存数据后,数据恢复正常。但是打印的数据显示前几个是错误的,也就是打印前后内存数据不一样,而且也不用全打印,只打印前8个数据就可以,但小于8还是会出问题。

串行数据添加帧头以及帧头检测

如果使用cache,则接收完成后需要使用

Xil_DCacheFlush();

Xil_DCacheInvalidateRange((UINTPTR)RxBufferPtr, MAX_PKT_LEN);

再打印前8个数据,否则会出问题。

使用ILA查看DMA的M_AXI_S2MM接口,数据传输都是正常的,说明还是读取内存的问题。目前不清楚怎么更好的解决这个问题。

3.运行结果

软件运行结果如下

串行数据添加帧头以及帧头检测

通过ILA捕获到的数据如下图所示,从图中可以看出添加帧头数据结束后header_end置为1,并开始输出FIFO中的数据。检测到帧头后,valid_en置为1,也就是fifo_1_to_8的读使能置为1,帧头后面的数据保存到FIFO中,转换为8位并行数据后通过AXIS传输。

串行数据添加帧头以及帧头检测

 这里需要注意的一个问题是,JTAG的时钟频率要小于ILA的时钟频率,JTAG默认时钟频率为15MHz,由于这里ILA使用的是5MHz的慢时钟,因此在硬件调试的时候会报错,所以需要将JTAG的时钟频率改为1MHz。关于这一点可以参考以下链接:

vivado-ila调试之时钟问题 - 灰信网(软件开发博客聚合) (freesion.com)

【vivado】ILA调试报错 The debug hub core was not detected 以及 Data read from hw_ila [hw_ila_1] is corrupted..._weixin_33909059的博客-CSDN博客

 文章来源地址https://www.toymoban.com/news/detail-425245.html

四 设计改进 

在前面的设计中涉及到跨时钟域控制的问题,可能会出现时序无法通过的问题。例如在并转串中FIFO的wr_en应该是在快时钟下,但是其控制信号为m_axis_tvalid & rst_done_p & (~fifo_full),其中rst_done是在慢时钟下产生的,这就导致跨时钟域控制,从而时序不收敛。因此需要对某些模块进行改进。

1.修改ind_clk_fifo_rst模块

首先需要修改的模块就是产生FIFO复位信号的模块。该模块根据AXIS_DMA输出的复位信号产生一个给FIFO使用的复位信号,并控制FIFO复位完成的时间。由于DMA输出的复位信号是在快时钟下,而产生的FIFO复位信号是在慢时钟下,因此导致跨时钟域控制的问题,为了解决该问题,参考了以下解决跨时钟域时序不收敛的处理方法,

FPGA中跨时钟域数据处理办法_好运公爵的博客-CSDN博客

FPGA跨时钟域处理方法_yang)的博客-CSDN博客_fpga跨时钟域处理

但经过尝试后并没有用。因此改使用Processor System Reset模块将DMA的复位信号同步到慢时钟下,并对之前的模块进行了修改: 

Xilinx IP解析之Processor System Reset v5.0_徐晓康的博客的博客-CSDN博客_proc_sys_reset

主要的改动就是将慢时钟复位和快时钟复位分开处理。根据dma输出的快时钟下复位信号,产生rst_flag,并进行计数,最终产生快时钟下的rst_done;根据同步到慢时钟下的dma复位信号,产生慢时钟下的rst_done和fifo_rst。具体代码如下:

module ind_clk_fifo_rst(
    input s_aclk,  //慢时钟(串行信号)
    input s_sys_rstn,  //慢时钟的复位信号
    input p_aclk,   //快时钟(并行信号)
    input p_sys_rstn,    //快时钟的复位信号
    input dma_rstn_p, //dma输出的复位信号(快时钟)
    input dma_rstn_s, //dma输出的复位信号(同步到慢时钟下)
    
//    output reg [8:0]cnt_p;   //快时钟下的计数器
//    output reg [8:0]cnt_s;   //慢时钟下的计数器
    output reg dma_rstn_p_ext,    //展宽后的dma输出的复位信号(快时钟)
    output reg fifo_rst,   //给FIFO的复位信号
    output reg rst_done_s,   //慢时钟复位完成标志
    output reg rst_done_p   //快时钟复位完成标志
    );

reg [9:0]cnt_p;   //快时钟下的计数器
reg [8:0]cnt_s;   //慢时钟下的计数器
reg rst_flag_p;  //快时钟DMA产生复位信号标志
reg rst_flag_s;  //慢时钟DMA产生复位信号标志
reg cnt_100_flag_p; //快时钟下cnt100信号
reg cnt_100_flag_s; //慢时钟下cnt100信号
reg cnt_10_flag_s; //慢时钟下cnt10信号
reg cnt_1_flag_p; //快时钟下cnt1信号

//检测DMA是否产生复位信号
always@(posedge p_aclk or negedge p_sys_rstn or negedge dma_rstn_p) begin
    if(!p_sys_rstn || !dma_rstn_p)
        rst_flag_p <= 1'b1;
    else if(cnt_100_flag_p)
        rst_flag_p <= 1'b0;
//    else
//        rst_flag_p <= rst_flag_p;
end
always@(posedge s_aclk or negedge s_sys_rstn or negedge dma_rstn_s) begin
    if(!s_sys_rstn || !dma_rstn_s)
        rst_flag_s <= 1'b1;
    else if(cnt_100_flag_s)//(cnt == 8'd100)
        rst_flag_s <= 1'b0;
//    else
//        rst_flag_s <= rst_flag_s;
end

//控制计数器
always@(posedge p_aclk or negedge p_sys_rstn or negedge dma_rstn_p) begin
    if(!p_sys_rstn || !dma_rstn_p)
        cnt_p <= 10'd0;
    else if(rst_flag_p)
        cnt_p <= cnt_p + 10'd1;
end
always@(posedge s_aclk or negedge s_sys_rstn or negedge dma_rstn_s) begin
    if(!s_sys_rstn || !dma_rstn_s)
        cnt_s <= 9'd0;
    else if(rst_flag_s)
        cnt_s <= cnt_s + 9'd1;
end

//控制cnt_flag100
always@(posedge p_aclk or negedge p_sys_rstn or negedge dma_rstn_p) begin
    if(!p_sys_rstn || !dma_rstn_p)
        cnt_100_flag_p <= 1'b0;
    else if(cnt_p >= 10'd800)
        cnt_100_flag_p <= 1'b1;
    else
        cnt_100_flag_p <= 1'b0;
end
always@(posedge s_aclk or negedge s_sys_rstn or negedge dma_rstn_s) begin
    if(!s_sys_rstn || !dma_rstn_s)
        cnt_100_flag_s <= 1'b0;
    else if(cnt_s >= 9'd80)
        cnt_100_flag_s <= 1'b1;
    else
        cnt_100_flag_s <= 1'b0;
end

//控制cnt_flag10
always@(posedge s_aclk or negedge s_sys_rstn or negedge dma_rstn_s) begin
    if(!s_sys_rstn || !dma_rstn_s)
        cnt_10_flag_s <= 1'b0;
    else if(cnt_s <= 9'd10 && cnt_s >9'd0)
        cnt_10_flag_s <= 1'b1;
    else
        cnt_10_flag_s <= 1'b0;
end

//控制cnt_flag1
always@(posedge p_aclk or negedge p_sys_rstn or negedge dma_rstn_p) begin
    if(!p_sys_rstn || !dma_rstn_p)
        cnt_1_flag_p <= 1'b0;
    else if(cnt_p >= 10'd20)
        cnt_1_flag_p <= 1'b1;
    else
        cnt_1_flag_p <= 1'b0;
end

//控制FIFO的复位信号
always@(posedge s_aclk or negedge s_sys_rstn or negedge dma_rstn_s) begin
    if(!s_sys_rstn || !dma_rstn_s)
        fifo_rst <= 1'b0;
    else
        fifo_rst <= cnt_10_flag_s;
end

//控制慢信号下的复位完成信号
always@(posedge s_aclk or negedge s_sys_rstn or negedge dma_rstn_s) begin
    if(!s_sys_rstn || !dma_rstn_s)
        rst_done_s <=1'b0;
    else
        rst_done_s <= cnt_100_flag_s;
end

//控制快信号下的复位完成信号
always@(posedge p_aclk or negedge p_sys_rstn or negedge dma_rstn_p) begin
    if(!p_sys_rstn || !dma_rstn_p)
        rst_done_p <=1'b0;
    else
        rst_done_p <= cnt_100_flag_p;
end

//在快时钟下,生成展宽后的dma输出的复位信号
always @ (posedge p_aclk or negedge p_sys_rstn)
begin
if (p_sys_rstn == 1'b0)
    dma_rstn_p_ext <= 1'b1;
else if (~dma_rstn_p) //检测到dma输出的复位信号
    dma_rstn_p_ext <= 1'b0;
else if (cnt_1_flag_p) //检测到复位完成信号,拉高
    dma_rstn_p_ext <= 1'b1;
else;
end

endmodule

需要注意的是,因为快时钟的频率更快,所以快时钟的计数目标值要更高。需要先产生慢时钟的复位完成信号,再产生快时钟的复位完成信号,这样对FIFO的读写没有影响。在使用的时候还需要连接Processor System Reset模块,产生慢时钟下的DMA复位信号。

串行数据添加帧头以及帧头检测

2.并转串的控制信号

(1)将fifo_not_full & rst_done_p(快时钟)作为m_axis_tready信号,表示FIFO不满并且快时钟复位完成时才开启axis传输。

(2)将m_axis_tvalid & fifo_not_full & rst_done_p(快时钟)作为fifo_wr_en信号,表示axis准备好、FIFO不满且快时钟复位完成时才开始往FIFO里些数据。

(3)将header_end(包含not empty) & rst_done_s(慢时钟)作为fifo_rd_en信号,表示FIFO不空、帧头输出完毕且慢时钟复位完成后才从FIFO中读出数据。

3.串转并的控制信号

(1)将not_tlast & fifo_not_empty & rst_done_p(快时钟)作为s_axis_tvalid信号,表示FIFO非空、快时钟复位完成且不是最后一个字节传输时,才开启axis传输。否则会出现两次传输之间有FF信号保存到converter的接收端,导致第二次接收数据出问题。

(2)将s_axis_tready & fifo_not_empty & rst_done_p(快时钟)作为fifo_rd_en信号,表示FIFO非空、快时钟复位完成且axis准备好才开始从FIFO中读出数据。

(3)将fifo_not_full & valid_en & rst_done_s(慢时钟)作为fifo_wr_en信号,表示FIFO不满、慢时钟复位完成且检测到帧头后才向FIFO中写入数据。

4.修改detect_frame_header模块

该模块取消掉axis_last控制信号。改让tlast信号通过控制s_axis_tvalid信号来控制什么时候结束串到并的转换。

5.修改counter_tlast模块

之前的设计中直接监测m_axis_tvalid的上升沿产生last_sig信号,没有同步到时钟下。因此参考一下链接实现上升沿检测:

FPGA基础学习——Verilog实现的边沿检测(上升沿下降沿检测)及Modelsim仿真_Fighting_XH的博客-CSDN博客_verilog边沿检测

module counter_tlast
#(
    parameter   CNT_TARGET = 16'd20   //parameter定义参数,目标字节数
)
(
    input   wire    sys_clk,
    input   wire    sys_rst_n,
    
//    input   wire    cnt_sig,    //计数信号
//    output reg[15:0] cnt_num,    
    input edge_sig,
//    output last_sig_r1,
//    output reg last_sig_r2,
//    input m_axis_tvalid,
    output  last_sig  //tlast信号  
);

reg     [15:0]  cnt_num; //16位宽的计数器 
wire data;
reg [1:0]data_r;
wire pose_edge_flag;
wire last_sig_r1;
reg last_sig_r2;


assign data = edge_sig;
//设置两个寄存器,实现前后电平状态的寄存
//相当于对dat_i 打两拍
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        data_r <= 2'b00;
    else
        data_r <= {data_r[0], data};    //{前一状态,后一状态}
end
//组合逻辑进行边沿检测
//data_r[1]表示前一状态,data_r[0]表示后一状态
assign pose_edge_flag = ~data_r[1] & data_r[0];

/* 计数器cnt_num */
always@ (posedge sys_clk or negedge sys_rst_n)
begin
    if(sys_rst_n == 1'b0)  //复位信号有效时(复位时),cnt_num清零
        cnt_num <= 16'd0;
    else if (cnt_num == (CNT_TARGET>>2)-1)   //计数到预设值,cnt_num清零
        cnt_num <= cnt_num;
    else if(pose_edge_flag)
        cnt_num <= cnt_num + 16'd1;    //每个cnt_sig上升沿cnt_num自增1
end

/* 由计数器控制的last_sig */
assign last_sig_r1 = (cnt_num == (CNT_TARGET>>2)-1)? 1'b1:1'b0;
always@ (posedge sys_clk or negedge sys_rst_n)
begin
    if(sys_rst_n == 1'b0)   //初始状态,需要使用dma的复位来给计数器进行复位
        last_sig_r2 <= 1'b0;
    else if (last_sig_r1 & edge_sig)  //计数器cnt_num记满一次,last_sig拉高
        last_sig_r2 <= 1'b1;
//    else     //cnt未记满,last_sig保持低电平
//        last_sig = 1'b0;
    else
        last_sig_r2 <= last_sig_r2;
end
assign last_sig = last_sig_r2 | (last_sig_r1 & edge_sig);
//assign last_sig = last_sig_r2;

endmodule

 需要注意的是,为了使tlast信号能和tvalid信号对齐,需要使用如下语句

last_sig = last_sig_r2 | (last_sig_r1 & edge_sig);

6.修改后的整体框图

串行数据添加帧头以及帧头检测

7.软件运行结果

软件的运行结果如下,可以正确发射和接收。

串行数据添加帧头以及帧头检测

软件部分发现之前需要打印的发送数据的问题解决了,现在不需要打印也可以正确接收,但还是需要使用Xil_DCacheFlush()更新内存。

串行数据添加帧头以及帧头检测

 

 

到了这里,关于串行数据添加帧头以及帧头检测的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包