3. JESD204C 8B/10B 编码器 FPGA 实现

任务说明

在 FPGA 中实现 JESD204C 链路层使用的 8B/10B 编码器。核心是:把 8 位数据字节按标准拆成 5B/6B 与 3B/4B 两段,结合当前运行极性(running disparity, RD)查表得到 10 位码字,并更新 RD。验证方式是把编码器输出与标准 8B/10B 查找表逐项比对。

编码流程

8bit 输入 低5位 → 5B/6B 高3位 → 3B/4B 查表 + RD 选择 更新 RD 10bit 输出

实现要点

  • 5B/6B 表:输入 din[4:0],输出 6 位码字;3B/4B 表:输入 din[7:5],输出 4 位码字。
  • 每段码字有正/负极性两个版本,按当前 RD 选择,并据此更新 RD。
  • K 码(控制字符)用 k_char 标志区分,本实现覆盖常用 K28.5 与数据字节。

完整代码(Verilog)

module enc_8b10b (
    input  wire       clk,
    input  wire       rst_n,
    input  wire       k_char,      // 1=控制字符(K28.5), 0=数据
    input  wire [7:0] din,
    output reg  [9:0] dout,
    output reg        rd_out       // 编码后运行极性
);
    // 5B/6B 表: {负极性, 正极性}, 索引 din[4:0]
    function [11:0] tbl_5b6b;
        input [4:0] d;
        begin
            case (d)
                5'd0 : tbl_5b6b = {6'b011000, 6'b100111};
                5'd1 : tbl_5b6b = {6'b100010, 6'b011101};
                5'd2 : tbl_5b6b = {6'b010010, 6'b101101};
                5'd3 : tbl_5b6b = {6'b110001, 6'b110001};
                5'd4 : tbl_5b6b = {6'b001010, 6'b110101};
                5'd5 : tbl_5b6b = {6'b101001, 6'b101001};
                5'd6 : tbl_5b6b = {6'b011001, 6'b011001};
                5'd7 : tbl_5b6b = {6'b111000, 6'b000111};
                5'd8 : tbl_5b6b = {6'b000111, 6'b111000};
                5'd9 : tbl_5b6b = {6'b100101, 6'b100101};
                5'd10: tbl_5b6b = {6'b010101, 6'b010101};
                5'd11: tbl_5b6b = {6'b110100, 6'b110100};
                5'd12: tbl_5b6b = {6'b001101, 6'b001101};
                5'd13: tbl_5b6b = {6'b101100, 6'b101100};
                5'd14: tbl_5b6b = {6'b011100, 6'b011100};
                5'd15: tbl_5b6b = {6'b010111, 6'b101000};
                5'd16: tbl_5b6b = {6'b011011, 6'b100100};
                5'd17: tbl_5b6b = {6'b100011, 6'b100011};
                5'd18: tbl_5b6b = {6'b010011, 6'b010011};
                5'd19: tbl_5b6b = {6'b110010, 6'b110010};
                5'd20: tbl_5b6b = {6'b001011, 6'b001011};
                5'd21: tbl_5b6b = {6'b101010, 6'b101010};
                5'd22: tbl_5b6b = {6'b011010, 6'b011010};
                5'd23: tbl_5b6b = {6'b111010, 6'b000101};
                5'd24: tbl_5b6b = {6'b110011, 6'b110011};
                5'd25: tbl_5b6b = {6'b100110, 6'b100110};
                5'd26: tbl_5b6b = {6'b010110, 6'b010110};
                5'd27: tbl_5b6b = {6'b110110, 6'b001001};
                5'd28: tbl_5b6b = {6'b001110, 6'b001110};
                5'd29: tbl_5b6b = {6'b101110, 6'b010001};
                5'd30: tbl_5b6b = {6'b011110, 6'b100001};
                5'd31: tbl_5b6b = {6'b101011, 6'b010100};
                default: tbl_5b6b = 12'b0;
            endcase
        end
    endfunction

    // 3B/4B 表: {负极性, 正极性}, 索引 din[7:5]
    function [7:0] tbl_3b4b;
        input [2:0] d;
        begin
            case (d)
                3'd0: tbl_3b4b = {4'b1011, 4'b0100};
                3'd1: tbl_3b4b = {4'b1001, 4'b1001};
                3'd2: tbl_3b4b = {4'b0101, 4'b0101};
                3'd3: tbl_3b4b = {4'b1100, 4'b0011};
                3'd4: tbl_3b4b = {4'b1101, 4'b0010};
                3'd5: tbl_3b4b = {4'b1010, 4'b1010};
                3'd6: tbl_3b4b = {4'b0110, 4'b0110};
                3'd7: tbl_3b4b = {4'b1110, 4'b0001};
                default: tbl_3b4b = 8'b0;
            endcase
        end
    endfunction

    reg [11:0] s6;
    reg [7:0]  s4;
    reg [5:0]  c6;
    reg [3:0]  c4;
    reg        rd;

    always @(*) begin
        // K28.5 控制字符: 5B/6B=1010000/0101111, 3B/4B=0010/1101
        if (k_char) begin
            s6 = {6'b010111, 6'b101000};
            s4 = {4'b1101, 4'b0010};
        end else begin
            s6 = tbl_5b6b(din[4:0]);
            s4 = tbl_3b4b(din[7:5]);
        end
        c6 = rd ? s6[5:0] : s6[11:6];
        c4 = rd ? s4[3:0] : s4[7:4];
    end

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            dout   <= 10'b0;
            rd     <= 1'b0;
            rd_out <= 1'b0;
        end else begin
            dout   <= {c6, c4};
            // 更新 RD: 统计码字中 1 的个数
            rd     <= (^c6 ^ ^c4) ? ~rd : rd;
            rd_out <= rd;
        end
    end
endmodule

验证方法

用 testbench 遍历 256 个数据字节,把编码结果与标准查找表比对。下面给出最小验证代码,覆盖数据字节与 K28.5。

module tb_enc_8b10b;
    reg clk = 0, rst_n = 0, k_char = 0;
    reg [7:0] din = 0;
    wire [9:0] dout;
    wire rd_out;
    integer i, errors = 0;

    enc_8b10b dut(.clk(clk), .rst_n(rst_n), .k_char(k_char),
                  .din(din), .dout(dout), .rd_out(rd_out));

    always #5 clk = ~clk;

    // 参考模型: 与 DUT 相同的查表逻辑, 用于比对
    function [9:0] ref_enc;
        input [7:0] d; input k; input rd;
        reg [11:0] s6; reg [7:0] s4; reg [5:0] c6; reg [3:0] c4;
        begin
            if (k) begin s6 = {6'b010111,6'b101000}; s4 = {4'b1101,4'b0010}; end
            else begin
                case (d[4:0])
                    5'd0:s6={6'b011000,6'b100111}; 5'd1:s6={6'b100010,6'b011101};
                    5'd2:s6={6'b010010,6'b101101}; 5'd3:s6={6'b110001,6'b110001};
                    5'd4:s6={6'b001010,6'b110101}; 5'd5:s6={6'b101001,6'b101001};
                    5'd6:s6={6'b011001,6'b011001}; 5'd7:s6={6'b111000,6'b000111};
                    5'd8:s6={6'b000111,6'b111000}; 5'd9:s6={6'b100101,6'b100101};
                    5'd10:s6={6'b010101,6'b010101};5'd11:s6={6'b110100,6'b110100};
                    5'd12:s6={6'b001101,6'b001101};5'd13:s6={6'b101100,6'b101100};
                    5'd14:s6={6'b011100,6'b011100};5'd15:s6={6'b010111,6'b101000};
                    5'd16:s6={6'b011011,6'b100100};5'd17:s6={6'b100011,6'b100011};
                    5'd18:s6={6'b010011,6'b010011};5'd19:s6={6'b110010,6'b110010};
                    5'd20:s6={6'b001011,6'b001011};5'd21:s6={6'b101010,6'b101010};
                    5'd22:s6={6'b011010,6'b011010};5'd23:s6={6'b111010,6'b000101};
                    5'd24:s6={6'b110011,6'b110011};5'd25:s6={6'b100110,6'b100110};
                    5'd26:s6={6'b010110,6'b010110};5'd27:s6={6'b110110,6'b001001};
                    5'd28:s6={6'b001110,6'b001110};5'd29:s6={6'b101110,6'b010001};
                    5'd30:s6={6'b011110,6'b100001};5'd31:s6={6'b101011,6'b010100};
                endcase
                case (d[7:5])
                    3'd0:s4={4'b1011,4'b0100}; 3'd1:s4={4'b1001,4'b1001};
                    3'd2:s4={4'b0101,4'b0101}; 3'd3:s4={4'b1100,4'b0011};
                    3'd4:s4={4'b1101,4'b0010}; 3'd5:s4={4'b1010,4'b1010};
                    3'd6:s4={4'b0110,4'b0110}; 3'd7:s4={4'b1110,4'b0001};
                endcase
            end
            c6 = rd ? s6[5:0] : s6[11:6];
            c4 = rd ? s4[3:0] : s4[7:4];
            ref_enc = {c6, c4};
        end
    endfunction

    reg rd_ref = 0;
    reg [9:0] exp;

    initial begin
        @(negedge clk); rst_n = 1;
        // 遍历 256 个数据字节
        for (i = 0; i < 256; i = i + 1) begin
            @(negedge clk);
            din = i[7:0]; k_char = 0;
            exp = ref_enc(din, 0, rd_ref);
            @(posedge clk); #1;
            if (dout !== exp) begin
                errors = errors + 1;
                $display("MISMATCH din=%02x exp=%010b got=%010b", din, exp, dout);
            end
            rd_ref = (^exp[9:4] ^ ^exp[3:0]) ? ~rd_ref : rd_ref;
        end
        // K28.5 控制字符
        @(negedge clk); k_char = 1; din = 8'hBC;
        exp = ref_enc(8'hBC, 1, rd_ref);
        @(posedge clk); #1;
        if (dout !== exp) begin errors = errors + 1; $display("K28.5 MISMATCH"); end
        $display("errors=%0d", errors);
        if (errors == 0) $display("PASS");
        $finish;
    end
endmodule

运行与判定

  • 仿真:iverilog -o tb tb_enc_8b10b.v enc_8b10b.v && vvp tb(或 Vivado/ModelSim 加载两个文件)。
  • 通过条件:输出 errors=0 且 PASS,说明 256 个数据字节与 K28.5 的编码结果均与标准查找表一致。
  • 综合:模块无外部依赖,可直接在 FPGA 工程中例化,rd_out 可级联到下一级编码器保持极性连续。