From 13516fe665bdd555a564e85d7e4f849a0cd3b8b0 Mon Sep 17 00:00:00 2001 From: michelle-nexthop <225432255+michelle-nexthop@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:10:41 -0800 Subject: [PATCH] Add fec: rs to port config for ports with >=50G per lane (#1950) Why I did it Ports with lane speed >=50G use PAM4 modulation and should indicate "fec": "rs" in the port config. This fixes issue Enhancement:FEC not configured for PAM4 speeds #23561 #### Why I did it Ports with lane speed >=50G use PAM4 modulation and should indicate "fec": "rs" in the port config. Without this, the FEC configuration is missing for PAM4 speeds, which can lead to link stability issues. ##### Work item tracking - Microsoft ADO **(number only)**: #### How I did it Modified `portconfig.py` to automatically set `"fec": "rs"` for ports where the lane speed is >=50G (PAM4 modulation). Updated the logic to detect lane speed based on port speed and number of lanes, and apply the appropriate FEC configuration. #### How to verify it 1. Generate port configuration for platforms with ports using >=50G per lane (e.g., 400G ports with 8 lanes, 200G ports with 4 lanes) 2. Verify that the generated config includes `"fec": "rs"` for these ports 3. Run the updated unit tests: `test_cfggen_platformJson.py` #### Which release branch to backport (provide reason below if selected) - [ ] 201811 - [ ] 201911 - [ ] 202006 - [ ] 202012 - [ ] 202106 - [ ] 202111 - [ ] 202205 - [ ] 202211 #### Tested branch (Please provide the tested image version) - [x] 202412 branch #### Description for the changelog Add automatic FEC RS configuration for ports with lane speed >=50G (PAM4 modulation) #### Link to config_db schema for YANG module changes #### A picture of a cute animal (not mandatory but encouraged) --- src/sonic-config-engine/portconfig.py | 8 +- .../tests/test_cfggen_platformJson.py | 132 ++++++++++++++++++ 2 files changed, 139 insertions(+), 1 deletion(-) diff --git a/src/sonic-config-engine/portconfig.py b/src/sonic-config-engine/portconfig.py index 3d2bc55d02..8491af37ef 100644 --- a/src/sonic-config-engine/portconfig.py +++ b/src/sonic-config-engine/portconfig.py @@ -374,13 +374,19 @@ def get_config(self): lanes = self._lanes[lane_id:lane_id + lanes_per_port] - ports[interface_name] = { + port_config = { 'alias': self._breakout_capabilities[alias_id], 'lanes': ','.join(lanes), 'speed': str(entry.default_speed), 'index': self._indexes[lane_id], 'subport': "0" if total_num_ports == 1 else str(alias_id + 1) } + + # If the lane speed is greater than 50G, enable FEC + if entry.default_speed // lanes_per_port >= 50000: + port_config['fec'] = 'rs' + + ports[interface_name] = port_config lane_id += lanes_per_port alias_id += 1 diff --git a/src/sonic-config-engine/tests/test_cfggen_platformJson.py b/src/sonic-config-engine/tests/test_cfggen_platformJson.py index 4a255ba5d4..4acde02262 100644 --- a/src/sonic-config-engine/tests/test_cfggen_platformJson.py +++ b/src/sonic-config-engine/tests/test_cfggen_platformJson.py @@ -116,3 +116,135 @@ def test_platform_json_no_interfaces(self): (ports, _, _) = get_port_config(port_config_file=self.platform_json) self.assertNotEqual(ports, None) self.assertEqual(ports, {}) + + # Check that FEC 'rs' is properly set for lanes with speed >= 50G per lane + def test_fec_rs(self): + # Ethernet0 is 1x100G + argument = ['-m', self.platform_sample_graph, '-p', self.platform_json, '-S', self.hwsku_json, '-v', "PORT[\'Ethernet0\']"] + output = self.run_script(argument) + port_config = utils.to_dict(output.strip()) + self.assertIn('fec', port_config) + self.assertEqual(port_config['fec'], 'rs') + + # Ethernet4 is 2x50G + argument = ['-m', self.platform_sample_graph, '-p', self.platform_json, '-S', self.hwsku_json, '-v', "PORT[\'Ethernet4\']"] + output = self.run_script(argument) + port_config = utils.to_dict(output.strip()) + self.assertNotIn('fec', port_config) + + # Check FEC logic with custom platform.json + def test_fec_rs_custom(self): + test_platform_json = { + "interfaces": { + "Ethernet200": { + "index": "50,50,50,50,50,50,50,50", + "lanes": "200,201,202,203,204,205,206,207", + "breakout_modes": { + "8x800G": ["Eth50/1", "Eth50/2", "Eth50/3", "Eth50/4", "Eth50/5", "Eth50/6", "Eth50/7", "Eth50/8"] + } + }, + "Ethernet208": { + "index": "51,51,51,51", + "lanes": "208,209,210,211", + "breakout_modes": { + "4x25G": ["Eth51/1", "Eth51/2", "Eth51/3", "Eth51/4"] + } + } + } + } + + test_hwsku_json = { + "interfaces": { + "Ethernet200": {"default_brkout_mode": "8x800G"}, + "Ethernet208": {"default_brkout_mode": "4x25G"}, + } + } + + with mock.patch('portconfig.readJson') as mock_read_json: + def side_effect(filename): + if 'platform' in filename: + return test_platform_json + elif 'hwsku' in filename: + return test_hwsku_json + return None + + mock_read_json.side_effect = side_effect + + from portconfig import get_child_ports + ports = get_child_ports("Ethernet200", "8x800G", "test_platform.json") + self.assertIn('fec', ports['Ethernet200']) + self.assertEqual(ports['Ethernet200']['fec'], 'rs') + + ports = get_child_ports("Ethernet208", "4x25G", "test_platform.json") + self.assertNotIn('fec', ports['Ethernet208']) + + # Check FEC logic for edge cases around the 50G per lane threshold + def test_fec_rs_for_edge_cases(self): + test_platform_json = { + "interfaces": { + "Ethernet200": { + "index": "50,50", + "lanes": "200,201", + "breakout_modes": { + "1x100G": ["Eth50/1"], + "2x50G": ["Eth50/1", "Eth50/2"] + } + }, + "Ethernet204": { + "index": "51,51", + "lanes": "204,205", + "breakout_modes": { + "1x102G": ["Eth51/1"] + } + }, + "Ethernet208": { + "index": "52", + "lanes": "208", + "breakout_modes": { + "1x50G": ["Eth52/1"] + } + }, + "Ethernet212": { + "index": "53", + "lanes": "212", + "breakout_modes": { + "1x49G": ["Eth53/1"] + } + } + } + } + + test_hwsku_json = { + "interfaces": { + "Ethernet200": {"default_brkout_mode": "1x100G"}, + "Ethernet204": {"default_brkout_mode": "1x102G"}, + "Ethernet208": {"default_brkout_mode": "1x50G"}, + "Ethernet212": {"default_brkout_mode": "1x49G"} + } + } + + with mock.patch('portconfig.readJson') as mock_read_json: + def side_effect(filename): + if 'platform' in filename: + return test_platform_json + elif 'hwsku' in filename: + return test_hwsku_json + return None + + mock_read_json.side_effect = side_effect + + from portconfig import get_child_ports + ports = get_child_ports("Ethernet200", "1x100G", "test_platform.json") + self.assertIn('fec', ports['Ethernet200']) + self.assertEqual(ports['Ethernet200']['fec'], 'rs') + + ports = get_child_ports("Ethernet204", "1x102G", "test_platform.json") + self.assertIn('fec', ports['Ethernet204']) + self.assertEqual(ports['Ethernet204']['fec'], 'rs') + + ports = get_child_ports("Ethernet208", "1x50G", "test_platform.json") + self.assertIn('fec', ports['Ethernet208']) + self.assertEqual(ports['Ethernet208']['fec'], 'rs') + + ports = get_child_ports("Ethernet212", "1x49G", "test_platform.json") + self.assertNotIn('fec', ports['Ethernet212'])