本文基于IEEE Std 802.3-2022 PCS 章节,介绍几种66b同步方法,处理位宽包括64bit数据到32bit数据,并给出一些优化方案。

整个简化流程可以表示为:

64bit输入数据->增加同步头->转换成64bit位宽数据-----> PMA tx ----->PMA rx ---->66b同步->去同步头->64bit数据

在上述流程中,没有包括加解扰,也没有包括FEC编解码及帧同步,这些内容后续再补充。PMA属于模拟部分,也不包括。

1.整体框架

假设发送模块输入信号分别为 i_control, i_data, i_frame_first, i_valid。这三个信号分别表示当前数据包含控制符(影响同步头),输入数据,起始帧指示(32拍64bit信号作为一帧),数据有效指示信号。模块输出数据为o_data,o_valid,此时输出数据为纯数据流。

信号名称

方向

位宽

含义说明

i_control

输入

1 bit

控制符指示信号。当为高电平时,表示当前 i_data 中包含控制字符;为低时表示 i_data 为普通数据载荷。

i_data

输入

64 bits

并行输入数据。每拍(clock cycle)传输 64 位数据。该数据可能是有效载荷、控制字符或空闲序列,具体含义由 i_controli_valid 共同决定。

i_frame_first

输入

1 bit

帧起始标志。当为高电平时,表示当前拍(cycle)的 i_data 是一帧的第一个 64-bit 数据块。一帧由连续 32 拍(即 32 × 64 = 2048 bits)组成。用于帧对齐、缓冲管理或协议解析。

i_valid

输入

1 bit

数据有效指示。当为高电平时,表示当前 i_data 及其伴随信号(i_control, i_frame_first)有效;为低时,i_data 应被忽略(视为无效或空闲周期)。

o_valid

输出

1 bit

输出有效指示:高电平表示 o_data 当前包含有效编码后的数据块;通常与输入 i_valid 对齐或经内部流水后延迟若干周期。

o_data

输出

64 bit

纯数据流输出:已将控制符按协议要求转换为对应的64B/66B 编码块,供后续扰码或串行化使用。

解释一下为什么需要i_frame_first 这个信号,由于32拍数据会插入64bit同步头,说明下一拍输入数据valid数据要保持低电平并且输出缓存下来的第33个64bit数据,所以是33拍数据一个循环。如果没有这个i_frame_first 指示信号根据33拍的周期重复校准,一旦内部计数器有扰动就再也无法和输入数据对齐了。这个问题我们可以在后续代码中查看。

首先来构造一下激励:

module top();
reg clk = 0;
reg rst_n = 0;
always  #5 clk = ~clk;

reg [6:0] cnt33 = 'd0;
reg data_vld;
reg first_dat;

always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'd0)begin
        cnt33 <= 'd0;
        data_vld <= 'd0;
        first_dat <= 'd1;
    end
    else if(cnt33 == 'd32)begin
        cnt33 <= 'd0;
        data_vld <= 'd0;
        first_dat <= 'd0;
    end
    else begin
        data_vld <= 'd1;
        cnt33 <= cnt33 + 'd1;
        first_dat <= cnt33 == 'd0;
    end    
end

initial begin
    #50;
    rst_n = 1; 
    #5000;
    $finish;
end

initial begin 
    $fsdbDumpfile("top.fsdb");
    $fsdbDumpvars(0,top);
    $fsdbDumpMDA();
end

endmodule

/*
# ------------------------------------
# File: /home/ssy/Code/hdl/MAC/pcs_66b_enc.v
# Project: /home/ssy/Code/hdl/MAC
# Created Date: Wednesday, December 17th 2025, 11:33:00 pm
# Author: Shirainbown
# -----
# Module: pcs_66b_enc.v
# -----
# Description:
# -----
# Copyright (c) 2025 Non Inc.
# ------------------------------------
*/

module pcs_66b_enc (
    input        clk,
    input        rst_n,

    // Input interface (frame-based, 64-bit)
    input  [63:0] i_data,
    input         i_control,
    input         i_frame_first,
    input         i_valid,

    // Output interface (pure 64-bit data stream)
    output reg [63:0] o_data,
    output reg        o_valid
);

// ==============================
// 1. 64B/66B Encoder
// ==============================
localparam [1:0] SYNC_DATA    = 2'b01;
localparam [1:0] SYNC_CONTROL = 2'b10;

wire [65:0] encoded_66b;     // [65:64] = sync header
reg        enc_valid;
assign  encoded_66b = i_control ? {  i_data, SYNC_CONTROL}: {  i_data, SYNC_DATA};
reg [6:0] frame_cnt;        // 0 to 31
reg [63:0] data_dly;


always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        frame_cnt   <= 7'd0;
        data_dly    <= 64'd0;
    end 
    else begin
        if (i_valid) begin
            frame_cnt <= i_frame_first == 1'd1 ? 7'd1 : frame_cnt + 1;
            data_dly <= i_data;
        end else begin
            frame_cnt <= frame_cnt>= 7'd32 ? 7'd0 :  frame_cnt;
            data_dly <= data_dly;
        end
    end
end

wire [129:0] tmp_data;
assign tmp_data = {encoded_66b, data_dly } >> (7'd64 - (frame_cnt * 2) );

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        o_data    <= 64'd0;
        o_valid   <= 1'b0;
    end else begin
        if (i_valid == 1'd1  ) begin
            o_data    <= tmp_data[0+:64]  ; 
            o_valid   <= 1'b1;
        end else begin
            o_valid <= frame_cnt == 7'd32 ? 1'd1:1'b0;
            o_data    <= frame_cnt == 7'd32 ? data_dly :o_data;
        end
    end
end

endmodule

module pcs_66b_dec (
    input        clk,
    input        rst_n,

    // Input: pure 64-bit data stream (from descrambler)
    input  [63:0] i_data,
    input         i_valid,     // usually always 1 in continuous mode

    // Output: reconstructed frame interface
    output reg [6:0]  slip_cnt,
    output reg [63:0] o_data,
    output reg        o_control,
    output reg        o_valid
);

localparam [1:0] SYNC_DATA    = 2'b01;
localparam [1:0] SYNC_CONTROL = 2'b10;

localparam LOCK_INIT = 3'd0;
localparam RESET_CNT = 3'd1;
localparam TEST_SH   = 3'd2;
localparam GOOD      = 3'd3;
localparam SLIP      = 3'd4;


wire [129:0] bit_buffer; //64+66
reg [63:0]   i_data_1d;
reg [63:0]   i_data_2d;
reg [2:0]   cur_state;
reg [2:0]   next_state;

assign bit_buffer = {i_data[1:0],i_data_1d,i_data_2d};

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        i_data_1d <= 64'd0;
        i_data_2d <= 64'd0;
    end 
    else begin
        i_data_1d <= i_valid == 1'd1 ? i_data:  i_data_1d;
        i_data_2d <= i_valid == 1'd1 ? i_data_1d:  i_data_2d;
    end
end

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        cur_state <= LOCK_INIT;
    end 
    else begin
        cur_state <= next_state;
    end
end

reg        locked  ;
reg [6:0]  hd_cnt  ;

reg        valid_1d;

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        valid_1d <= 1'b0;
    end
    else begin
        valid_1d <= i_valid;
    end
end

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        hd_cnt <= 7'b0;
    end
    else if(i_valid )begin
        hd_cnt <= (hd_cnt + 7'd2 - (cur_state == SLIP ) ) >= 7'd66 ? hd_cnt - 7'd64 : hd_cnt + 7'd2 - (cur_state == SLIP);
    end
end
// Sync header extraction
wire [1:0] sync_hdr = bit_buffer[hd_cnt+:2];

wire       is_valid_sh_1d      = ((sync_hdr == 2'b01) || (sync_hdr == 2'b10)) &&(valid_1d == 1) ;
wire       is_invalid_sh_1d    = (sync_hdr[0] == sync_hdr[1]) &&(valid_1d == 1) ;
wire       is_control_block_1d = (sync_hdr == SYNC_CONTROL) &&(valid_1d == 1) ;


reg [5:0]  valid_sh_cnt;
reg [4:0]  invalid_sh_cnt;


// State transition logic
always @(*) begin
    case (cur_state)
        LOCK_INIT: begin
            next_state = RESET_CNT;
        end

        RESET_CNT: begin
            if (valid_1d) begin
                next_state = TEST_SH;
            end
        end

        TEST_SH: begin
            if(is_valid_sh_1d )begin
                if(valid_sh_cnt >= 6'd63 )begin
                    next_state =  invalid_sh_cnt == 'd0   ? GOOD :  (locked == 1'd1) ? RESET_CNT : TEST_SH;
                end else begin
                    next_state = TEST_SH;
                end
            end
            else if (is_invalid_sh_1d) begin
                if(invalid_sh_cnt >= 5'd15)begin
                    next_state = SLIP;
                end
                else  begin
                    next_state =  (locked == 1'd1) ? TEST_SH : SLIP;
                end
            end
        end

        GOOD: begin
            next_state = RESET_CNT;
        end

        SLIP: begin
            next_state = RESET_CNT;
        end

        default: next_state = LOCK_INIT;
    endcase
end


// Counters and lock flag
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin    
        valid_sh_cnt       <= 6'd0;
        invalid_sh_cnt     <= 5'd0;
        locked <= 1'b0;
        slip_cnt <= 'd0;
    end else begin
        case (cur_state)
            LOCK_INIT: begin
                valid_sh_cnt    <= 6'd0;
                invalid_sh_cnt  <= 5'd0;
                locked <= 1'b0;
                slip_cnt <= 'd0;
            end

            RESET_CNT: begin
                valid_sh_cnt    <= 6'd0;
                invalid_sh_cnt  <= 5'd0;
            end

            TEST_SH: begin
                if (is_valid_sh_1d) begin
                    valid_sh_cnt <= valid_sh_cnt + 1;
                end else if(is_invalid_sh_1d) begin
                    invalid_sh_cnt  <= invalid_sh_cnt + 1;
                end
            end
            
            GOOD: begin
               locked <= 1'b1;
            end

            SLIP: begin
                slip_cnt <= slip_cnt +1;
                locked <= 1'd0;
            end
        endcase
    end
end

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        o_data         <= 64'd0;
        o_control      <= 1'b0;
        o_valid        <= 1'b0;
    end else begin
        if (locked == 1'd1 &&  valid_1d  == 1'd1 && hd_cnt <= 7'd62 ) begin
            o_data        <= bit_buffer[hd_cnt+2+:64];
            o_control     <= is_control_block_1d;
            o_valid       <= 1'b1;
        end else begin
            o_valid <= 1'b0;
            o_control <= 1'd0;
            // Keep other outputs stable or zero
        end
    end
end

endmodule
// testbench
module top();
reg clk = 0;
reg rst_n = 0;
always  #5 clk = ~clk;

reg [6:0] cnt33 = 'd0;
reg data_vld;
reg first_dat;

always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'd0)begin
        cnt33 <= 'd0;
    end
    else if(cnt33 <= 34)begin
      cnt33 <=cnt33 +1;
    end   
    else begin
      cnt33 <=1;
    end 
end

initial begin
    rst_n = 0;
    #50;
    rst_n = 1; 
    #5000;
    $finish;
end

wire [63:0] data;
wire vld;
pcs_66b_enc u_pcs_66b_enc(
    .clk           (clk           ),
    .rst_n         (rst_n         ),
    .i_data        (cnt33         ),
    .i_control     (cnt33%4==0           ),
    .i_frame_first (cnt33 == 1 ),
    .i_valid       (cnt33<=32       ),
    .o_data        ( data        ),
    .o_valid       (vld        )
);

pcs_66b_dec u_pcs_66b_dec(
    .clk           (clk           ),
    .rst_n         (rst_n         ),
    .i_data        (data         ),
    .i_valid       (vld       ),   
    .o_control     (           ),
    .o_data        (         ),
    .o_valid       (        )
);


initial begin 
    $fsdbDumpfile("tb_top.fsdb");
    $fsdbDumpvars(0,top);
    $fsdbDumpMDA();
end
endmodule