GT收发器PHY层设计(3)PHY层设计

文章目录

前言

上一篇内容当中我们已经将gt_module模块设计完成,本篇开始进行PHY收发模块设计

一、设计框图

二、PHY层基本传输协议

我们在设置IP核的时候设置过时钟纠正序列,同步码正是该序列,用来纠正时钟的累积误差,一般至少发送俩组同步码,即俩次BC50,BC就是K28.5。SOF则表示一帧数据的开始,EOF表示结束,空闲时期发送LFSR伪随机序列。
注:这里稍微区分一下逗号码和同步码,逗号码是用来指示当前串并转换的位置是否正确,同步码是用来纠正时钟误差。

三、PHY_TX模块

向上通过AXIS接口与用户连接,向下与gt_module模块连接。

3.1、模块接口

c 复制代码
module PHY_Tx(
    input               i_clk           ,
    input               i_rst           ,

    input  [31:0]       i_tx_axis_data  ,
    input  [3 :0]       i_tx_axis_keep  ,    
    input               i_tx_axis_valid ,
    input               i_tx_axis_last  ,
    output              o_tx_axis_ready ,

    input               i_gt_tx_done    ,
    output [31:0]       o_gt_tx_data    ,
    output [3 :0]       o_gt_tx_char    
);

3.2、组帧状态机描述

状态机按照数据格式进行跳转,分别进行填充同步码,起始位、数据、结束位,以及在空闲时期插入同步码。

c 复制代码
always @(*)begin
    case (r_cur_state)
        P_ST_INIT   : r_nxt_state = i_gt_tx_done ? P_ST_IDLE : P_ST_INIT;
        P_ST_IDLE   : r_nxt_state = ri_tx_axis_valid_1d ? P_ST_COMMA : 
                                    r_st_cnt == P_INSERT_LEN ? P_ST_INSERT : P_ST_IDLE;
        P_ST_COMMA  : r_nxt_state = P_ST_SOF;
        P_ST_SOF    : r_nxt_state = P_ST_DATA;
        P_ST_DATA   : r_nxt_state = !i_tx_axis_valid && (r_st_cnt == r_tx_data_len - 3) ? P_ST_EOF :P_ST_DATA ;
        P_ST_EOF    : r_nxt_state = ri_tx_axis_keep >= 4'b1110 ? P_ST_EOF2 : P_ST_IDLE;
        P_ST_EOF2   : r_nxt_state = P_ST_IDLE;
        P_ST_INSERT : r_nxt_state = ri_tx_axis_valid_1d ? P_ST_COMMA :
                                    r_st_cnt == 1 ? P_ST_IDLE : P_ST_INSERT;
        default     : r_nxt_state = P_ST_INIT;
    endcase
end

3.3、数据大小端问题

需要格外注意的一点是大小端问题,GT发送和接收是小端模式的,在组帧的时候我们按照习惯的大端模式进行组帧,只需要在输出时记得将数据字节顺序重排一下。

c 复制代码
assign o_gt_tx_data = {ro_gt_tx_data[7:0],ro_gt_tx_data[15:8],ro_gt_tx_data[23:16],ro_gt_tx_data[31:24]};
assign o_gt_tx_char = {ro_gt_tx_char[0],ro_gt_tx_char[1],ro_gt_tx_char[2],ro_gt_tx_char[3]};

3.4、字节对齐

用户输入的数据是4字节对齐的,尾端通过keep信号指示有效字节位,在进行组帧的时候,我们是在SOF位后紧接着填充数据字段,也就是说数据整体向后移了一个byte位,所以需要处理4字节对齐的问题,主要是根据用户数据尾端keep来处理SOF位置。
4字节对齐主要是通过打一拍来解决,SOF位置的处理则需要根据用户数据keep信号的情况进行讨论。

c 复制代码
always @(posedge i_clk or posedge i_rst)begin
    if(i_rst)begin
        ro_gt_tx_data <= 'd0;
        ro_gt_tx_char <= 'd0;
    end 
    else if(r_cur_state == P_ST_COMMA)begin
        // ro_gt_tx_data <= 32'h50BC50BC;
        ro_gt_tx_data <= 32'hbc50bc50;
        ro_gt_tx_char <= 4'b1010;
    end
    else if(r_cur_state == P_ST_SOF)begin
        ro_gt_tx_data <= {8'hFB,w_fifo_dout[31:8]};
        ro_gt_tx_char <= 4'b1000;
    end
    else if(r_cur_state == P_ST_DATA)begin
        ro_gt_tx_data <= {r_fifo_dout[7:0],w_fifo_dout[31:8]};
        ro_gt_tx_char <= 4'b0000;
    end
    else if(r_cur_state == P_ST_EOF && ri_tx_axis_keep == 4'b1000)begin
        ro_gt_tx_data <= {r_fifo_dout[7:0],w_fifo_dout[31:24],8'hfd,w_lfsr_data[31:24]};
        ro_gt_tx_char <= 4'b0010;
    end
    else if(r_cur_state == P_ST_EOF && ri_tx_axis_keep == 4'b1100)begin
        ro_gt_tx_data <= {r_fifo_dout[7:0],w_fifo_dout[31:16],8'hfd};
        ro_gt_tx_char <= 4'b0001;
    end
    else if(r_cur_state == P_ST_EOF && ri_tx_axis_keep == 4'b1110)begin
        ro_gt_tx_data <= {r_fifo_dout[7:0],w_fifo_dout[31:8]};
        ro_gt_tx_char <= 4'b0000;
    end
    else if(r_cur_state == P_ST_EOF && ri_tx_axis_keep == 4'b1111)begin
        ro_gt_tx_data <= {r_fifo_dout[7:0],w_fifo_dout[31:8]};
        ro_gt_tx_char <= 4'b0000;
    end
    else if(r_cur_state == P_ST_EOF2 && ri_tx_axis_keep == 4'b1110)begin
        ro_gt_tx_data <= {8'hfd,w_lfsr_data[31:8]};
        ro_gt_tx_char <= 4'b1000;
    end
    else if(r_cur_state == P_ST_EOF2 && ri_tx_axis_keep == 4'b1111)begin
        ro_gt_tx_data <= {r_fifo_dout[7:0],8'hfd,w_lfsr_data[31:16]};
        ro_gt_tx_char <= 4'b0100;
    end
    else if(r_cur_state == P_ST_INSERT)begin
        ro_gt_tx_data <= {16'hbc50,16'hbc50};
        ro_gt_tx_char <= 4'b0101;
    end
    else begin
        ro_gt_tx_data <= w_lfsr_data;
        ro_gt_tx_char <= 4'b0000;
    end
end

四、PHY_RX模块

4.1、模块接口

c 复制代码
module PHY_Rx(
    input               i_clk           ,
    input               i_rst           ,

    output [31:0]       o_rx_axis_data  ,
    output [3 :0]       o_rx_axis_keep  ,    
    output              o_rx_axis_valid ,
    output              o_rx_axis_last  ,
    input               i_rx_axis_ready ,

    input               i_rx_ByteAlign  ,
    input  [31:0]       i_gt_rx_data    ,
    input  [3 :0]       i_gt_rx_char    
);

4.2、大小端转换

c 复制代码
assign w_gt_rx_data = {i_gt_rx_data[7:0],i_gt_rx_data[15:8],i_gt_rx_data[23:16],i_gt_rx_data[31:24]};
assign w_gt_rx_char = {i_gt_rx_char[0],i_gt_rx_char[1],i_gt_rx_char[2],i_gt_rx_char[3]};

将接收数据先进行大小端转换,然后就可以按照大端习惯进行处理,这个看个人情况吧,我只习惯大端模式

4.3、起始位

重点: 为什么我们需要讨论起始位SOF,这是因为GT接收只进行了字节对齐,也就是转换串行数据为并行数据时,每个8bit数据就是我们发送端发送的8bit数据,但这不意味着恢复出来的32bit数据也是对齐的,SOF可以出现在1拍数据(32bit,4byte)的4个byte当中的任何一个byte位置上。
r_comma_access 表示同步码被识别
r_sof 表示起始码被识别
r_sof_local 用于指示起始码字节位置

c 复制代码
always @(posedge i_clk or posedge i_rst)begin
    if(i_rst)
        r_comma_access <= 'd0;
    else if((ri_gt_rx_data[15:0] == 16'hbc50) && (ri_gt_rx_char[1:0] == 2'b10) && (w_gt_rx_data[31:24] == 8'hfb) && (w_gt_rx_char[3] == 1'b1))
        r_comma_access <= 'd1;
    else if((ri_gt_rx_data[23:0] == 24'hbc50fb) && (ri_gt_rx_char[2:0] == 3'b101))
        r_comma_access <= 'd1;
    else if((ri_gt_rx_data[31:8] == 24'hbc50fb) && (ri_gt_rx_char[3:0] == 4'b1010))
        r_comma_access <= 'd1;
    else if((ri_gt_rx_data_1d[7:0] == 8'hbc) && (ri_gt_rx_char_1d[0] == 1'b1) && (ri_gt_rx_data[31:16] == 16'h50fb) && (ri_gt_rx_char[3:2] == 2'b01))
        r_comma_access <= 'd1;
    else
        r_comma_access <= r_comma_access;
end

always @(posedge i_clk or posedge i_rst)begin
    if(i_rst)
        r_sof <= 'd0;
    else if(r_comma_access && (ri_gt_rx_data_1d[31:24] == 8'hfb) && (ri_gt_rx_char_1d[3] == 1'b1))
        r_sof <= 'd1;
    else if(r_comma_access && (ri_gt_rx_data_1d[23:16] == 8'hfb) && (ri_gt_rx_char_1d[2] == 1'b1))
        r_sof <= 'd1;
    else if(r_comma_access && (ri_gt_rx_data_1d[15:8] == 8'hfb) && (ri_gt_rx_char_1d[1] == 1'b1))
        r_sof <= 'd1;
    else if(r_comma_access && (ri_gt_rx_data_1d[7:0] == 8'hfb) && (ri_gt_rx_char_1d[0] == 1'b1))
        r_sof <= 'd1;
    else
        r_sof <= 'd0;
end

//1表示最高字节位置(从左到右)
always @(posedge i_clk or posedge i_rst)begin
    if(i_rst)
        r_sof_local <= 'd0;
    else if(r_comma_access && (ri_gt_rx_data_1d[31:24] == 8'hfb) && (ri_gt_rx_char_1d[3] == 1'b1))
        r_sof_local <= 'd1;
    else if(r_comma_access && (ri_gt_rx_data_1d[23:16] == 8'hfb) && (ri_gt_rx_char_1d[2] == 1'b1))
        r_sof_local <= 'd2;
    else if(r_comma_access && (ri_gt_rx_data_1d[15:8] == 8'hfb) && (ri_gt_rx_char_1d[1] == 1'b1))
        r_sof_local <= 'd3;
    else if(r_comma_access && (ri_gt_rx_data_1d[7:0] == 8'hfb) && (ri_gt_rx_char_1d[0] == 1'b1))
        r_sof_local <= 'd4;
    else
        r_sof_local <= r_sof_local;
end

4.4、结束位

结束位的字节位置我们也需要进行记录,以便进行keep信号的处理
r_eof 表示结束位被识别
r_eof_1f :r_eof的前一拍数据
r_eof_2f :r_eof的前俩拍数据
r_eof_local :用于指示结束码字节位置

c 复制代码
always @(posedge i_clk or posedge i_rst)begin
    if(i_rst)
        r_eof <= 'd0;
    else if(r_comma_access && (ri_gt_rx_data_1d[31:24] == 8'hfd) && (ri_gt_rx_char_1d[3] == 1'b1))
        r_eof <= 'd1;
    else if(r_comma_access && (ri_gt_rx_data_1d[23:16] == 8'hfd) && (ri_gt_rx_char_1d[2] == 1'b1))
        r_eof <= 'd1;
    else if(r_comma_access && (ri_gt_rx_data_1d[15:8] == 8'hfd) && (ri_gt_rx_char_1d[1] == 1'b1))
        r_eof <= 'd1;
    else if(r_comma_access && (ri_gt_rx_data_1d[7:0] == 8'hfd) && (ri_gt_rx_char_1d[0] == 1'b1))
        r_eof <= 'd1;
    else
        r_eof <= 'd0;
end
always @(posedge i_clk or posedge i_rst)begin
    if(i_rst)
        r_eof_1f <= 'd0;
    else if(r_comma_access && (ri_gt_rx_data[31:24] == 8'hfd) && (ri_gt_rx_char[3] == 1'b1))
        r_eof_1f <= 'd1;
    else if(r_comma_access && (ri_gt_rx_data[23:16] == 8'hfd) && (ri_gt_rx_char[2] == 1'b1))
        r_eof_1f <= 'd1;
    else if(r_comma_access && (ri_gt_rx_data[15:8] == 8'hfd) && (ri_gt_rx_char[1] == 1'b1))
        r_eof_1f <= 'd1;
    else if(r_comma_access && (ri_gt_rx_data[7:0] == 8'hfd) && (ri_gt_rx_char[0] == 1'b1))
        r_eof_1f <= 'd1;
    else
        r_eof_1f <= 'd0;
end
always @(posedge i_clk or posedge i_rst)begin
    if(i_rst)
        r_eof_2f <= 'd0;
    else if(r_comma_access && (w_gt_rx_data[31:24] == 8'hfd) && (w_gt_rx_char[3] == 1'b1))
        r_eof_2f <= 'd1;
    else if(r_comma_access && (w_gt_rx_data[23:16] == 8'hfd) && (w_gt_rx_char[2] == 1'b1))
        r_eof_2f <= 'd1;
    else if(r_comma_access && (w_gt_rx_data[15:8] == 8'hfd) && (w_gt_rx_char[1] == 1'b1))
        r_eof_2f <= 'd1;
    else if(r_comma_access && (w_gt_rx_data[7:0] == 8'hfd) && (w_gt_rx_char[0] == 1'b1))
        r_eof_2f <= 'd1;
    else
        r_eof_2f <= 'd0;
end
//1表示最高字节位置(从左到右)
always @(posedge i_clk or posedge i_rst)begin
    if(i_rst)
        r_eof_local <= 'd0;
    else if(r_comma_access && (w_gt_rx_data[31:24] == 8'hfd) && (w_gt_rx_char[3] == 1'b1))
        r_eof_local <= 'd1;
    else if(r_comma_access && (w_gt_rx_data[23:16] == 8'hfd) && (w_gt_rx_char[2] == 1'b1))
        r_eof_local <= 'd2;
    else if(r_comma_access && (w_gt_rx_data[15:8] == 8'hfd) && (w_gt_rx_char[1] == 1'b1))
        r_eof_local <= 'd3;
    else if(r_comma_access && (w_gt_rx_data[7:0] == 8'hfd) && (w_gt_rx_char[0] == 1'b1))
        r_eof_local <= 'd4;
    else
        r_eof_local <= r_eof_local;
end

4.5、axis数据流恢复

进行数据恢复的时候需要根据起始码位置来处理4字节对齐问题,其中最困难的地方在于keep信号的恢复,它需要同时考虑起始码和结束码的问题 ,这里大家可以根据代码认真思考一下,主要是根据起始码位置和结束码位置,对4'b1111进行移位来恢复最后一帧数据的keep信号。

c 复制代码
always @(posedge i_clk or posedge i_rst)begin
    if(i_rst)
        ro_rx_axis_data <= 'd0;
    else if((r_sof || r_run) && r_sof_local == 'd1)
        ro_rx_axis_data <= {ri_gt_rx_data_2d[23:0],ri_gt_rx_data_1d[31:24]};
    else if((r_sof || r_run) && r_sof_local == 'd2)
        ro_rx_axis_data <= {ri_gt_rx_data_2d[15:0],ri_gt_rx_data_1d[31:16]};
    else if((r_sof || r_run) && r_sof_local == 'd3)
        ro_rx_axis_data <= {ri_gt_rx_data_2d[7:0],ri_gt_rx_data_1d[31:8]};
    else if((r_sof || r_run) && r_sof_local == 'd4)
        ro_rx_axis_data <= ri_gt_rx_data_1d;
    else
        ro_rx_axis_data <= ro_rx_axis_data;
end

always @(posedge i_clk or posedge i_rst)begin
    if(i_rst)
        ro_rx_axis_keep <= 'd0;
    else if(ro_rx_axis_last)
        ro_rx_axis_keep <= 'd0;
    else if(r_eof_1f && (r_sof_local >= (r_eof_local - 1)))
        ro_rx_axis_keep <= (4'hf << (r_sof_local + 1 - r_eof_local));
    else if(r_eof && (r_sof_local < (r_eof_local - 1)))
        ro_rx_axis_keep <= (4'hf << (4-(r_eof_local - 1 - r_sof_local)));
    else if(r_sof || r_run)
        ro_rx_axis_keep <= 4'hf;
    else
        ro_rx_axis_keep <= 'd0;
end
 
always @(posedge i_clk or posedge i_rst)begin
    if(i_rst)
        ro_rx_axis_valid <= 'd0;
    else if(ro_rx_axis_last)
        ro_rx_axis_valid <= 'd0;
    else if(r_sof)
        ro_rx_axis_valid <= 'd1;
    else
        ro_rx_axis_valid <= ro_rx_axis_valid;
end

always @(posedge i_clk or posedge i_rst)begin
    if(i_rst)
        ro_rx_axis_last <= 'd0;
    else if(ro_rx_axis_last)
        ro_rx_axis_last <= 'd0;
    else if(r_eof_2f && (r_sof_local == 4 && r_eof_local == 1))
        ro_rx_axis_last <= 'd1;
    else if(r_eof_1f && (r_sof_local >= (r_eof_local - 1)))
        ro_rx_axis_last <= 'd1;
    else if(r_eof)
        ro_rx_axis_last <= 'd1;
    else
        ro_rx_axis_last <= 'd0;
end

五、LFSR伪随机码

发送端空闲时期需要发送伪随机码来模拟信道的高斯噪声

c 复制代码
module LFSR_gen#(
    parameter       P_LFSR_INIT = 16'hA076
)(
    input           i_clk       ,
    input           i_rst       ,

    output [31:0]   o_lfsr_data 
);
reg  [31:0] r_lfsr_data ;
reg  [15:0] r_lfsr      ;
wire [47:0] w_xor_run   ;

assign w_xor_run[47:32] = r_lfsr;
assign o_lfsr_data = r_lfsr_data;
genvar i;
generate
    for(i = 0 ; i < 32 ; i = i + 1)begin
        assign w_xor_run[31 - i] = w_xor_run[47 - i]^w_xor_run[46 - i]^w_xor_run[45 - i]^w_xor_run[32 - i];
    end
endgenerate

always @(posedge i_clk or posedge i_rst)begin
    if(i_rst)
        r_lfsr <= P_LFSR_INIT;
    else
        r_lfsr <= w_xor_run[15:0];
end

always @(posedge i_clk or posedge i_rst)begin
    if(i_rst)
        r_lfsr_data <= 'd0;
    else
        r_lfsr_data <= w_xor_run[31:0];  
end

endmodule

六、链路空闲时期处理

在链路空闲时期需要发送伪随机码,并且定期插入同步码0xBC60。

在接收端XCLK时钟域需要跨到RXUSERCLK时钟域,虽然俩者是同频的,但细微的差别也会导致FIFO的上溢和下溢问题,都会是的接收数据出错,我们该如何修正读快写满或者是读慢写快的问题呢?

解决办法就是弹性(Elastic)buffer,这和8B10B编码也有关系,因为涉及到了K码,我们在传输数据的时候会先发送同步码,同步码当中存在K码,当FIFO快写满的时候,弹性buffer就将FIFO当中的K码删除掉一些,当快读空的时候,就在FIFO里插入一些K码。

相关推荐
西岸行者4 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
ZPC82104 天前
docker 镜像备份
人工智能·算法·fpga开发·机器人
ZPC82104 天前
docker 使用GUI ROS2
人工智能·算法·fpga开发·机器人
悠哉悠哉愿意4 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码4 天前
嵌入式学习路线
学习
毛小茛4 天前
计算机系统概论——校验码
学习
babe小鑫4 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms4 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下4 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。4 天前
2026.2.25监控学习
学习