3. JESD204C 8B/10B 编码器 FPGA 实现
任务说明
在 FPGA 中实现 JESD204C 链路层使用的 8B/10B 编码器。核心是:把 8 位数据字节按标准拆成 5B/6B 与 3B/4B 两段,结合当前运行极性(running disparity, RD)查表得到 10 位码字,并更新 RD。验证方式是把编码器输出与标准 8B/10B 查找表逐项比对。
编码流程
实现要点
- 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可级联到下一级编码器保持极性连续。