点击进入高速收发器系列文章导航界面
前文完成了发送模块的设计,本文接着完成接收模块的设计,接收模块相对发送模块会更加麻烦。
1、设计思路
前文在讲解官方示例工程时,提到GTX IP的接收部分没有做字对齐,需要用户自己编写字对齐模块,由于发送端可能发送任意字节数据,因此停止位也可能出现在一帧数据的任意位置。
下图是自定义的帧格式,在实际收发数据过程中,部分逗号可能被用于时钟纠正。在检测数据帧时,将24’hbc50fb作为一帧数据的帧头,帧尾依旧是8’hfd。
由于GTX IP的接收通道没有进行字对齐,接收的帧头和帧尾的数据可能会出现下图中的16种现象。
起始位8’hfb可能出现在一个数据的任何字节位置,停止位8’hfd也可能出现在一个数据的任意字节位置(因为发送端可能发送任意字节数据)。这些条件会使得输出给用户的最后一个数据的部分数据无效,需要用掩码进行控制。
蓝色数字用于记录起始位和停止位在所处数据中哪个字节,这与最后一个数据的掩码信号直接相关。
黄色的掩码表示这个数据会出现在下一个数据中,与停止位对齐的数据都i是有效的,由三种情况出现这种情况。
可以对图2中的规律进行总结,首先当停止位所在字节数减去起始位所在字节数等于1时,输出给用户的最后一个数据所有字节均有效。
当起始位所在位置与停止位所在位置相差2时,输出给用户的最后一个数据只有最高字节有效。
当停止位所在字节数减去起始位所在字节数等于3 或者 起始位所在字节数减去停止位所在字节数等于1时,输出给用户的最后一个数据高两字节数据均有效。
当起始位和停止位所在的位置相等时,输出给用户最后一个数据的高三字节均有效。
本次主要思路是使用移位寄存器对GTX IP输出数据进行暂存,然后检测帧头,并且确定起始位和停止位在数据中的位置,便于后续生成数据掩码信号。
当检测到帧头之后,就可以根据起始位所在位置向用户输出数据了,由于模块内部没有存储结构,因此数据信号不需要等待用户的应答信号。
本模块设计的难点在于一帧数据的最后一个数据的掩码信号和最后一个数据有效指示信号的生成,其余信号都比较简单。
2、代码讲解
本模块并没有使用计数器、状态机、FIFO等结构,仅仅以输出数据和K码指示信号的移位寄存器作为主体,完成设计。
首先是端口部分,包括GTX IP输出端口的输出数据、K码指示信号、字节对齐指示信号,用户端口是去掉数据应答信号的axi_stream流端口,如下所示。
module phy_rx (input clk ,//系统时钟信号;input rst ,//系统复位信号,高电平有效;output reg [31 : 0] axi_s_data ,//输出接收的数据,先输出高字节数据;output reg [3 : 0] axi_s_keep ,//输出接收数据有效指示信号;output reg axi_s_last ,//输出一帧数据的最后一个数据指示信号;output reg axi_s_valid ,//输出数据有效指示信号;input gt_rx_bytealign ,//输入数据字节对齐指示信号;input [31 : 0] gt_rx_data ,//输入待解析的数据;input [3 : 0] gt_rx_char //输入数据K码指示信号,高电平有效;
);
下面是移位寄存器,对GTX IP输出的数据和K码指示信号暂存,注意在写移位寄存器的时候,尽量不要写复位相关的代码,因为移位寄存器不需要复位。
//将接收的数据和K码指示信号暂存,用于后续处理。always@(posedge clk)begin{gt_rx_char_r[2],gt_rx_char_r[1],gt_rx_char_r[0]} <= {gt_rx_char_r[1],gt_rx_char_r[0],gt_rx_char};{gt_rx_data_r[2],gt_rx_data_r[1],gt_rx_data_r[0]} <= {gt_rx_data_r[1],gt_rx_data_r[0],gt_rx_data};eof_flag_r <= {eof_flag_r[0],eof_flag};end
下面检测图2中可能出现进行四种帧头的数据格式,检测到帧头后把commoa_flag拉高一个时钟,与gt_rx_data_r[1]对齐。
//当检测到24'hBC50FB时,表示检测到同步码且检测到起始位,同步标志信号与gt_rx_data_r[1]对齐。//为了后续方便生成keep信号,只能将数据打两拍在进行检测。always@(posedge clk)beginif(rst)begin//初始值为0;commoa_flag <= 1'b0;end//检测到的序列是32'h50bc50bc,32'hXXXXXXfb时拉高,表示检测到同步码。else if((gt_rx_data_r[1][31:16] == 16'h50bc && gt_rx_char_r[1][3:2] == 2'b01) && (gt_rx_data_r[0][7:0] == 8'hfb && gt_rx_char_r[0][0] == 1'b1))begincommoa_flag <= 1'b1;end//检测到的序列是32'hBC50BCXX,32'hXXXXfb50时拉高,表示检测到同步码。else if((gt_rx_data_r[1][31:24] == 8'hbc && gt_rx_char_r[1][3] == 1'b1) && (gt_rx_data_r[0][15:0] == 16'hfb50 && gt_rx_char_r[0][1:0] == 2'b10))begincommoa_flag <= 1'b1;end//检测到的序列是32'h50BCXXXX,32'hXXfb50BC时拉高,表示检测到同步码。else if(gt_rx_data_r[0][23:0] == 24'hfb50bc && gt_rx_char_r[0][2:0] == 3'b101)begincommoa_flag <= 1'b1;end//检测到的序列是32'hBCXXXXXX,32'hfb50BC50时拉高,表示检测到同步码。else if(gt_rx_data_r[0][31:8] == 24'hfb50bc && gt_rx_char_r[0][3:1] == 3'b101)begincommoa_flag <= 1'b1;endelse begincommoa_flag <= 1'b0;endend
检测到帧头后,将接收数据标志信号(rx_data_flag)拉高,直到接收完一帧数据后拉低。
//接收数据标志信号,与gt_rx_data_r[2]对齐。always@(posedge clk)beginif(rst)begin//初始值为0;rx_data_flag <= 1'b0;endelse if(axi_s_last)begin//当检测到停止位时拉低。rx_data_flag <= 1'b0;endelse if(commoa_flag && gt_rx_bytealign)begin//当检测到同步码和起始位后拉高,表示一帧数据的开始。rx_data_flag <= 1'b1;endend
并且在检测到帧头后,需要记录起始位位于数据中的字节数。
//检测到起始位标志信号,以及标志信号位于所在数据的字节数,与gt_rx_data_r[2]对齐。always@(posedge clk)beginif(rst)begin//初始值为0;sof_local <= 3'd0;endelse if(commoa_flag && gt_rx_bytealign)begin//检测到同步码和起始位时,记录起始位位于该数据的字节位置。if((gt_rx_data_r[1][7:0] == 8'hfb) && gt_rx_char_r[1][0])sof_local <= 3'd0;else if((gt_rx_data_r[1][15:8] == 8'hfb) && gt_rx_char_r[1][1])sof_local <= 3'd1;else if((gt_rx_data_r[1][23:16] == 8'hfb) && gt_rx_char_r[1][2])sof_local <= 3'd2;else if((gt_rx_data_r[1][31:24] == 8'hfb) && gt_rx_char_r[1][3])sof_local <= 3'd3;endend
之后需要检测一帧数据的帧尾8’hfd,并且记录停止位8’hfd在该数据中的字节数。
//检测到停止位标志信号,以及记录停止位位于该数据的字节位置。always@(posedge clk)beginif(rst)begin//初始值为0;eof_flag <= 1'b0;eof_local <= 3'd0;endelse if(rx_data_flag)begin//当接收数据时,检测停止位的位置。if((gt_rx_data[7:0] == 8'hfd) && gt_rx_char[0])begin//检测到停止位位于最低字节。eof_flag <= 1'b1;eof_local <= 3'd0;endelse if((gt_rx_data[15:8] == 8'hfd) && gt_rx_char[1])begineof_flag <= 1'b1;eof_local <= 3'd1;endelse if((gt_rx_data[23:16] == 8'hfd) && gt_rx_char[2])begineof_flag <= 1'b1;eof_local <= 3'd2;endelse if((gt_rx_data[31:24] == 8'hfd) && gt_rx_char[3])begin//检测到停止位位于最高字节。eof_flag <= 1'b1;eof_local <= 3'd3;endelse begineof_flag <= 1'b0;endendelse begineof_flag <= 1'b0;endend
当处于接收数据状态(rx_data_flag为高电平)时,根据起始位所在字节数,把接收的前后两字节数据拼接成输出数据。
注意GTX IP输出数据是小端对齐,而输出给用户的数据是大端对齐,此处在输出数据时需要进行调换。比如起始位位于最高字节时,直接将gt_rx_data_r[1]的高低字节顺序调换输出即可。
//生成输出数据,由于输出数据是先输出高字节数据,而接收的数据是先发送低字节数据,所以在拼接时需要进行纠正处理。always@(posedge clk)beginif(rst)begin//初始值为0;axi_s_data <= 32'd0;endelse if(rx_data_flag)begincase (sof_local)//rx_data_flag与gt_rx_data_r[2]对齐,因此赋值时直接使用gt_rx_data_r[2]的信号。2'd0 : axi_s_data <= {gt_rx_data_r[2][15:8],gt_rx_data_r[2][23:16],gt_rx_data_r[2][31:24],gt_rx_data_r[1][7:0]};//将先接收的数据放在高字节处进行输出。2'd1 : axi_s_data <= {gt_rx_data_r[2][23:16],gt_rx_data_r[2][31:24],gt_rx_data_r[1][7:0],gt_rx_data_r[1][15:8]};//将先接收的数据放在高字节处进行输出。2'd2 : axi_s_data <= {gt_rx_data_r[2][31:24],gt_rx_data_r[1][7:0],gt_rx_data_r[1][15:8],gt_rx_data_r[1][23:16]};//将先接收的数据放在高字节处进行输出。2'd3 : axi_s_data <= {gt_rx_data_r[1][7:0],gt_rx_data_r[1][15:8],gt_rx_data_r[1][23:16],gt_rx_data_r[1][31:24]};//将先接收的数据放在高字节处进行输出。default : ;endcaseendend
至于数据掩码信号,通过两个always模块生成,第一个模块通过图2描述的方式生成最后一个数据的掩码信号。
//计算最后一个输出数据的掩码信号;always@(posedge clk)beginif(rst)begin//初始值为0;axi_s_keep_r <= 4'hf;endelse if(eof_flag)begin//结束标志有效时,通过起始位和停止位的位置,计算最后一个数据的有效字节位置。if(eof_local - sof_local == 3'd1)beginaxi_s_keep_r <= 4'b1111;endelse if((eof_local - sof_local == 3'd2) || (sof_local - eof_local == 3'd2))beginaxi_s_keep_r <= 4'b1000;endelse if((eof_local - sof_local == 3'd3) || (sof_local - eof_local == 3'd1))beginaxi_s_keep_r <= 4'b1100;endelse if(eof_local == sof_local)beginaxi_s_keep_r <= 4'b1110;endendend
但是部分掩码信号应该作用在后一个输出数据(图2中黄色部分掩码)。因此需要第二个always确定何时输出最后一个数据的掩码。
//生成数据掩码信号;always@(posedge clk)beginif(rst)begin//初始值为4'hf;axi_s_keep <= 4'hf;end//当最后一个数据刚好在停止位所在数据时,将计算结果赋值输出。else if(((sof_local > 1) || ((sof_local <= 1) && (((eof_local - sof_local < 2) && (eof_local >= sof_local)) || (sof_local > eof_local)))) && eof_flag_r[0])beginaxi_s_keep <= axi_s_keep_r;endelse if((((sof_local <= 1) && (eof_local > sof_local) && (eof_local - sof_local >= 2))) && eof_flag_r[1])beginaxi_s_keep <= axi_s_keep_r;endelse begin//其余时间输出的数据均有效。axi_s_keep <= 4'hf;endend
之后是一帧数据的结尾数据的指示信号,这个信号与K码指示信号类似,也是根据图二生成的信号,对应代码如下所示。
//生成输出最后一个数据的指示信号;always@(posedge clk)beginif(rst)begin//初始值为0;axi_s_last <= 1'b0;endelse if(sof_local > 1)beginif((sof_local == 3) && (eof_local == 0))//axi_s_last <= eof_flag;elseaxi_s_last <= eof_flag_r[0];endelse if(((eof_local - sof_local < 2) && (eof_local >= sof_local)) || (sof_local > eof_local))beginaxi_s_last <= eof_flag_r[0];endelse if((eof_local > sof_local) && (eof_local - sof_local >= 2))beginaxi_s_last <= eof_flag_r[1];endelse beginaxi_s_last <= 1'b0;endend
最后是输出数据有效指示信号,接收数据后一直为高电平,直到向用户输出最后一个数据后拉低。
//生成数据有效指示信号;always@(posedge clk)beginif(rst)begin//初始值为0;axi_s_valid <= 1'b0;endelse if(axi_s_last)begin//接收完数据时拉低。axi_s_valid <= 1'b0;endelse if(rx_data_flag)begin//开始接收数据时拉高。axi_s_valid <= 1'b1;endend
当理解了图2后,整个设计其实难度也比较小,主要的就是掩码信号和最后数据指示信号的生成,以及一些信号之间的对齐关系。
3、模块仿真
将PHY发送和接收模块例化形成PHY顶层模块,对应代码如下所示。
//--###############################################################################################
//--#
//--# File Name : pdy_module
//--# Designer : 数字站
//--# Tool : Vivado 2021.1
//--# Design Date : 2024.3.17
//--# Description : 例化PHY的收发模块。
//--# Version : 0.0
//--# Coding scheme : GBK(If the Chinese comment of the file is garbled, please do not save it and check whether the file is opened in GBK encoding mode)
//--#
//--###############################################################################################
module phy_module (input i_tx_clk ,//发送部分时钟信号;input i_tx_rst ,//发送部分复位信号,高电平有效;input i_rx_clk ,//接收部分时钟信号;input i_rx_rst ,//接收模块复位信号,高电平有效;input [31:0] i_axi_s_data ,//AXI的数据输入信号;input [3 :0] i_axi_s_keep ,//AXI的数据掩码信号,低电平有效;input i_axi_s_last ,//AXI输入最后一个数据指示信号;input i_axi_s_valid ,//AXI输入数据有效指示信号;output o_axi_s_ready ,//AXI输入数据应答信号;output [31:0] o_axi_s_data ,//输出接收的数据,先输出高字节数据;output [3 :0] o_axi_s_keep ,//输出接收数据有效指示信号;output o_axi_s_last ,//输出一帧数据的最后一个数据指示信号;output o_axi_s_valid ,//输出数据有效指示信号;input gt_tx_done ,//GTX发送部分初始化成功,高电平有效;output [31:0] gt_tx_data ,//GTX发送数据;output [3 :0] gt_tx_char ,//GTX发送数据K码指示信号,高电平有效;input gt_rx_bytealign ,//输入数据字节对齐指示信号;input [31:0] gt_rx_data ,//输入待解析的数据;input [3 :0] gt_rx_char //输入数据K码指示信号,高电平有效;
);//例化PHY芯片的发送模块。phy_tx u_phy_tx(.clk ( i_tx_clk ),//发送部分时钟信号;.rst ( i_tx_rst ),//发送部分复位信号,低电平有效;.axi_s_data ( i_axi_s_data ),//AXI的数据输入信号;.axi_s_keep ( i_axi_s_keep ),//AXI的数据掩码信号,低电平有效;.axi_s_last ( i_axi_s_last ),//AXI输入最后一个数据指示信号;.axi_s_valid ( i_axi_s_valid ),//AXI输入数据有效指示信号;.axi_s_ready ( o_axi_s_ready ),//AXI输入数据应答信号;.gt_tx_done ( gt_tx_done ),//GTX发送部分初始化成功,高电平有效;.gt_tx_data ( gt_tx_data ),//GTX发送数据;.gt_tx_char ( gt_tx_char ) //GTX发送数据K码指示信号,高电平有效;);//例化PHY芯片的接收模块。phy_rx u_phy_rx(.clk ( i_rx_clk ),//接收部分时钟信号;.rst ( i_rx_rst ),//接收模块复位信号,低电平有效;.axi_s_data ( o_axi_s_data ),//输出接收的数据,先输出高字节数据;.axi_s_keep ( o_axi_s_keep ),//输出接收数据有效指示信号;.axi_s_last ( o_axi_s_last ),//输出一帧数据的最后一个数据指示信号;.axi_s_valid ( o_axi_s_valid ),//输出数据有效指示信号;.gt_rx_bytealign( gt_rx_bytealign ),//输入数据字节对齐指示信号;.gt_rx_data ( gt_rx_data ),//输入待解析的数据;.gt_rx_char ( gt_rx_char ) //输入数据K码指示信号,高电平有效;);endmodule
PHY顶层模块的TestBench如下所示,可以通过修改TX_KEEP和SOF_LOCAL来确定接收模块接收数据的起始位和停止位的位置,进而验证图2中16种数据格式。
//--###############################################################################################
//--#
//--# File Name : tb_phy_module
//--# Designer : 数字站
//--# Tool : Vivado 2021.1
//--# Design Date : 2024.3.17
//--# Description : TestBench
//--# Version : 0.0
//--# Coding scheme : GBK(If the Chinese comment of the file is garbled, please do not save it and check whether the file is opened in GBK encoding mode)
//--#
//--###############################################################################################
`timescale 1 ns/1 ns
module tb_phy_module();localparam CYCLE = 10 ;//系统时钟周期,单位ns,默认10ns;localparam RST_TIME = 10 ;//系统复位持续时间,默认10个系统时钟周期;localparam TX_KEEP = 4'b1111 ;//发送最后一个数据的有效位数,大端对齐;localparam SOF_LOCAL = 0 ;//起始位位于第几字节。reg clk ;//系统时钟,默认100MHz;reg rst_n ;//系统复位,默认低电平有效;reg [7 : 0] send_value ;reg [31 : 0] i_axi_s_data ;reg [3 : 0] i_axi_s_keep ;reg i_axi_s_last ;reg i_axi_s_valid ;wire o_axi_s_ready ;wire [31 : 0] gt_tx_data ;wire [3 : 0] gt_tx_char ;reg [3 : 0] gt_tx_char_r;reg [31 : 0] gt_tx_data_r;//wire [31: 0] gt_tx_data_o ;wire [3 : 0] gt_tx_char_o ;phy_module u_phy_module(.i_tx_clk ( clk ),//发送部分时钟信号;.i_tx_rst_n ( rst_n ),//发送部分复位信号,低电平有效;.i_rx_clk ( clk ),//接收部分时钟信号;.i_rx_rst_n ( rst_n ),//发送模块复位信号,低电平有效;.i_axi_s_data ( i_axi_s_data ),//AXI的数据输入信号;.i_axi_s_keep ( i_axi_s_keep ),//AXI的数据掩码信号,低电平有效;.i_axi_s_last ( i_axi_s_last ),//AXI输入最后一个数据指示信号;.i_axi_s_valid ( i_axi_s_valid ),//AXI输入数据有效指示信号;.o_axi_s_ready ( o_axi_s_ready ),//AXI输入数据应答信号;.o_axi_s_data ( ),//输出接收的数据,先输出高字节数据;.o_axi_s_keep ( ),//输出接收数据有效指示信号;.o_axi_s_last ( ),//输出一帧数据的最后一个数据指示信号;.o_axi_s_valid ( ),//输出数据有效指示信号;.gt_tx_done ( 1'b1 ),//GTX发送部分初始化成功,高电平有效;.gt_tx_data ( gt_tx_data ),//GTX发送数据;.gt_tx_char ( gt_tx_char ),//GTX发送数据K码指示信号,高电平有效;.gt_rx_bytealign( 1'b1 ),//输入数据字节对齐指示信号;.gt_rx_data ( gt_tx_data_o ),//输入待解析的数据;.gt_rx_char ( gt_tx_char_o ) //输入数据K码指示信号,高电平有效;);always@(posedge clk)begingt_tx_data_r <= gt_tx_data;gt_tx_char_r <= gt_tx_char;end//根据起始位的位置插入不同的数据。generateif(SOF_LOCAL == 0)beginassign gt_tx_data_o = gt_tx_data_r;assign gt_tx_char_o = gt_tx_char_r;endelse if(SOF_LOCAL == 1)beginassign gt_tx_data_o = {gt_tx_data[23:0],gt_tx_data_r[31:24]};assign gt_tx_char_o = {gt_tx_char[2:0],gt_tx_char_r[3]};endelse if(SOF_LOCAL == 2)beginassign gt_tx_data_o = {gt_tx_data[15:0],gt_tx_data_r[31:16]};assign gt_tx_char_o = {gt_tx_char[1:0],gt_tx_char_r[3:2]};endelse if(SOF_LOCAL == 3)beginassign gt_tx_data_o = {gt_tx_data[7:0],gt_tx_data_r[31:8]};assign gt_tx_char_o = {gt_tx_char[0],gt_tx_char_r[3:1]};endendgenerate//生成周期为CYCLE数值的系统时钟;initial beginclk = 0;forever #(CYCLE/2) clk = ~clk;end//生成复位信号;initial beginrst_n = 1;#2;rst_n = 0;//开始时复位10个时钟;#(RST_TIME*CYCLE);rst_n = 1;end//生成输入信号din;initial begini_axi_s_data = 32'd0;i_axi_s_keep = 4'd0;i_axi_s_last = 1'd0;i_axi_s_valid = 1'd0;wait(rst_n);//等待复位完成;repeat(10) @(posedge clk);forever beginphy_tx_task(21);endend//发送数据的任务;task phy_tx_task(input [7 : 0] len);begin : phy_tx_task_0integer i;i_axi_s_data <= 32'd0;i_axi_s_keep <= 4'd0;i_axi_s_last <= 1'd0;i_axi_s_valid <= 1'd0;send_value <= 8'd1;@(posedge clk);wait(o_axi_s_ready);for(i=0 ; i<len ; i=i+1)beginsend_value <= send_value + 1;i_axi_s_data <= {send_value,send_value,send_value,send_value};if(i == len - 1)begini_axi_s_last <= 1'b1;i_axi_s_keep <= TX_KEEP;endelse begini_axi_s_last <= 1'b0;i_axi_s_keep <= 4'hf;endi_axi_s_valid <= 1'b1;@(posedge clk);endi_axi_s_data <= 32'd0;i_axi_s_keep <= 4'd0;i_axi_s_last <= 1'd0;i_axi_s_valid <= 1'd0;@(posedge clk);endendtaskendmodule
如下图示,起始位和停止位均处于最低字节,输出给用户的最后一个数据里高三字节数据均有效。
如下图示,起始位位于最低字节,停止位位于第1字节,那么输出给用户的最后一个数据的所有位均有效,仿真结果正确。
如下图示,起始位位于最低字节,停止位位于第2字节,那么输出给用户的最后一个数据的只有最高字节有效,仿真结果正确。
如下图示,起始位位于最低字节,停止位位于第3字节,那么输出给用户的最后一个数据的高两字节均有效,仿真结果正确。
如下图示,起始位位于第1字节,停止位位于第0字节,那么输出给用户的最后一个数据的高两字节均有效,仿真结果正确。
如下图示,起始位位于第1字节,停止位位于第1字节,那么输出给用户的最后一个数据的高三字节均有效,仿真结果正确。
如下图示,起始位位于第1字节,停止位位于第2字节,那么输出给用户的最后一个数据的所有字节均有效,仿真结果正确。
如下图示,起始位位于第1字节,停止位位于第3字节,那么输出给用户的最后一个数据只有最高字节有效,仿真结果正确。
如下图示,起始位位于第2字节,停止位位于第0字节,那么输出给用户的最后一个数据只有最高字节有效,仿真结果正确。
如下图示,起始位位于第2字节,停止位位于第1字节,那么输出给用户的最后一个数据只有最高两字节有效,仿真结果正确。
如下图示,起始位位于第2字节,停止位位于第2字节,那么输出给用户的最后一个数据只有高三字节均有效,仿真结果正确。
如下图示,起始位位于第2字节,停止位位于第3字节,那么输出给用户的最后一个数据所有字节均有效,仿真结果正确。
如下图示,起始位位于第3字节,停止位位于第0字节,那么输出给用户的最后一个数据所有字节均有效,仿真结果正确。
如下图示,起始位位于第3字节,停止位位于第1字节,那么输出给用户的最后一个数据只有最高字节数据有效,仿真结果正确。
如下图示,起始位位于第3字节,停止位位于第2字节,那么输出给用户的最后一个数据的高两字节数据均有效,仿真结果正确。
如下图示,起始位位于第3字节,停止位位于第3字节,那么输出给用户的最后一个数据的高三字节数据均有效,仿真结果正确。
仿真到此结束,通过修改TestBench中的TX_KEEP和SOF_LOCAL参数,完成了十六种接收格式的仿真,输出给用户的数据均没有问题,仿真结束。
本模块主要完成输出数据的检测、字对齐、掩码信号的生成,支持接收发送端的任意字节数据。
PHY顶层模块对应的RTL视图如下所示,由于高速收发器是全双工通信,因此把发送和接收分为两个模块。
关于自定义PHY接收模块的设计到此结束了,下文将GT收发器模块、自定义PH发送模块、自定义PHY接收模块综合,然后通过光口对协议进行验证。
如果对文章内容理解有疑惑或者对代码不理解,可以在评论区或者后台留言,看到后均会回复!
如果本文对您有帮助,还请多多点赞👍、评论💬和收藏⭐!您的支持是我更新的最大动力!将持续更新工程!