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。
读取流程其实很简单:
- 发送起始信号
- 发送写地址(0xA0)
- 发送要读取的EEPROM字节地址(比如0x00)
- 发送重启信号
- 发送读地址(0xA1)
- 读取数据,每读一个字节后发送ACK
- 读取最后一个字节后发送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,那肯定不行。
配置流程大致如下:
- 根据EEPROM信息确定速率:从0x0E字节读取标称比特率,确认模块支持的能力。
- 配置FPGA收发器:设置PLL分频比、参考时钟频率、数据位宽等。
- 配置协议层:比如以太网协议,需要配置MAC层、PCS层、PMA层。
- 设置TX_DISABLE信号:有些模块需要拉低这个引脚才能正常发送。
- 等待模块锁定:模块内部有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的速率和协议。每一步都有坑,但只要你按流程来,问题不大。
下一章,我们聊聊实际调试中遇到的一些诡异问题,以及怎么用示波器和逻辑分析仪抓波形。