Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SFF-8472 Rx power conversion function incorrect #449

Open
JaapKeuter opened this issue Mar 28, 2024 · 5 comments · May be fixed by #479
Open

SFF-8472 Rx power conversion function incorrect #449

JaapKeuter opened this issue Mar 28, 2024 · 5 comments · May be fixed by #479

Comments

@JaapKeuter
Copy link

# RX_PWR(uW) = RX_PWR_4 * RX_PWR_AD +
# RX_PWR_3 * RX_PWR_AD +
# RX_PWR_2 * RX_PWR_AD +
# RX_PWR_1 * RX_PWR_AD +
# RX_PWR(0)
off = self.dom_ext_calibration_constants['RX_PWR_4']['offset']
rx_pwr_byte3 = int(eeprom_data[off], 16)
rx_pwr_byte2 = int(eeprom_data[off + 1], 16)
rx_pwr_byte1 = int(eeprom_data[off + 2], 16)
rx_pwr_byte0 = int(eeprom_data[off + 3], 16)
rx_pwr_4 = (rx_pwr_byte3 << 24) | (rx_pwr_byte2 << 16) | (rx_pwr_byte1 << 8) | (rx_pwr_byte0 & 0xff)
off = self.dom_ext_calibration_constants['RX_PWR_3']['offset']
rx_pwr_byte3 = int(eeprom_data[off], 16)
rx_pwr_byte2 = int(eeprom_data[off + 1], 16)
rx_pwr_byte1 = int(eeprom_data[off + 2], 16)
rx_pwr_byte0 = int(eeprom_data[off + 3], 16)
rx_pwr_3 = (rx_pwr_byte3 << 24) | (rx_pwr_byte2 << 16) | (rx_pwr_byte1 << 8) | (rx_pwr_byte0 & 0xff)
off = self.dom_ext_calibration_constants['RX_PWR_2']['offset']
rx_pwr_byte3 = int(eeprom_data[off], 16)
rx_pwr_byte2 = int(eeprom_data[off + 1], 16)
rx_pwr_byte1 = int(eeprom_data[off + 2], 16)
rx_pwr_byte0 = int(eeprom_data[off + 3], 16)
rx_pwr_2 = (rx_pwr_byte3 << 24) | (rx_pwr_byte2 << 16) | (rx_pwr_byte1 << 8) | (rx_pwr_byte0 & 0xff)
off = self.dom_ext_calibration_constants['RX_PWR_1']['offset']
rx_pwr_byte3 = int(eeprom_data[off], 16)
rx_pwr_byte2 = int(eeprom_data[off + 1], 16)
rx_pwr_byte1 = int(eeprom_data[off + 2], 16)
rx_pwr_byte0 = int(eeprom_data[off + 3], 16)
rx_pwr_1 = (rx_pwr_byte3 << 24) | (rx_pwr_byte2 << 16) | (rx_pwr_byte1 << 8) | (rx_pwr_byte0 & 0xff)
off = self.dom_ext_calibration_constants['RX_PWR_0']['offset']
rx_pwr_byte3 = int(eeprom_data[off], 16)
rx_pwr_byte2 = int(eeprom_data[off + 1], 16)
rx_pwr_byte1 = int(eeprom_data[off + 2], 16)
rx_pwr_byte0 = int(eeprom_data[off + 3], 16)
rx_pwr_0 = (rx_pwr_byte3 << 24) | (rx_pwr_byte2 << 16) | (rx_pwr_byte1 << 8) | (rx_pwr_byte0 & 0xff)
rx_pwr = (rx_pwr_4 * result) + (rx_pwr_3 * result) + (rx_pwr_2 * result) + (rx_pwr_1 * result) + rx_pwr_0

SFF-8472 section 9.3

The specification states, under 5)
Received power, RX_PWR, is given in uW by the following equation:

Rx_PWR (uW) = Rx_PWR(4) * Rx_PWR_ADe4 (16 bit unsigned integer) +
           Rx_PWR(3) * Rx_PWR_ADe3 (16 bit unsigned integer) +
           Rx_PWR(2) * Rx_PWR_ADe2 (16 bit unsigned integer) +
           Rx_PWR(1) * Rx_PWR_AD (16 bit unsigned integer) +
           Rx_PWR(0)

which means Rx_PWR_AD is raised to the power of 4, 3 and 2 respectively in the expression. The comment and code does not reflect that.

Furthermore, the calibration constants are stored in IEEE Std 754 single precision float format in the device. These cannot be treated as integers, as is done in the code.

ishidawataru added a commit to ishidawataru/sonic-platform-common that referenced this issue Jun 28, 2024
closes sonic-net#449

Signed-off-by: Wataru Ishida <wataru.ishid@gmail.com>
@ishidawataru ishidawataru linked a pull request Jun 28, 2024 that will close this issue
@ishidawataru
Copy link

@JaapKeuter Could you test #479?

ishidawataru added a commit to ishidawataru/sonic-platform-common that referenced this issue Jun 28, 2024
closes sonic-net#449

Signed-off-by: Wataru Ishida <wataru.ishid@gmail.com>
@JaapKeuter
Copy link
Author

@JaapKeuter Could you test #479?

Test? I wish I could, but have no SONiC setup either.
Maybe I could find some EEPROM dumps from SFPs that use external calibration and run these through this code to see if the output makes sense. It will not be a test of this code in-situ though.

@ishidawataru
Copy link

I see. I can also do that test if you can share the EEPROM dumps and the expected rx power of the SFPs.

@JaapKeuter
Copy link
Author

I have been able to get two A2 page dumps from a SFP with different received power levels. Now I've to find some time to 'run the numbers'.

@JaapKeuter
Copy link
Author

Analysis of SFP received optical power calculation code

Introduction

The question was put forward to come up with data read from a SFP with
external calibration of the received optical power. This data, the so-called
A2 page, would then be used to evaluate Python code converting this data
into the received optical power value.

This analysis is based on two data points taken from the same SFP with different
received optical power levels.

References

  1. SFF-8472 Specification for Management Interface for SFP+ Rev R12.4

Specifications

Section 9 of Ref 1. contains the relevant specifications for the interpretation
of the A2 page data, in particular section 9.3, which goes into External
Calibration
. Together with section 9.5 Calibration Constants for External
Calibration Option
this allows for the calculation of the received optical
power from the A2 page data. Section 9.8 Real Time Diagnostics and Control
Registers
specifies the field for the measured received optical power.

Method of analysis

  • Set a light level on the Rx port of the SFP
  • Retrieve the A2 page of the SFP and the reported received optical power
  • Extract the calibration constants from the A2 page
  • Extract the raw received optical power value from the A2 page
  • Convert the raw received optical power value into dBm
  • Verify converted value with reported value
  • Put all the raw data into Python code
  • Verify output value with reported value

Collected data and analysis

In this section the retrieved A2 page data sets are presented, and using the
specifications listed above the calculation of the received optical power is
made from this data. Then the data is used in the Python code to verify whether
the results match. The Python code is derived from the proposed implementation.

First sample

Data

Received avg. optical power: -6.1 dBm

Raw A2 dump:
00000000 5a 00 f6 00 55 00 fb 00 87 57 70 c8 83 94 74 8b Z...U....Wp...t.
00000010 6f 77 04 3f 6a 28 05 4f 7b d2 0f 97 62 5b 13 a0 ow.?j(.O{...b[..
00000020 ff ff 05 94 ff ff 06 ad 00 00 00 00 00 00 00 00 ................
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000040 00 00 00 00 3d bc da 2a c1 fd c5 28 01 d7 00 00 ....=..*...(....
00000050 00 80 00 00 01 00 00 00 01 04 00 00 00 00 00 8c ................
00000060 40 00 7f 00 22 00 25 00 68 d0 00 00 00 00 10 00 @...".%.h.......
00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000f0 00 00 00 00 00 00 00 00 00 00 00 00 08 3d 00 20 .............=.

Analysis

Calibration constants

Rx_PWR(4): A2[56-59]: A2[0x38-0x3b]: 00 00 00 00: Float = 5.877471754111438e-39 = 0
Rx_PWR(3): A2[60-63]: A2[0x3c-0x3f]: 00 00 00 00: Float = 5.877471754111438e-39 = 0
Rx_PWR(2): A2[64-67]: A2[0x40-0x43]: 00 00 00 00: Float = 5.877471754111438e-39 = 0
Rx_PWR(1): A2[89-71]: A2[0x44-0x47]: 3d bc da 2a: Float = 0.0922129899263382    = 0.09221299
Rx_PWR(0): A2[72-75]: A2[0x48-0x4b]: c1 fd c5 28: Float = -31.721267700195312   = -31.721268

Raw received power value

Rx_PWR_AD: A2[104-105]: A2[0x68-0x69]: 68 d0 : 26832

Measured received optical power

Rx_PWR(uW) = ( Rx_PWR(4) * Rx_PWR_AD^4 +
               Rx_PWR(3) * Rx_PWR_AD^3 +
               Rx_PWR(2) * Rx_PWR_AD^2 +
               Rx_PWR(1) * Rx_PWR_AD +
               Rx_PWR(0) ) / 10

           = ( 0 * 26832^4 +
               0 * 26832^3 +
               0 * 26832^2 +
               0.09221299 * 26832 +
               -31.721268 ) / 10

           = ( 0 + 0 + 0 + 2474.25894768 + -31.721268 ) / 10

           = 244.253767968 uW

Conversion to dBm

Opt_Pwr(dBm) = 10 * Log10(Pwr(mW))

             = 10 * Log10(244.253767968 / 1000)

             = -6.12158727936 dBm

Putting data into Python code

#!/bin/env python3

from math import log10
from struct import unpack

def mw_to_dbm(mW):
    if mW == 0:
        return float("-inf")
    elif mW < 0:
        return float("NaN")
    return 10. * log10(mW)

def power_in_dbm_str(mW):
    return "%.4f%s" % (mw_to_dbm(mW), "dBm")

rx_pwr_ad = 26832
result = 0

coeff = unpack('!f', b'\x00\x00\x00\x00')[0]
result += coeff * rx_pwr_ad ** 4

coeff = unpack('!f', b'\x00\x00\x00\x00')[0]
result += coeff * rx_pwr_ad ** 3

coeff = unpack('!f', b'\x00\x00\x00\x00')[0]
result += coeff * rx_pwr_ad ** 2

coeff = unpack('!f', b'\x3d\xbc\xda\x2a')[0]
result += coeff * rx_pwr_ad ** 1

coeff = unpack('!f', b'\xc1\xfd\xc5\x28')[0]
result += coeff * rx_pwr_ad ** 0

result = result * 0.0001
print(power_in_dbm_str(result))

This give the output

-6.1216dBm

Result

The reported value, the converted retrieved data and the Python code all come
to the same value for the received optical power.

Second sample

Data

Received avg. optical power: -16.8 dBm

Raw A2 dump:
00000000 5a 00 f6 00 55 00 fb 00 87 57 70 c8 83 94 74 8b Z...U....Wp...t.
00000010 6f 77 04 3f 6a 28 05 4f 7b d2 0f 97 62 5b 13 a0 ow.?j(.O{...b[..
00000020 ff ff 05 94 ff ff 06 ad 00 00 00 00 00 00 00 00 ................
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000040 00 00 00 00 3d bc da 2a c1 fd c5 28 01 d7 00 00 ....=..*...(....
00000050 00 80 00 00 01 00 00 00 01 04 00 00 00 00 00 8c ................
00000060 40 00 7f 00 22 00 25 00 0a 30 00 00 00 00 10 00 @...".%..0......
00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000f0 00 00 00 00 00 00 00 00 00 00 00 00 08 3d 00 00 .............=..

Analysis

Calibration constants

Rx_PWR(4): A2[56-59]: A2[0x38-0x3b]: 00 00 00 00: Float = 5.877471754111438e-39 = 0
Rx_PWR(3): A2[60-63]: A2[0x3c-0x3f]: 00 00 00 00: Float = 5.877471754111438e-39 = 0
Rx_PWR(2): A2[64-67]: A2[0x40-0x43]: 00 00 00 00: Float = 5.877471754111438e-39 = 0
Rx_PWR(1): A2[89-71]: A2[0x44-0x47]: 3d bc da 2a: Float = 0.0922129899263382    = 0.09221299
Rx_PWR(0): A2[72-75]: A2[0x48-0x4b]: c1 fd c5 28: Float = -31.721267700195312   = -31.721268

Raw received power value

Rx_PWR_AD: A2[104-105]: A2[0x68-0x69]: 0a 30 : 2608

Measured received optical power

Rx_PWR(uW) = ( Rx_PWR(4) * Rx_PWR_AD^4 +
               Rx_PWR(3) * Rx_PWR_AD^3 +
               Rx_PWR(2) * Rx_PWR_AD^2 +
               Rx_PWR(1) * Rx_PWR_AD +
               Rx_PWR(0) ) / 10

           = ( 0 * 2608^4 +
               0 * 2608^3 +
               0 * 2608^2 +
               0.09221299 * 2608 +
               -31.721268 ) / 10

           = ( 0 + 0 + 0 + 240.49147792 + -31.721268 ) / 10

           = 20.877020992 uW

Conversion to dBm

Opt_Pwr(dBm) = 10 * Log10(Pwr(mW))

             = 10 * Log10(20.877020992 / 1000)

             = -16.803314721 dBm

Putting data into Python code

#!/bin/env python3

from math import log10
from struct import unpack

def mw_to_dbm(mW):
    if mW == 0:
        return float("-inf")
    elif mW < 0:
        return float("NaN")
    return 10. * log10(mW)

def power_in_dbm_str(mW):
    return "%.4f%s" % (mw_to_dbm(mW), "dBm")

rx_pwr_ad = 2608
result = 0

coeff = unpack('!f', b'\x00\x00\x00\x00')[0]
result += coeff * rx_pwr_ad ** 4

coeff = unpack('!f', b'\x00\x00\x00\x00')[0]
result += coeff * rx_pwr_ad ** 3

coeff = unpack('!f', b'\x00\x00\x00\x00')[0]
result += coeff * rx_pwr_ad ** 2

coeff = unpack('!f', b'\x3d\xbc\xda\x2a')[0]
result += coeff * rx_pwr_ad ** 1

coeff = unpack('!f', b'\xc1\xfd\xc5\x28')[0]
result += coeff * rx_pwr_ad ** 0

result = result * 0.0001
print(power_in_dbm_str(result))

This give the output

-16.8033dBm

Result

The reported value, the converted retrieved data and the Python code all come
to the same value for the received optical power.

Conclusion

The Python code correctly converts the data extracted from the SFP into the
received optical power value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants