3、初始化配置:通过I2C读取SFP的EEPROM信息、解析DDM数据、配置速率与协议

好,咱们接着往下聊。上一章我们把硬件连接搞定了,电平也匹配好了。但SFP模块插上去,它到底是个什么玩意儿?是千兆的还是万兆的?单模还是多模?工作温度多少?这些信息,模块自己知道,但它不会主动告诉你。

你得去问它。怎么问?通过I2C总线,去读它内部的EEPROM。这就像你去酒店前台查房客信息一样,模块的EEPROM就是它的“身份证”。

3.1 为什么要读EEPROM?

我个人习惯,上电后第一件事,不是急着发数据,而是先读EEPROM。为什么?

  • 确认模块身份:看看是不是你想要的型号。我曾经在项目中遇到过,采购拿错了模块,插上去死活不通,折腾了半天才发现是单模模块配了多模光纤。
  • 获取能力信息:模块支持哪些速率?支持哪些协议?这些都在EEPROM里写着。
  • 获取DDM数据:Digital Diagnostic Monitoring,数字诊断监控。说白了就是模块的“体检报告”——温度、电压、发射功率、接收功率、偏置电流。

你想想看,如果连模块的基本信息都不知道,后面的调试就是盲人摸象。

3.2 I2C读取流程

SFP模块的I2C地址通常是0xA0(写地址)或0xA1(读地址)。注意,这是7位地址左移一位后的结果。如果你用7位地址,那就是0x50

读取流程其实很简单:

  1. 发送起始信号
  2. 发送写地址(0xA0)
  3. 发送要读取的EEPROM字节地址(比如0x00)
  4. 发送重启信号
  5. 发送读地址(0xA1)
  6. 读取数据,每读一个字节后发送ACK
  7. 读取最后一个字节后发送NACK,然后发送停止信号

嗯,这里要注意:EEPROM的地址空间是0x00到0xFF,共256个字节。其中0x00到0x7F是强制性的基础信息,0x80到0xFF是厂商自定义区域。

核心代码片段(Verilog状态机):

// I2C主控状态机 - 读取SFP EEPROM
localparam IDLE      = 4'd0;
localparam START     = 4'd1;
localparam SEND_ADDR = 4'd2;
localparam SEND_REG  = 4'd3;
localparam RESTART   = 4'd4;
localparam READ_DATA = 4'd5;
localparam STOP      = 4'd6;

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        state <= IDLE;
        // ... 其他寄存器清零
    end else begin
        case (state)
            IDLE: begin
                if (start_read) begin
                    state <= START;
                    scl_out <= 1'b1;
                    sda_out <= 1'b1;
                end
            end
            START: begin
                // 拉低SDA,产生起始条件
                sda_out <= 1'b0;
                state <= SEND_ADDR;
            end
            SEND_ADDR: begin
                // 发送0xA0,等待ACK
                // ... 移位发送逻辑
                if (ack_received) state <= SEND_REG;
            end
            SEND_REG: begin
                // 发送寄存器地址,比如0x00
                // ... 移位发送逻辑
                if (ack_received) state <= RESTART;
            end
            RESTART: begin
                // 产生重启条件
                sda_out <= 1'b1;
                scl_out <= 1'b1;
                state <= READ_DATA;
            end
            READ_DATA: begin
                // 读取8位数据,每读一位拉低SCL
                // ... 移位接收逻辑
                if (byte_done) begin
                    data_reg <= {data_reg[6:0], sda_in};
                    if (last_byte) begin
                        sda_out <= 1'b1; // NACK
                        state <= STOP;
                    end else begin
                        sda_out <= 1'b0; // ACK
                    end
                end
            end
            STOP: begin
                sda_out <= 1'b0;
                scl_out <= 1'b1;
                sda_out <= 1'b1; // 停止条件
                state <= IDLE;
                read_done <= 1'b1;
            end
        endcase
    end
end

小技巧:实际项目中,我建议用状态机+计数器的方式实现I2C时序。别用那种“延时等待”的写法,那玩意儿在FPGA里不靠谱,时序容易跑飞。

3.3 解析EEPROM关键字段

读回来的数据是原始字节,你得知道每个字节代表什么意思。SFF-8472标准里定义得很清楚。我挑几个最常用的字段说说:

地址 字节数 含义 说明
0x00 1 标识符 0x03表示SFP,0x09表示SFP+
0x01 1 扩展标识符 bit6=1表示支持DDM
0x02 1 连接器类型 0x07表示LC连接器
0x03-0x0A 8 收发器编码 bit0=1表示支持千兆以太网
0x0B-0x0D 3 编码方式 0x01表示8B/10B编码
0x0E 1 标称比特率 单位:100Mbps,0x0C=1.2Gbps
0x10-0x13 4 厂商名称 ASCII码,比如"FINISAR"
0x14-0x17 4 厂商OUI IEEE公司标识
0x18-0x27 16 厂商PN号 ASCII码,比如"FTLF8519P3BNL"
0x28-0x2F 8 厂商序列号 ASCII码
0x60-0x61 2 波长 单位:0.05nm,0x2328=850nm

注意:厂商名称和PN号是ASCII码,但有些厂商会在末尾补空格(0x20)。解析的时候记得去掉尾部空格。我曾经被这个坑过,字符串比较死活对不上。

3.4 DDM数据解析

DDM数据在地址0xA0到0xBF(注意,这是A2h地址空间,不是A0h)。读取方式和前面一样,只是把I2C地址换成0xA2(写地址)或0xA3(读地址)。

DDM数据格式如下:

地址 字节数 参数 计算公式
0xA0-0xA1 2 温度 T = (MSB << 8 | LSB) * 0.0625 - 273.15 (°C)
0xA2-0xA3 2 VCC电压 V = (MSB << 8 | LSB) * 0.0001 (V)
0xA4-0xA5 2 偏置电流 I = (MSB << 8 | LSB) * 0.002 (mA)
0xA6-0xA7 2 发射功率 P_tx = (MSB << 8 | LSB) * 0.0001 (mW)
0xA8-0xA9 2 接收功率 P_rx = (MSB << 8 | LSB) * 0.0001 (mW)

举个例子:如果读到温度寄存器是0x0C 0x80,那温度值就是(0x0C << 8 | 0x80) = 3200,乘以0.0625得200,减去273.15得-73.15°C。嗯?这个温度明显不对,说明模块可能没正常工作,或者I2C读错了。

实际项目经验:我遇到过一种情况,读回来的DDM数据全是0xFF。一开始以为是I2C时序问题,折腾了半天。后来发现是模块的电源没供上。所以,读DDM之前,先确认模块的电源和使能信号都正常。

3.5 配置速率与协议

读完了信息,接下来就是配置。SFP模块本身没有“配置寄存器”让你写速率。速率是由FPGA的收发器(比如GTP、GTX、GTH)决定的。

说白了,你FPGA发什么速率,模块就工作在什么速率。但前提是:模块必须支持这个速率。你拿一个千兆模块,非要让它跑10G,那肯定不行。

配置流程大致如下:

  1. 根据EEPROM信息确定速率:从0x0E字节读取标称比特率,确认模块支持的能力。
  2. 配置FPGA收发器:设置PLL分频比、参考时钟频率、数据位宽等。
  3. 配置协议层:比如以太网协议,需要配置MAC层、PCS层、PMA层。
  4. 设置TX_DISABLE信号:有些模块需要拉低这个引脚才能正常发送。
  5. 等待模块锁定:模块内部有CDR(时钟数据恢复)电路,需要一段时间锁定。

个人建议:配置速率时,先配一个低速的,比如1G,确认链路能通。然后再切换到目标速率。这样如果出了问题,容易定位是速率问题还是其他问题。

3.6 避坑指南

做SFP初始化,我踩过的坑不少。挑几个典型的说说:

  • I2C时序不满足:SFP模块的I2C速率通常支持100kHz和400kHz。别跑太快,有些模块扛不住。我习惯用100kHz,稳。
  • 地址搞错:EEPROM是0xA0,DDM是0xA2。别搞混了。我刚开始做的时候,读DDM一直用0xA0,读回来的全是乱码。
  • 模块热插拔:SFP支持热插拔,但FPGA的收发器不一定支持。插拔后需要重新初始化。我一般用Mod_ABS信号检测模块是否在位。
  • 速率协商:千兆以太网支持自动协商,但万兆一般不支持。如果两端速率不匹配,链路就是不通的。

重要提醒:有些廉价模块的EEPROM数据可能不标准。比如厂商名称字段填的是乱码,或者DDM数据精度不够。遇到这种情况,别慌,先确认模块本身是不是好的。换一个正规厂商的模块试试。

好了,这一章的内容就这些。总结一下:先读EEPROM确认模块身份,再读DDM看模块健康状况,最后根据模块能力配置FPGA的速率和协议。每一步都有坑,但只要你按流程来,问题不大。

下一章,我们聊聊实际调试中遇到的一些诡异问题,以及怎么用示波器和逻辑分析仪抓波形。