diff --git a/cmake/AsicConfigV3ConfigCli.cmake b/cmake/AsicConfigV3ConfigCli.cmake new file mode 100644 index 0000000000000..78b6823cc623b --- /dev/null +++ b/cmake/AsicConfigV3ConfigCli.cmake @@ -0,0 +1,38 @@ +# CMake to build libraries and binaries in fboss/lib/asic_config_v3 +# Data-driven ASIC config generation + +include(FBPythonBinary) + +set( + ASIC_CONFIG_V3_PY_SRCS + "fboss/lib/asic_config_v3/__init__.py" + "fboss/lib/asic_config_v3/base_generator.py" + "fboss/lib/asic_config_v3/gen.py" + "fboss/lib/asic_config_v3/generators/__init__.py" + "fboss/lib/asic_config_v3/generators/broadcom_xgs_generator.py" + "fboss/lib/platform_mapping_v2/asic_vendor_config.py" + "fboss/lib/platform_mapping_v2/gen.py" + "fboss/lib/platform_mapping_v2/helpers.py" + "fboss/lib/platform_mapping_v2/platform_mapping_v2.py" + "fboss/lib/platform_mapping_v2/port_profile_mapping.py" + "fboss/lib/platform_mapping_v2/profile_settings.py" + "fboss/lib/platform_mapping_v2/read_files_utils.py" + "fboss/lib/platform_mapping_v2/si_settings.py" + "fboss/lib/platform_mapping_v2/static_mapping.py" +) + +add_fb_thrift_python_executable( + fboss-asic-config-v3-gen + MAIN_MODULE fboss.lib.asic_config_v3.gen:generate_all_asic_configs + SOURCES ${ASIC_CONFIG_V3_PY_SRCS} + DEPENDS + platform_config_python + switch_config_python + transceiver_python + phy_python + platform_mapping_config_python + fboss_common_python + python-pyyaml::python-pyyaml +) + +install_fb_python_executable(fboss-asic-config-v3-gen) diff --git a/fboss/lib/asic_config_v2/run-helper.sh b/fboss/lib/asic_config_v2/run-helper.sh index 80ba38234ea30..02491b608cb6e 100755 --- a/fboss/lib/asic_config_v2/run-helper.sh +++ b/fboss/lib/asic_config_v2/run-helper.sh @@ -1,2 +1,5 @@ #!/bin/bash -python3 fboss/lib/oss/run-helper.py --target fboss-asic-config-gen "$@" +python3 fboss/lib/oss/run-helper.py \ + --target fboss-asic-config-gen.GEN_PY_EXE \ + --extra-cmake-defines='{"RANGE_V3_TESTS": "OFF"}' \ + "$@" diff --git a/fboss/lib/asic_config_v3/__init__.py b/fboss/lib/asic_config_v3/__init__.py new file mode 100644 index 0000000000000..35302f75f52a5 --- /dev/null +++ b/fboss/lib/asic_config_v3/__init__.py @@ -0,0 +1 @@ +# pyre-strict diff --git a/fboss/lib/asic_config_v3/base_generator.py b/fboss/lib/asic_config_v3/base_generator.py new file mode 100644 index 0000000000000..f151cfd37ce4a --- /dev/null +++ b/fboss/lib/asic_config_v3/base_generator.py @@ -0,0 +1,82 @@ +# pyre-strict + +import copy +import os +from abc import ABC, abstractmethod +from typing import Any + +# Resolve config paths relative to the repo root rather than this file so the +# generator can run from the same checkout layout the bundled getdeps build +# expects. +_FBOSS_DIR: str = os.getcwd() + "/fboss" +MODULE_DIR: str = f"{_FBOSS_DIR}/lib/asic_config_v3" + + +def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]: + """Recursively merge ``override`` on top of ``base``. + + Dict values present in both are merged recursively. For any other value + type (scalars, lists, or a type mismatch between the two sides) the + override replaces the base entry outright. Returns a new dict; the inputs + are not mutated. + """ + result = copy.deepcopy(base) + for key, ov_value in override.items(): + if ( + key in result + and isinstance(result[key], dict) + and isinstance(ov_value, dict) + ): + result[key] = _deep_merge(result[key], ov_value) + else: + result[key] = copy.deepcopy(ov_value) + return result + + +class BaseAsicConfigGenerator(ABC): + """Abstract base class for ASIC config generators. + + Subclasses implement ``generate()`` to produce vendor-specific output + such as YAML or JSON. + """ + + def __init__( + self, + platform_name: str, + variant: str, + platform_config: dict[str, Any], + ) -> None: + self.platform_name = platform_name + self.variant = variant + self.platform_config = platform_config + + vendor = platform_config.get("vendor") + asic = platform_config.get("asic") + if not vendor: + raise ValueError("platform asic_config.json must define 'vendor'") + if not asic: + raise ValueError("platform asic_config.json must define 'asic'") + self.asic_vendor: str = vendor + self.asic_name: str = asic + + # The platform JSON may declare a top-level ``defaults`` block inherited + # by every variant. The effective variant config is produced by deep- + # merging the variant-specific entries on top of ``defaults``. Dict + # values are merged recursively; scalars and lists are replaced. + defaults = platform_config.get("defaults", {}) + variant_override = platform_config.get("variants", {}).get(variant, {}) + self.variant_config: dict[str, Any] = _deep_merge(defaults, variant_override) + self.asic_config_params: dict[str, Any] = self.variant_config.get( + "asic_config_params", {} + ) + + @abstractmethod + def generate(self) -> str: + """Generate the complete ASIC config and return it as a string.""" + ... + + @property + @abstractmethod + def output_extension(self) -> str: + """File extension for the generated output (e.g. '.yml' or '.json').""" + ... diff --git a/fboss/lib/asic_config_v3/common/ocp_sai_common.json b/fboss/lib/asic_config_v3/common/ocp_sai_common.json new file mode 100644 index 0000000000000..c0af00ae2bd0b --- /dev/null +++ b/fboss/lib/asic_config_v3/common/ocp_sai_common.json @@ -0,0 +1,4 @@ +{ + "_comment": "OCP SAI standard settings (vendor-agnostic).", + "global": {} +} diff --git a/fboss/lib/asic_config_v3/gen.py b/fboss/lib/asic_config_v3/gen.py new file mode 100644 index 0000000000000..55b7bf3d37d2c --- /dev/null +++ b/fboss/lib/asic_config_v3/gen.py @@ -0,0 +1,115 @@ +# pyre-strict + +import json +import os +import sys + +from fboss.lib.asic_config_v3.base_generator import ( + BaseAsicConfigGenerator, + MODULE_DIR, +) +from fboss.lib.asic_config_v3.generators.broadcom_xgs_generator import ( + BroadcomXgsGenerator, +) + +OUTPUT_DIR: str = f"{MODULE_DIR}/generated_asic_configs" + +# Add a new (vendor, asic) entry when bringing up a new ASIC family. +_GENERATOR_REGISTRY: dict[tuple[str, str], type[BaseAsicConfigGenerator]] = { + ("broadcom", "tomahawk5"): BroadcomXgsGenerator, + ("broadcom", "tomahawk6"): BroadcomXgsGenerator, +} + + +def get_generator( + platform_name: str, variant: str, platform_config: dict +) -> BaseAsicConfigGenerator: + """Instantiate the correct generator based on vendor and ASIC.""" + vendor = platform_config["vendor"] + asic = platform_config["asic"] + key = (vendor, asic) + generator_cls = _GENERATOR_REGISTRY.get(key) + if not generator_cls: + raise ValueError(f"No generator registered for vendor={vendor}, asic={asic}") + return generator_cls(platform_name, variant, platform_config) + + +def discover_platforms() -> dict: + """Return a mapping of platform name to platform config. + + Discovered by scanning ``platforms/*/asic_config.json``. + """ + platforms_dir = os.path.join(MODULE_DIR, "platforms") + platforms = {} + + if not os.path.exists(platforms_dir): + return platforms + + for platform_name in os.listdir(platforms_dir): + platform_path = os.path.join(platforms_dir, platform_name) + if not os.path.isdir(platform_path): + continue + + config_path = os.path.join(platform_path, "asic_config.json") + if not os.path.exists(config_path): + continue + + with open(config_path) as f: + platform_config = json.load(f) + platforms[platform_name] = platform_config + + return platforms + + +def generate_all_asic_configs() -> None: + """Generate ASIC configs for every discovered platform and variant.""" + os.makedirs(OUTPUT_DIR, exist_ok=True) + + for filename in os.listdir(OUTPUT_DIR): + if filename.endswith((".json", ".yml")): + os.remove(os.path.join(OUTPUT_DIR, filename)) + + platforms = discover_platforms() + + for platform_name, platform_config in platforms.items(): + vendor = platform_config.get("vendor", "") + asic = platform_config.get("asic", "") + + if (vendor, asic) not in _GENERATOR_REGISTRY: + print( + f"Skipping {platform_name} (no generator for vendor={vendor}, asic={asic})", + file=sys.stderr, + ) + continue + + variants = platform_config.get("variants", {}) + + for variant_name in variants: + print( + f"Generating ASIC config for {platform_name}/{variant_name}...", + file=sys.stderr, + ) + + try: + generator = get_generator(platform_name, variant_name, platform_config) + output = generator.generate() + + output_filename = ( + f"{platform_name}_{variant_name}{generator.output_extension}" + ) + output_path = os.path.join(OUTPUT_DIR, output_filename) + + print(f"Writing to {output_path}", file=sys.stderr) + with open(output_path, "w", encoding="utf-8") as f: + f.write(output) + + except Exception as e: + print( + f"Error generating config for {platform_name}/{variant_name}: {e}", + file=sys.stderr, + ) + raise + + +if __name__ == "__main__": + generate_all_asic_configs() diff --git a/fboss/lib/asic_config_v3/generators/__init__.py b/fboss/lib/asic_config_v3/generators/__init__.py new file mode 100644 index 0000000000000..35302f75f52a5 --- /dev/null +++ b/fboss/lib/asic_config_v3/generators/__init__.py @@ -0,0 +1 @@ +# pyre-strict diff --git a/fboss/lib/asic_config_v3/generators/broadcom_xgs_generator.py b/fboss/lib/asic_config_v3/generators/broadcom_xgs_generator.py new file mode 100644 index 0000000000000..287558625b03a --- /dev/null +++ b/fboss/lib/asic_config_v3/generators/broadcom_xgs_generator.py @@ -0,0 +1,505 @@ +# pyre-strict + +import json +import os +from typing import Any + +import yaml + +from fboss.lib.asic_config_v3.base_generator import ( + BaseAsicConfigGenerator, + MODULE_DIR, +) +from fboss.lib.platform_mapping_v2.gen import read_all_vendor_data +from fboss.lib.platform_mapping_v2.platform_mapping_v2 import PlatformMappingParser + + +class BroadcomXgsGenerator(BaseAsicConfigGenerator): + """Data-driven ASIC config generator for the Broadcom XGS family. + + Produces multi-document YAML output with typed tables. + """ + + ASIC_FAMILY: str = "xgs" + + def __init__( + self, + platform_name: str, + variant: str, + platform_config: dict[str, Any], + ) -> None: + super().__init__(platform_name, variant, platform_config) + + self.values: dict[str, Any] = {} + self.asic_config: dict[str, Any] = {} + + self._load_vendor_configs() + self._init_tables() + + self.parser = PlatformMappingParser(read_all_vendor_data(), self.platform_name) + + self.num_ports_per_core: int = self.platform_config.get("num_ports_per_core", 2) + self.mmu_size: int = self.asic_config.get("mmu_size", 9416) + + # Compute lanes_per_port from ASIC port_architecture and platform num_ports_per_core + port_arch = self.asic_config.get("port_architecture", {}) + num_lanes_per_core = port_arch.get("num_lanes_per_core", 8) + self.lanes_per_port: int = num_lanes_per_core // self.num_ports_per_core + + @property + def output_extension(self) -> str: + return ".yml" + + def _load_vendor_configs(self) -> None: + """Load the family-wide OCP, SDK, SAI common blocks and the per-ASIC config.""" + vendor = self.asic_vendor + asic = self.asic_name + + ocp_sai_common_path = os.path.join(MODULE_DIR, "common", "ocp_sai_common.json") + if os.path.exists(ocp_sai_common_path): + with open(ocp_sai_common_path) as f: + self.ocp_sai_common = json.load(f) + else: + self.ocp_sai_common = {"global": {}} + + vendor_sdk_common_path = os.path.join( + MODULE_DIR, "vendors", vendor, self.ASIC_FAMILY, "sdk_common.json" + ) + with open(vendor_sdk_common_path) as f: + self.vendor_sdk_common = json.load(f) + + vendor_sai_common_path = os.path.join( + MODULE_DIR, "vendors", vendor, self.ASIC_FAMILY, "sai_common.json" + ) + with open(vendor_sai_common_path) as f: + self.vendor_sai_common = json.load(f) + + asic_config_path = os.path.join( + MODULE_DIR, "vendors", vendor, self.ASIC_FAMILY, "asics", f"{asic}.json" + ) + with open(asic_config_path) as f: + self.asic_config = json.load(f) + + self.asic_name = self.asic_config.get("asic", asic) + + def _init_tables(self) -> None: + """Initialize empty dictionaries for each table.""" + table_names = self.asic_config.get("table_names", []) + for table_name in table_names: + self.values[table_name] = {} + + def _read_preamble(self, file_name: str) -> str: + """Read preamble file contents.""" + preamble_path = os.path.join(MODULE_DIR, file_name) + with open(preamble_path, encoding="utf-8") as f: + return f.read() + + def get_lane_map_str(self, lane_map: list[int]) -> str: + """Convert a lane map list to its little-endian hex string form. + + For example, [3, 2, 1, 0, 7, 6, 5, 4] becomes "0x45670123". + """ + lane_map_str = "".join(str(lane) for lane in reversed(lane_map)) + return f"0x{lane_map_str}" + + def get_polarity_map_str(self, pn_map: list[int]) -> str: + """Convert a polarity map (per-lane binary flags) to a hex byte string. + + The polarity bits are read little-endian and packed into a single byte + rendered as "0x" plus two uppercase hex digits. + """ + pn_map_str = "".join(str(pn) for pn in reversed(pn_map)) + return f"0x{int(pn_map_str, 2):02X}" + + def _apply_pass_through_settings(self) -> None: + """Copy each pass-through source block to its target output table. + + A variant may declare a top-level dict at the same source key to + replace the ASIC default for that pass-through entirely. This lets a + platform emit a subset or different values than the chip-wide defaults. + """ + for pt in self.asic_config.get("pass_through_settings", []): + source_key = pt["source"] + target_table = pt["target_table"] + data = self.variant_config.get( + source_key, self.asic_config.get(source_key, {}) + ) + self.values[target_table].update(data) + + def _compute_skip_from_sai_common(self) -> list[str]: + """Collect skip_from_sai_common fields from all active conditional settings.""" + skip_fields: list[str] = [] + for cs in self.asic_config.get("conditional_settings", []): + condition = cs.get("condition", {}) + source = condition.get("source", "asic_config_params") + if source == "asic_config_params": + param_value = self.asic_config_params.get(condition["param"]) + elif source == "features": + param_value = self.variant_config.get("features", {}).get( + condition["param"] + ) + else: + continue + if param_value == condition.get("equals"): + skip_fields.extend(cs.get("skip_from_sai_common", [])) + return skip_fields + + def _apply_conditional_settings(self) -> None: + """Evaluate and apply conditional settings. + + ASIC-level entries from ``asic_config.conditional_settings`` are + evaluated first, then platform-level entries from + ``variant_config.conditional_settings``. Within each list, matching + entries are applied in array order. Platform entries are applied after + ASIC entries so platform settings can override chip-level ones. + """ + asic_entries = self.asic_config.get("conditional_settings", []) + platform_entries = self.variant_config.get("conditional_settings", []) + for cs in list(asic_entries) + list(platform_entries): + condition = cs.get("condition", {}) + source = condition.get("source", "asic_config_params") + if source == "asic_config_params": + param_value = self.asic_config_params.get(condition["param"]) + elif source == "features": + param_value = self.variant_config.get("features", {}).get( + condition["param"] + ) + else: + continue + if param_value != condition.get("equals"): + continue + + for table_name, entries in cs.get("apply", {}).items(): + self.values.setdefault(table_name, {}).update(entries) + + apply_from = cs.get("apply_from") + if apply_from: + source_key = apply_from["source"] + target_table = apply_from["target_table"] + self.values[target_table].update(self.asic_config.get(source_key, {})) + + def _apply_layered_global_settings(self) -> None: + """Apply layered settings to the ``global`` table. Higher-numbered steps override lower.""" + # OCP SAI common (vendor-agnostic SAI standard defaults) + self.values["global"].update(self.ocp_sai_common.get("global", {})) + + # ASIC global defaults (chip-specific base settings) + self.values["global"].update(self.asic_config.get("global_defaults", {})) + + # Vendor SDK common (shared BCM settings) + self.values["global"].update(self.vendor_sdk_common.get("global", {})) + + # Vendor SAI common (with skip_from_sai_common filtering) + skip_fields = self._compute_skip_from_sai_common() + for key, value in self.vendor_sai_common.get("global", {}).items(): + if key in skip_fields: + continue + self.values["global"][key] = value + + # ASIC SAI overrides (chip-specific SAI overrides) + self.values["global"].update(self.asic_config.get("sai_overrides", {})) + + # Platform-level SAI overrides from the variant config, layered + # on top of the chip-level SAI overrides so platform tuning wins. + self.values["global"].update( + self.variant_config.get("platform_sai_overrides", {}) + ) + + def _get_core_range(self) -> list[int]: + """Return the list of core indices to include in the port mapping. + + Honors the ``core_range`` override (a comma-separated list of inclusive + ranges such as ``"0-15,48-63"``) when present, otherwise falls back to + the ASIC's full core count. + """ + port_mapping_overrides = self.variant_config.get("port_mapping_overrides", {}) + core_range_str = port_mapping_overrides.get("core_range", None) + + if core_range_str: + cores = [] + for part in core_range_str.split(","): + if "-" in part: + start, end = part.split("-") + cores.extend(range(int(start), int(end) + 1)) + else: + cores.append(int(part)) + return cores + port_arch = self.asic_config.get("port_architecture", {}) + num_cores = port_arch.get("num_cores", 64) + return list(range(num_cores)) + + def _get_logical_port_to_physical_port_mapping(self) -> list[list[int]]: + """Compute the logical-to-physical port mapping for this variant. + + The mapping formula is driven by the ASIC's ``port_architecture`` and + may be tuned per variant via ``port_mapping_overrides``: + + * ``num_logical_ports_per_datapath``: override the ASIC-level value. + * ``num_lp_ports_on_even_core``: override the ASIC-level default. + * ``lp_start_step_offset``: offset added to the logical-ports-per- + datapath value when computing the per-datapath logical-port start. + Defaults to 1. + * ``lp_offset_simple``: when true, compute the per-port logical-port + offset as ``i * lanes_per_port`` on all cores rather than using + the default even / odd conditional. + """ + port_arch = self.asic_config.get("port_architecture", {}) + port_mapping_overrides = self.variant_config.get("port_mapping_overrides", {}) + + lp_per_dp = port_mapping_overrides.get( + "num_logical_ports_per_datapath", + port_arch.get("num_logical_ports_per_datapath", 10), + ) + pp_per_dp = port_arch.get("num_physical_ports_per_datapath", 16) + lp_on_even_core = port_mapping_overrides.get( + "num_lp_ports_on_even_core", + port_arch.get("num_lp_ports_on_even_core_default", 8), + ) + lanes_per_core = port_arch.get("num_lanes_per_core", 8) + lp_start_step_offset = port_mapping_overrides.get("lp_start_step_offset", 1) + lp_offset_simple = port_mapping_overrides.get("lp_offset_simple", False) + + logical_to_physical_port_mapping = [] + cores = self._get_core_range() + + for core_num in cores: + core_offset = 1 if core_num <= 1 else 0 + lp_start_offset = 0 if core_num % 2 == 0 else lp_on_even_core + lp_start = ( + (core_num // 2) * (lp_per_dp + lp_start_step_offset) + + lp_start_offset + + core_offset + ) + pp_start_offset = 0 if core_num % 2 == 0 else lanes_per_core + pp_start = ((core_num // 2) * pp_per_dp) + 1 + pp_start_offset + + for i in range(self.num_ports_per_core): + if lp_offset_simple: + lp_offset = i * self.lanes_per_port + else: + lp_offset = i * (self.lanes_per_port if core_num % 2 == 0 else 1) + pp_offset = i * self.lanes_per_port + lp_id = lp_start + lp_offset + pp_id = pp_start + pp_offset + logical_to_physical_port_mapping.append([lp_id, pp_id]) + + return logical_to_physical_port_mapping + + def _generate_logical_port_to_physical_port_mapping( + self, mgmt_port: bool = False + ) -> None: + """Generate PC_PORT_PHYS_MAP entries.""" + lp_mapping = self._get_logical_port_to_physical_port_mapping() + + # Reserve the CPU port row for logical 0 to physical 0 mapping. + lp_mapping.append([0, 0]) + + for lp_map in lp_mapping: + pm_key = (f"PORT_ID: {lp_map[0]}",) + pm_value = (f"PC_PHYS_PORT_ID: {lp_map[1]}",) + self.values["PC_PORT_PHYS_MAP"][pm_key] = pm_value + + if mgmt_port: + mgmt_defaults = self.asic_config.get("mgmt_port_defaults", {}) + mgmt_overrides = self.variant_config.get("mgmt_port_overrides", {}) + logical_id = mgmt_overrides.get( + "logical_id", mgmt_defaults.get("logical_id", 76) + ) + physical_id = mgmt_overrides.get( + "physical_id", mgmt_defaults.get("physical_id", 513) + ) + pm_key = (f"PORT_ID: {logical_id}",) + pm_value = (f"PC_PHYS_PORT_ID: {physical_id}",) + self.values["PC_PORT_PHYS_MAP"][pm_key] = pm_value + + def _generate_lane_map(self) -> None: + """Generate lane map entries in PC_PM_CORE.""" + static_mapping = self.parser.get_static_mapping().get_static_mapping() + lane_maps = static_mapping.phy_lane_map + + for core_id, lane_info in lane_maps.items(): + tx_lane_info, rx_lane_info = lane_info.tx_lane_info, lane_info.rx_lane_info + pm_key = (f"PC_PM_ID: {core_id + 1}", "CORE_INDEX: 0") + pm_value = ( + f"RX_LANE_MAP: {self.get_lane_map_str(list(rx_lane_info))}", + "RX_LANE_MAP_AUTO: 0", + f"TX_LANE_MAP: {self.get_lane_map_str(list(tx_lane_info))}", + "TX_LANE_MAP_AUTO: 0", + ) + pm_value = self.values["PC_PM_CORE"].get(pm_key, ()) + pm_value + self.values["PC_PM_CORE"][pm_key] = pm_value + + def _generate_polarity_map(self) -> None: + """Generate polarity map entries in PC_PM_CORE.""" + static_mapping = self.parser.get_static_mapping().get_static_mapping() + pn_swap_maps = static_mapping.polarity_swap_map + + for core_id, swap_map in pn_swap_maps.items(): + tx_swap_map, rx_swap_map = swap_map.tx_lane_info, swap_map.rx_lane_info + pm_key = (f"PC_PM_ID: {core_id + 1}", "CORE_INDEX: 0") + pm_value = ( + f"RX_POLARITY_FLIP: {self.get_polarity_map_str(list(rx_swap_map))}", + "RX_POLARITY_FLIP_AUTO: 0", + f"TX_POLARITY_FLIP: {self.get_polarity_map_str(list(tx_swap_map))}", + "TX_POLARITY_FLIP_AUTO: 0", + ) + pm_value = self.values["PC_PM_CORE"].get(pm_key, ()) + pm_value + self.values["PC_PM_CORE"][pm_key] = pm_value + + def _generate_port_config(self, mgmt_port: bool = False) -> None: + """Generate PC_PORT and PORT entries.""" + port_config = self.variant_config.get("port_config", {}) + cpu_port_config = self.variant_config.get("cpu_port", {}) + mgmt_port_config = self.variant_config.get("mgmt_port", {}) + mgmt_defaults = self.asic_config.get("mgmt_port_defaults", {}) + mgmt_overrides = self.variant_config.get("mgmt_port_overrides", {}) + + default_speed = port_config.get("default_speed", 400000) + speed_to_fec = port_config.get("speed_to_fec", {}) + + cpu_speed = cpu_port_config.get("speed", 10000) + cpu_num_lanes = cpu_port_config.get("num_lanes", 1) + self.values["PC_PORT"][(f"PORT_ID: {0}",)] = ( + "ENABLE: 1", + f"SPEED: {cpu_speed}", + f"NUM_LANES: {cpu_num_lanes}", + ) + + fec = speed_to_fec.get(str(default_speed), "PC_FEC_RS544_2XN") + fp_enable = port_config.get("enable", 0) + port_ranges = [ + f"[{lp_map[0]}, {lp_map[0]}]" + for lp_map in self._get_logical_port_to_physical_port_mapping() + ] + port_ranges_str = ", ".join(port_ranges) + pc_key = (f"PORT_ID: [{port_ranges_str}]",) + pc_value = ( + f"ENABLE: {fp_enable}", + f"SPEED: {default_speed}", + f"NUM_LANES: {self.lanes_per_port}", + f"FEC_MODE: {fec}", + f"MAX_FRAME_SIZE: {self.mmu_size}", + ) + self.values["PC_PORT"][pc_key] = pc_value + + if mgmt_port and mgmt_port_config.get("enabled", False): + logical_id = mgmt_overrides.get( + "logical_id", mgmt_defaults.get("logical_id", 76) + ) + mgmt_speed = mgmt_overrides.get("speed", mgmt_defaults.get("speed", 100000)) + mgmt_num_lanes = mgmt_overrides.get( + "num_lanes", mgmt_defaults.get("num_lanes", 4) + ) + mgmt_fec = mgmt_overrides.get( + "fec", mgmt_defaults.get("fec", "PC_FEC_RS528") + ) + mgmt_enable = mgmt_port_config.get("enable", 0) + + mgmt_port_key = (f"PORT_ID: {logical_id}",) + mgmt_port_value = ( + f"ENABLE: {mgmt_enable}", + f"SPEED: {mgmt_speed}", + f"NUM_LANES: {mgmt_num_lanes}", + f"FEC_MODE: {mgmt_fec}", + f"MAX_FRAME_SIZE: {self.mmu_size}", + ) + self.values["PC_PORT"][mgmt_port_key] = mgmt_port_value + + # Optionally include the mgmt port in the PORT (MTU) range alongside FP. + if mgmt_port_config.get("in_port_block", False): + port_ranges.append(f"[{logical_id}, {logical_id}]") + port_ranges_str = ", ".join(port_ranges) + pc_key = (f"PORT_ID: [{port_ranges_str}]",) + + self.values["PORT"][pc_key] = ( + f"MTU: {self.mmu_size}", + "MTU_CHECK: 1", + ) + + def _generate_device_config_overrides(self) -> None: + """Apply device config overrides from variant config.""" + self.values["DEVICE_CONFIG"].update( + self.variant_config.get("device_config_overrides", {}) + ) + + def generate(self) -> str: + """Build the full ASIC config and return it as a YAML string. + + The XGS layering defines a nine-step priority order. Later steps + override earlier steps when a key appears in more than one source: + + 1. Pass-through settings (named data blocks routed to output tables). + 2. OCP SAI common. + 3. ASIC global defaults. + 4. Vendor SDK common. + 5. Vendor SAI common (with skip_from_sai_common filtering). + 6. ASIC SAI overrides, then platform-level SAI overrides. + 7. Conditional settings (feature toggles). + 8. Device config overrides (per-variant DEVICE_CONFIG entries). + 9. Lane and polarity maps from platform_mapping_v2 (into PC_PM_CORE). + + The numbering describes *priority*, not execution order. The layered + global settings (steps 2-6) are applied before pass-through settings + so keys that both contribute to the ``global`` dict appear in a + deterministic positional order. Pass-through-only tables (PORT_CONFIG, + FP_CONFIG, TM_THD_CONFIG, CTR_EFLEX_CONFIG) are disjoint from the + global layering and unaffected by this choice. + + Port mapping and port-config generation target disjoint tables + (PC_PORT_PHYS_MAP, PC_PORT, PORT) and stand outside the nine-step + priority order. + """ + self._apply_layered_global_settings() + self._apply_pass_through_settings() + self._apply_conditional_settings() + self._generate_device_config_overrides() + self._generate_logical_port_to_physical_port_mapping(mgmt_port=True) + self._generate_port_config(mgmt_port=True) + self._generate_lane_map() + self._generate_polarity_map() + + return self._generate_yaml_string() + + def _generate_yaml_string(self) -> str: + """Render the populated tables as a multi-document YAML string.""" + table_names = self.asic_config.get("table_names", []) + unit = 0 + tables = [] + + for table_name in table_names: + if table_name.startswith( + ( + "PC_", + "PORT_CONFIG", + "TM_THD_CONFIG", + "PORT", + "DEVICE_CONFIG", + "FP_CONFIG", + "CTR_EFLEX_CONFIG", + "DLB_ECMP_CONFIG", + ) + ): + device = "device" + else: + device = "bcm_device" + + if table_value := self.values.get(table_name): + tables.append({device: {unit: {table_name: table_value}}}) + + yaml_str = yaml.dump_all( + tables, sort_keys=False, explicit_start=True, explicit_end=True + ) + + # Strip tuple tags and bullet-style indentation introduced by yaml.dump + # so the output matches the legacy format produced by asic_config_v2. + yaml_str = yaml_str.replace(" !!python/tuple", "") + yaml_str = yaml_str.replace("- ", " ") + yaml_str = yaml_str.replace("'", "") + + preamble_str = "" + if self.asic_config.get("preamble_support", False): + preamble_file = self.variant_config.get("preamble_file") + if preamble_file: + preamble_str = self._read_preamble(preamble_file) + + return preamble_str + yaml_str diff --git a/fboss/lib/asic_config_v3/platforms/icecube800banw/asic_config.json b/fboss/lib/asic_config_v3/platforms/icecube800banw/asic_config.json new file mode 100644 index 0000000000000..2467467229142 --- /dev/null +++ b/fboss/lib/asic_config_v3/platforms/icecube800banw/asic_config.json @@ -0,0 +1,67 @@ +{ + "platform_name": "icecube800banw", + "vendor": "broadcom", + "asic": "tomahawk6", + "num_ports_per_core": 2, + "defaults": { + "asic_config_params": { + "config_type": "YAML_CONFIG", + "exact_match": false, + "mmu_lossless": false, + "config_gen_type": "DEFAULT" + }, + "port_config": { + "default_speed": 800000, + "speed_to_fec": { + "100000": "PC_FEC_RS544_2XN", + "200000": "PC_FEC_RS544_2XN_IEEE", + "400000": "PC_FEC_RS544_2XN_IEEE", + "800000": "PC_FEC_RS544_2XN_IEEE" + } + }, + "cpu_port": { + "speed": 10000, + "num_lanes": 1 + }, + "mgmt_port": { + "enabled": true + }, + "mgmt_port_overrides": { + "logical_id": 268, + "physical_id": 514 + }, + "port_mapping_overrides": { + "num_logical_ports_per_datapath": 16, + "lp_start_step_offset": 2, + "lp_offset_simple": true + }, + "platform_sai_overrides": { + "l3_ecmp_member_secondary_mem_size": "8192", + "sai_port_phy_time_sync_en": "0", + "sai_eapol_trap_use_bcast_mac": "1", + "sai_stats_disable_mask": "0x800" + }, + "features": { + "generate_autoload_board_settings": true + }, + "conditional_settings": [ + { + "name": "prod_stat_custom_receive0", + "description": "Emits stat_custom_receive0_management_mode when running with the DEFAULT generation profile.", + "condition": { + "source": "asic_config_params", + "param": "config_gen_type", + "equals": "DEFAULT" + }, + "apply": { + "global": { + "stat_custom_receive0_management_mode": 1 + } + } + } + ] + }, + "variants": { + "default": {} + } +} diff --git a/fboss/lib/asic_config_v3/platforms/icecube800bc/asic_config.json b/fboss/lib/asic_config_v3/platforms/icecube800bc/asic_config.json new file mode 100644 index 0000000000000..c9678a331a2d0 --- /dev/null +++ b/fboss/lib/asic_config_v3/platforms/icecube800bc/asic_config.json @@ -0,0 +1,44 @@ +{ + "platform_name": "icecube800bc", + "vendor": "broadcom", + "asic": "tomahawk6", + "num_ports_per_core": 2, + "defaults": { + "asic_config_params": { + "config_type": "YAML_CONFIG", + "exact_match": false, + "mmu_lossless": false, + "config_gen_type": "DEFAULT" + }, + "port_config": { + "default_speed": 800000, + "speed_to_fec": { + "100000": "PC_FEC_RS544", + "200000": "PC_FEC_RS544_2XN", + "400000": "PC_FEC_RS544_2XN", + "800000": "PC_FEC_RS544_2XN" + } + }, + "cpu_port": { + "speed": 10000, + "num_lanes": 1 + }, + "mgmt_port": { + "enabled": true, + "speed_variants": { + "100000": { + "num_lanes": 4, + "fec": "PC_FEC_RS528" + } + } + }, + "features": { + "generate_dlb_config": false, + "generate_low_clock_freq_settings": false, + "generate_autoload_board_settings": true + } + }, + "variants": { + "default": {} + } +} diff --git a/fboss/lib/asic_config_v3/platforms/montblanc/asic_config.json b/fboss/lib/asic_config_v3/platforms/montblanc/asic_config.json new file mode 100644 index 0000000000000..4776e58609512 --- /dev/null +++ b/fboss/lib/asic_config_v3/platforms/montblanc/asic_config.json @@ -0,0 +1,56 @@ +{ + "platform_name": "montblanc", + "vendor": "broadcom", + "asic": "tomahawk5", + "num_ports_per_core": 2, + "defaults": { + "asic_config_params": { + "config_type": "YAML_CONFIG", + "exact_match": false, + "mmu_lossless": false, + "config_gen_type": "DEFAULT" + }, + "port_config": { + "default_speed": 400000, + "speed_to_fec": { + "100000": "PC_FEC_RS544", + "200000": "PC_FEC_RS544_2XN", + "400000": "PC_FEC_RS544_2XN", + "800000": "PC_FEC_RS544_2XN" + } + }, + "cpu_port": { + "speed": 10000, + "num_lanes": 1 + }, + "mgmt_port": { + "enabled": true, + "speed_variants": { + "100000": { + "num_lanes": 4, + "fec": "PC_FEC_RS528" + }, + "10000": { + "num_lanes": 1, + "fec": "PC_FEC_NONE", + "pmd_firmware": { + "MEDIUM_TYPE_AUTO": 0, + "MEDIUM_TYPE": "PC_PHY_MEDIUM_COPPER" + } + } + } + }, + "device_config_overrides": { + "CORE_CLK_FREQ": "CLK_1125MHZ", + "PP_CLK_FREQ": "CLK_675MHZ" + }, + "features": { + "generate_dlb_config": true, + "generate_low_clock_freq_settings": true, + "generate_autoload_board_settings": true + } + }, + "variants": { + "base": {} + } +} diff --git a/fboss/lib/asic_config_v3/platforms/wedge800bact/asic_config.json b/fboss/lib/asic_config_v3/platforms/wedge800bact/asic_config.json new file mode 100644 index 0000000000000..8c51e829df4c5 --- /dev/null +++ b/fboss/lib/asic_config_v3/platforms/wedge800bact/asic_config.json @@ -0,0 +1,56 @@ +{ + "platform_name": "wedge800bact", + "vendor": "broadcom", + "asic": "tomahawk5", + "num_ports_per_core": 1, + "defaults": { + "asic_config_params": { + "config_type": "YAML_CONFIG", + "exact_match": false, + "mmu_lossless": false, + "config_gen_type": "DEFAULT" + }, + "port_config": { + "default_speed": 800000, + "speed_to_fec": { + "100000": "PC_FEC_RS544", + "200000": "PC_FEC_RS544_2XN", + "400000": "PC_FEC_RS544_2XN", + "800000": "PC_FEC_RS544_2XN" + } + }, + "cpu_port": { + "speed": 10000, + "num_lanes": 1 + }, + "mgmt_port": { + "enabled": true, + "speed_variants": { + "100000": { + "num_lanes": 4, + "fec": "PC_FEC_RS528" + }, + "10000": { + "num_lanes": 1, + "fec": "PC_FEC_NONE", + "pmd_firmware": { + "MEDIUM_TYPE_AUTO": 0, + "MEDIUM_TYPE": "PC_PHY_MEDIUM_COPPER" + } + } + } + }, + "port_mapping_overrides": { + "core_range": "0-15,48-63", + "num_lp_ports_on_even_core": 4 + }, + "features": { + "generate_dlb_config": true, + "generate_low_clock_freq_settings": false, + "generate_autoload_board_settings": true + } + }, + "variants": { + "base": {} + } +} diff --git a/fboss/lib/asic_config_v3/run-helper.sh b/fboss/lib/asic_config_v3/run-helper.sh new file mode 100755 index 0000000000000..d42d68d7ddb80 --- /dev/null +++ b/fboss/lib/asic_config_v3/run-helper.sh @@ -0,0 +1,5 @@ +#!/bin/bash +python3 fboss/lib/oss/run-helper.py \ + --target fboss-asic-config-v3-gen.GEN_PY_EXE \ + --extra-cmake-defines='{"RANGE_V3_TESTS": "OFF"}' \ + "$@" diff --git a/fboss/lib/asic_config_v3/schemas/broadcom_xgs_asic_config.schema.json b/fboss/lib/asic_config_v3/schemas/broadcom_xgs_asic_config.schema.json new file mode 100644 index 0000000000000..81677ac2cd1a1 --- /dev/null +++ b/fboss/lib/asic_config_v3/schemas/broadcom_xgs_asic_config.schema.json @@ -0,0 +1,271 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Broadcom XGS ASIC Config", + "description": "Per-ASIC configuration defining the chip's port architecture, output table layout, and SDK / SAI default settings used by the asic_config_v3 generator.", + "type": "object", + "required": [ + "vendor", + "asic", + "port_architecture", + "mmu_size", + "table_names", + "mgmt_port_defaults", + "preamble_support", + "global_defaults", + "sai_overrides", + "flex_counter_settings", + "ctr_eflex_config", + "dlb_defaults", + "port_config_defaults", + "fp_config_defaults", + "tm_thd_config_defaults", + "pass_through_settings", + "conditional_settings" + ], + "additionalProperties": false, + "properties": { + "vendor": { + "type": "string", + "description": "ASIC vendor identifier (e.g. \"broadcom\")." + }, + "asic": { + "type": "string", + "description": "ASIC chip identifier (e.g. \"tomahawk5\", \"tomahawk6\")." + }, + "port_architecture": { + "type": "object", + "description": "Core, lane, and datapath layout consumed by the logical-to-physical port mapping logic.", + "required": [ + "num_cores", + "num_lanes_per_core", + "num_logical_ports_per_datapath", + "num_physical_ports_per_datapath", + "num_lp_ports_on_even_core_default" + ], + "additionalProperties": false, + "properties": { + "num_cores": { + "type": "integer", + "description": "Total number of MAC cores on the ASIC." + }, + "num_lanes_per_core": { + "type": "integer", + "enum": [4, 8], + "description": "Number of physical SerDes lanes per core." + }, + "num_logical_ports_per_datapath": { + "type": "integer", + "description": "Number of logical ports per datapath. Used to derive logical port identifiers." + }, + "num_physical_ports_per_datapath": { + "type": "integer", + "description": "Number of physical ports per datapath. Used to derive physical port identifiers." + }, + "num_lp_ports_on_even_core_default": { + "type": "integer", + "description": "Default number of logical ports placed on even-numbered cores. May be overridden per platform variant." + } + } + }, + "mmu_size": { + "type": "integer", + "description": "Memory Management Unit (MMU) buffer size in bytes. Also used as the MAX_FRAME_SIZE and PORT MTU values." + }, + "table_names": { + "type": "array", + "description": "Ordered list of output YAML table names. Controls which tables appear in the generated config and the order in which they are emitted.", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "mgmt_port_defaults": { + "type": "object", + "description": "Default settings for the management (out-of-band) port.", + "required": ["logical_id", "physical_id", "speed", "num_lanes", "fec"], + "additionalProperties": false, + "properties": { + "logical_id": { + "type": "integer", + "description": "Logical port identifier for the management port." + }, + "physical_id": { + "type": "integer", + "description": "Physical port identifier for the management port." + }, + "speed": { + "type": "integer", + "description": "Default port speed in Mbps (e.g. 100000 for 100 GbE)." + }, + "num_lanes": { + "type": "integer", + "description": "Number of physical lanes used by the management port." + }, + "fec": { + "type": "string", + "description": "Forward Error Correction (FEC) mode (e.g. \"PC_FEC_RS528\")." + } + } + }, + "preamble_support": { + "type": "boolean", + "description": "Whether this ASIC supports a preamble section prepended to the output YAML. When false, any preamble_file declared by a platform variant is ignored." + }, + "global_defaults": { + "type": "object", + "description": "ASIC-specific SDK global settings copied verbatim into the \"global\" output table.", + "additionalProperties": { + "type": ["integer", "string"] + } + }, + "sai_overrides": { + "type": "object", + "description": "ASIC-specific SAI settings layered on top of the vendor sai_common values in the \"global\" output table.", + "additionalProperties": { + "type": ["integer", "string"] + } + }, + "flex_counter_settings": { + "type": "object", + "description": "Flex counter reservation settings. Routed to an output table via pass_through_settings.", + "additionalProperties": { + "type": "integer" + } + }, + "ctr_eflex_config": { + "type": "object", + "description": "Enhanced flex counter settings emitted into the CTR_EFLEX_CONFIG output table. Routed via pass_through_settings.", + "additionalProperties": { + "type": "integer" + } + }, + "dlb_defaults": { + "type": "object", + "description": "Dynamic Load Balancing (DLB) settings applied conditionally via conditional_settings (typically gated by the generate_dlb_config feature).", + "additionalProperties": { + "type": ["integer", "string"] + } + }, + "dlb_ecmp_config_defaults": { + "type": "object", + "description": "Defaults for the DLB_ECMP_CONFIG output table. Applied conditionally via conditional_settings, gated by the generate_dlb_ecmp_config feature.", + "additionalProperties": { + "type": ["integer", "string"] + } + }, + "pass_through_settings": { + "type": "array", + "description": "Declarative list of data blocks copied to output tables after the layered global settings have been applied.", + "items": { + "type": "object", + "required": ["source", "target_table"], + "additionalProperties": false, + "properties": { + "source": { + "type": "string", + "description": "Key of the data block in this ASIC config to copy from." + }, + "target_table": { + "type": "string", + "description": "Output table name to copy the settings into." + } + } + } + }, + "port_config_defaults": { + "type": "object", + "description": "Default values for the PORT_CONFIG output table. Routed via pass_through_settings.", + "additionalProperties": { + "type": ["integer", "string"] + } + }, + "fp_config_defaults": { + "type": "object", + "description": "Default values for the FP_CONFIG output table. Routed via pass_through_settings.", + "additionalProperties": { + "type": ["integer", "string"] + } + }, + "tm_thd_config_defaults": { + "type": "object", + "description": "Default values for the TM_THD_CONFIG output table. Routed via pass_through_settings.", + "additionalProperties": { + "type": ["integer", "string"] + } + }, + "conditional_settings": { + "type": "array", + "description": "Declarative conditional settings. Each entry is evaluated against the variant config; matching entries are applied to the named output tables.", + "items": { + "$ref": "#/$defs/conditional_setting" + } + } + }, + "$defs": { + "conditional_setting": { + "type": "object", + "required": ["name", "condition"], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "Identifier for this conditional setting; must be unique within the surrounding list." + }, + "description": { + "type": "string", + "description": "Free-form description of the conditional setting and the configuration it produces." + }, + "condition": { + "type": "object", + "required": ["source", "param", "equals"], + "additionalProperties": false, + "properties": { + "source": { + "type": "string", + "enum": ["asic_config_params", "features"], + "description": "Variant-config section holding the parameter to evaluate." + }, + "param": { + "type": "string", + "description": "Name of the parameter to compare." + }, + "equals": { + "description": "Expected value; the condition is satisfied when the parameter equals this value." + } + } + }, + "apply": { + "type": "object", + "description": "Inline settings applied when the condition is satisfied. Keyed by target output table name.", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": ["integer", "string"] + } + } + }, + "apply_from": { + "type": "object", + "description": "Reference to a data block elsewhere in this ASIC config. The named source block is copied into the named target table when the condition is satisfied.", + "required": ["source", "target_table"], + "additionalProperties": false, + "properties": { + "source": { + "type": "string" + }, + "target_table": { + "type": "string" + } + } + }, + "skip_from_sai_common": { + "type": "array", + "description": "Keys to omit from the vendor sai_common block when this condition is satisfied.", + "items": { + "type": "string" + } + } + } + } + } +} diff --git a/fboss/lib/asic_config_v3/schemas/platform_config.schema.json b/fboss/lib/asic_config_v3/schemas/platform_config.schema.json new file mode 100644 index 0000000000000..386cd710272d3 --- /dev/null +++ b/fboss/lib/asic_config_v3/schemas/platform_config.schema.json @@ -0,0 +1,267 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Platform Config", + "description": "Per-platform configuration declaring the vendor, ASIC, port layout, and one or more configuration variants for the asic_config_v3 generator.", + "type": "object", + "required": ["platform_name", "vendor", "asic", "variants"], + "additionalProperties": true, + "properties": { + "platform_name": { + "type": "string", + "description": "Platform identifier. Must match the directory name under platforms/." + }, + "vendor": { + "type": "string", + "description": "ASIC vendor identifier. Must match a directory name under vendors/." + }, + "asic": { + "type": "string", + "description": "ASIC chip identifier. Must match the basename of a JSON file under vendors//asics/." + }, + "num_ports_per_core": { + "type": "integer", + "description": "Number of front-panel ports per ASIC core. Combined with num_lanes_per_core to derive the number of lanes per port, unless num_lanes_per_port is set explicitly." + }, + "num_lanes_per_port": { + "type": "integer", + "description": "Optional. Explicit override of the lanes-per-port value derived from num_lanes_per_core / num_ports_per_core. Used by platforms that wire only a subset of the ASIC lanes per port." + }, + "defaults": { + "$ref": "#/$defs/variant", + "description": "Shared variant fields inherited by every variant via deep merge. Values declared inside a specific variant override the corresponding entries in this block." + }, + "variants": { + "type": "object", + "description": "Named configuration variants for this platform. Each variant produces a separate output config file.", + "minProperties": 1, + "additionalProperties": { + "$ref": "#/$defs/variant" + } + } + }, + "$defs": { + "variant": { + "type": "object", + "description": "A single configuration variant. Fields may be omitted when their value is inherited from the platform-level defaults block.", + "additionalProperties": true, + "properties": { + "asic_config_params": { + "type": "object", + "description": "Parameters controlling generator behavior for this variant.", + "additionalProperties": true, + "properties": { + "config_type": { + "type": "string", + "enum": ["YAML_CONFIG", "JSON_CONFIG"], + "description": "Output configuration format." + }, + "exact_match": { + "type": "boolean", + "description": "When true, enables exact-match forwarding table entries." + }, + "mmu_lossless": { + "type": "boolean", + "description": "When true, enables MMU lossless mode for PFC / RDMA workloads." + }, + "config_gen_type": { + "type": "string", + "enum": ["DEFAULT", "HW_TEST"], + "description": "Configuration generation profile. Controls which vendor configuration profile is applied." + } + } + }, + "port_config": { + "type": "object", + "description": "Front-panel port settings. Fields may be omitted in a variant when inherited from the platform defaults.", + "additionalProperties": false, + "properties": { + "default_speed": { + "type": "integer", + "description": "Default port speed in Mbps (e.g. 400000 for 400 GbE)." + }, + "speed_to_fec": { + "type": "object", + "description": "Mapping from port speed (encoded as a decimal string) to FEC mode.", + "additionalProperties": { + "type": "string" + } + }, + "enable": { + "type": "integer", + "description": "Optional. Value emitted as PC_PORT.ENABLE for front-panel ports. Defaults to 0." + }, + "link_training": { + "type": "integer", + "description": "Optional. When set, emits LINK_TRAINING: on front-panel PC_PORT entries." + } + } + }, + "cpu_port": { + "type": "object", + "description": "CPU port (logical port 0) settings.", + "required": ["speed", "num_lanes"], + "additionalProperties": false, + "properties": { + "speed": { + "type": "integer", + "description": "CPU port speed in Mbps." + }, + "num_lanes": { + "type": "integer", + "description": "Number of lanes used by the CPU port." + } + } + }, + "mgmt_port": { + "type": "object", + "description": "Management port settings. Fields may be omitted in a variant when inherited from the platform defaults.", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether the management port is emitted in the generated config." + }, + "enable": { + "type": "integer", + "description": "Optional. Value emitted as PC_PORT.ENABLE for the management port. Defaults to 0." + }, + "in_port_block": { + "type": "boolean", + "description": "Optional. When true, includes the management port in the PORT (MTU) range alongside the front-panel ports." + }, + "speed_variants": { + "type": "object", + "description": "Per-speed management port settings, keyed by port speed (encoded as a decimal string).", + "additionalProperties": { + "$ref": "#/$defs/mgmt_speed_config" + } + } + } + }, + "device_config_overrides": { + "type": "object", + "description": "Optional. Key / value pairs written to the DEVICE_CONFIG output table (for example, clock frequencies).", + "additionalProperties": { + "type": "string" + } + }, + "port_mapping_overrides": { + "type": "object", + "description": "Optional. Overrides for the logical-to-physical port mapping formula.", + "additionalProperties": false, + "properties": { + "core_range": { + "type": "string", + "description": "Comma-separated list of inclusive core ranges to include in the port mapping (e.g. \"0-15,48-63\"). Defaults to all cores on the ASIC." + }, + "num_lp_ports_on_even_core": { + "type": "integer", + "description": "Override for the number of logical ports placed on even-numbered cores." + }, + "num_logical_ports_per_datapath": { + "type": "integer", + "description": "Override for the ASIC-level num_logical_ports_per_datapath value." + }, + "lp_start_step_offset": { + "type": "integer", + "description": "Offset added to num_logical_ports_per_datapath when computing the per-datapath logical-port start. Defaults to 1." + }, + "lp_offset_simple": { + "type": "boolean", + "description": "When true, computes the per-port logical-port offset as i * lanes_per_port on all cores rather than using the default even / odd conditional." + }, + "core_offset_apply_all": { + "type": "boolean", + "description": "When true, applies the +1 core offset to every core rather than only cores 0 and 1. Required for platforms with a linear-stride port layout." + } + } + }, + "mgmt_port_overrides": { + "type": "object", + "description": "Optional. Per-platform overrides of the ASIC-level mgmt_port_defaults.", + "additionalProperties": false, + "properties": { + "logical_id": {"type": "integer"}, + "physical_id": {"type": "integer"}, + "speed": {"type": "integer"}, + "num_lanes": {"type": "integer"}, + "fec": {"type": "string"} + } + }, + "platform_sai_overrides": { + "type": "object", + "description": "Optional. Platform-level SAI settings layered on top of the ASIC sai_overrides. These take precedence within the global-table layering.", + "additionalProperties": { + "type": ["integer", "string"] + } + }, + "conditional_settings": { + "type": "array", + "description": "Optional. Platform-level conditional settings, evaluated after the ASIC-level conditional_settings so that platform values take precedence.", + "items": {"type": "object"} + }, + "features": { + "type": "object", + "description": "Boolean feature toggles enabling optional config sections. All fields are optional; absent flags default to disabled.", + "additionalProperties": false, + "properties": { + "generate_dlb_config": { + "type": "boolean", + "description": "When true, includes Dynamic Load Balancing settings from the ASIC dlb_defaults block." + }, + "generate_low_clock_freq_settings": { + "type": "boolean", + "description": "When true, includes low-clock-frequency device settings." + }, + "generate_autoload_board_settings": { + "type": "boolean", + "description": "When true, emits AUTOLOAD_BOARD_SETTINGS: 0 in the DEVICE_CONFIG output table." + }, + "generate_dlb_ecmp_config": { + "type": "boolean", + "description": "When true, emits the DLB_ECMP_CONFIG output table from the ASIC dlb_ecmp_config_defaults block." + } + } + }, + "preamble_file": { + "type": "string", + "description": "Optional. Path (relative to the asic_config_v3 module root) of a preamble file prepended to the generated YAML. Honored only when the ASIC declares preamble_support: true." + }, + "platform_mapping_name": { + "type": "string", + "description": "Optional. Overrides the platform_mapping_v2 directory consulted for lane-map and polarity data. Defaults to platform_name. Used when multiple variants share an asic_config but consume sibling platform_mapping_v2 directories." + }, + "ctr_eflex_config": { + "type": "object", + "description": "Optional. Variant-level override of the ASIC ctr_eflex_config pass-through block. When present, replaces the ASIC-level value in its entirety.", + "additionalProperties": { + "type": ["integer", "string"] + } + } + } + }, + "mgmt_speed_config": { + "type": "object", + "description": "Per-speed management port configuration.", + "required": ["num_lanes", "fec"], + "additionalProperties": false, + "properties": { + "num_lanes": { + "type": "integer", + "description": "Number of physical lanes used at this speed." + }, + "fec": { + "type": "string", + "description": "FEC mode applied at this speed." + }, + "pmd_firmware": { + "type": "object", + "description": "Optional. PMD firmware overrides applied at this speed.", + "additionalProperties": { + "type": ["integer", "string"] + } + } + } + } + } +} diff --git a/fboss/lib/asic_config_v3/schemas/vendor_common.schema.json b/fboss/lib/asic_config_v3/schemas/vendor_common.schema.json new file mode 100644 index 0000000000000..2dc883cd5e19d --- /dev/null +++ b/fboss/lib/asic_config_v3/schemas/vendor_common.schema.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Vendor Common Config", + "description": "Vendor-level common settings (sdk_common.json and sai_common.json). All key-value pairs under 'global' are pass-through settings copied to the output.", + "type": "object", + "required": ["global"], + "additionalProperties": false, + "properties": { + "global": { + "type": "object", + "description": "Pass-through SDK or SAI settings copied to the 'global' output table.", + "additionalProperties": { + "type": "string" + } + } + } +} diff --git a/fboss/lib/asic_config_v3/validation/validate_asic_configs.py b/fboss/lib/asic_config_v3/validation/validate_asic_configs.py new file mode 100755 index 0000000000000..b26e834918bd7 --- /dev/null +++ b/fboss/lib/asic_config_v3/validation/validate_asic_configs.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 + +""" +Validate ASIC config JSON files against their JSON schemas. + +Usage: + python3 validate_asic_configs.py +""" + +import glob +import json +import os +import sys + +import jsonschema + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +MODULE_DIR = os.path.dirname(SCRIPT_DIR) +SCHEMAS_DIR = os.path.join(MODULE_DIR, "schemas") +VENDORS_DIR = os.path.join(MODULE_DIR, "vendors") +PLATFORMS_DIR = os.path.join(MODULE_DIR, "platforms") + +# Each entry pairs a JSON schema with the set of config files validated against it. +VALIDATION_TARGETS = [ + { + "description": "Vendor common configs", + "schema": "vendor_common.schema.json", + "files_glob": os.path.join(VENDORS_DIR, "**", "*_common.json"), + }, + { + "description": "Broadcom XGS ASIC configs", + "schema": "broadcom_xgs_asic_config.schema.json", + "files_glob": os.path.join( + VENDORS_DIR, "broadcom", "xgs", "asics", "tomahawk*.json" + ), + }, + { + "description": "Platform configs", + "schema": "platform_config.schema.json", + "files_glob": os.path.join(PLATFORMS_DIR, "**", "asic_config.json"), + }, +] + + +def validate(schema, config_path): + with open(config_path) as f: + config = json.load(f) + jsonschema.validate(config, schema) + + +def main(): + passed = 0 + failed = 0 + + for target in VALIDATION_TARGETS: + schema_path = os.path.join(SCHEMAS_DIR, target["schema"]) + with open(schema_path) as f: + schema = json.load(f) + + files = sorted(glob.glob(target["files_glob"], recursive=True)) + + print(f"{target['description']} ({target['schema']}):") + + if not files: + print(" No files found.") + continue + + for path in files: + name = os.path.relpath(path, MODULE_DIR) + try: + validate(schema, path) + print(f" {name}: PASSED") + passed += 1 + except jsonschema.ValidationError as e: + print(f" {name}: FAILED - {e.message}") + failed += 1 + + print() + + print(f"{passed} passed, {failed} failed") + sys.exit(1 if failed else 0) + + +if __name__ == "__main__": + main() diff --git a/fboss/lib/asic_config_v3/vendors/broadcom/xgs/asics/tomahawk5.json b/fboss/lib/asic_config_v3/vendors/broadcom/xgs/asics/tomahawk5.json new file mode 100644 index 0000000000000..fec043478decc --- /dev/null +++ b/fboss/lib/asic_config_v3/vendors/broadcom/xgs/asics/tomahawk5.json @@ -0,0 +1,141 @@ +{ + "vendor": "broadcom", + "asic": "tomahawk5", + + "port_architecture": { + "num_cores": 64, + "num_lanes_per_core": 8, + "num_logical_ports_per_datapath": 10, + "num_physical_ports_per_datapath": 16, + "num_lp_ports_on_even_core_default": 8 + }, + + "mmu_size": 9416, + "table_names": [ + "PC_PM_CORE", + "PC_PORT_PHYS_MAP", + "PC_PORT", + "PORT_CONFIG", + "global", + "port", + "TM_THD_CONFIG", + "PORT", + "DEVICE_CONFIG", + "FP_CONFIG", + "CTR_EFLEX_CONFIG", + "PC_PMD_FIRMWARE", + "DLB_ECMP_CONFIG" + ], + "mgmt_port_defaults": { + "logical_id": 76, + "physical_id": 513, + "speed": 100000, + "num_lanes": 4, + "fec": "PC_FEC_RS528" + }, + "preamble_support": true, + + "global_defaults": { + "l3_alpm_template": 1, + "l3_alpm_hit_mode": 1, + "ipv6_lpm_128b_enable": 1, + "pktio_driver_type": 1, + "qos_map_multi_get_mode": 1, + "rx_cosq_mapping_management_mode": 0, + "l3_iif_reservation_skip": 0 + }, + "sai_overrides": { + "sai_field_group_auto_prioritize": 1, + "bcm_tunnel_term_compatible_mode": 1, + "sai_l2_cpu_fdb_event_suppress": 1, + "sai_port_phy_time_sync_en": 1, + "sai_stats_support_mask": "0x802", + "sai_disable_internal_port_serdes": 1, + "stat_custom_receive0_management_mode": 1, + "sai_stats_disable_mask": "0x800" + }, + "flex_counter_settings": { + "global_flexctr_ing_action_num_reserved": 20, + "global_flexctr_ing_pool_num_reserved": 8, + "global_flexctr_ing_op_profile_num_reserved": 20, + "global_flexctr_ing_group_num_reserved": 2, + "global_flexctr_egr_action_num_reserved": 8, + "global_flexctr_egr_pool_num_reserved": 5, + "global_flexctr_egr_op_profile_num_reserved": 10, + "global_flexctr_egr_group_num_reserved": 1 + }, + "ctr_eflex_config": { + "CTR_ING_EFLEX_OPERMODE_PIPEUNIQUE": 1, + "CTR_ING_EFLEX_OPERMODE_PIPE_INSTANCE_UNIQUE": 1, + "CTR_EGR_EFLEX_OPERMODE_PIPEUNIQUE": 1, + "CTR_EGR_EFLEX_OPERMODE_PIPE_INSTANCE_UNIQUE": 1 + }, + "dlb_defaults": { + "ecmp_dlb_port_speeds": 1, + "l3_ecmp_member_secondary_mem_size": 4096 + }, + "dlb_ecmp_config_defaults": { + "PATH_QUALITY_PROFILE_MODE": "WIDE" + }, + + "port_config_defaults": {"PORT_SYSTEM_PROFILE_OPERMODE_PIPEUNIQUE": 1}, + "fp_config_defaults": {"FP_ING_OPERMODE": "GLOBAL_PIPE_AWARE"}, + "tm_thd_config_defaults": {"THRESHOLD_MODE": "LOSSY"}, + + "pass_through_settings": [ + {"source": "port_config_defaults", "target_table": "PORT_CONFIG"}, + {"source": "fp_config_defaults", "target_table": "FP_CONFIG"}, + {"source": "tm_thd_config_defaults", "target_table": "TM_THD_CONFIG"}, + {"source": "flex_counter_settings", "target_table": "global"}, + {"source": "ctr_eflex_config", "target_table": "CTR_EFLEX_CONFIG"} + ], + + "conditional_settings": [ + { + "name": "mmu_lossless", + "description": "Enables MMU lossless mode for PFC and RDMA workloads.", + "condition": {"source": "asic_config_params", "param": "mmu_lossless", "equals": true}, + "apply": { + "global": { + "sai_mmu_custom_config": 1, + "sai_rdma_udf_disable": 1, + "sai_l3_byte1_udf_disable": 1, + "clm_enable": 1 + }, + "TM_THD_CONFIG": { + "THRESHOLD_MODE": "LOSSY_AND_LOSSLESS", + "SKIP_BUFFER_RESERVATION": 1 + } + }, + "skip_from_sai_common": ["sai_mmu_qgroups_default", "sai_optimized_mmu"] + }, + { + "name": "exact_match", + "description": "Enables exact-match forwarding table entries.", + "condition": {"source": "asic_config_params", "param": "exact_match", "equals": true}, + "apply": { + "global": {"fpem_mem_entries_width": 1, "fpem_mem_entries": 65536} + } + }, + { + "name": "dlb_config", + "description": "Enables Dynamic Load Balancing settings for ECMP groups.", + "condition": {"source": "features", "param": "generate_dlb_config", "equals": true}, + "apply_from": {"source": "dlb_defaults", "target_table": "global"} + }, + { + "name": "dlb_ecmp_config", + "description": "Emits the DLB_ECMP_CONFIG path-quality profile settings.", + "condition": {"source": "features", "param": "generate_dlb_ecmp_config", "equals": true}, + "apply_from": {"source": "dlb_ecmp_config_defaults", "target_table": "DLB_ECMP_CONFIG"} + }, + { + "name": "autoload_board_settings", + "description": "Disables board auto-loading by setting AUTOLOAD_BOARD_SETTINGS to 0.", + "condition": {"source": "features", "param": "generate_autoload_board_settings", "equals": true}, + "apply": { + "DEVICE_CONFIG": {"AUTOLOAD_BOARD_SETTINGS": 0} + } + } + ] +} diff --git a/fboss/lib/asic_config_v3/vendors/broadcom/xgs/asics/tomahawk6.json b/fboss/lib/asic_config_v3/vendors/broadcom/xgs/asics/tomahawk6.json new file mode 100644 index 0000000000000..56285db0cdbf9 --- /dev/null +++ b/fboss/lib/asic_config_v3/vendors/broadcom/xgs/asics/tomahawk6.json @@ -0,0 +1,131 @@ +{ + "vendor": "broadcom", + "asic": "tomahawk6", + + "port_architecture": { + "num_cores": 64, + "num_lanes_per_core": 8, + "num_logical_ports_per_datapath": 10, + "num_physical_ports_per_datapath": 16, + "num_lp_ports_on_even_core_default": 8 + }, + + "mmu_size": 9416, + "table_names": [ + "PC_PM_CORE", + "PC_PORT_PHYS_MAP", + "PC_PORT", + "PORT_CONFIG", + "global", + "port", + "TM_THD_CONFIG", + "PORT", + "DEVICE_CONFIG", + "FP_CONFIG", + "CTR_EFLEX_CONFIG", + "DLB_ECMP_CONFIG" + ], + "mgmt_port_defaults": { + "logical_id": 76, + "physical_id": 513, + "speed": 100000, + "num_lanes": 4, + "fec": "PC_FEC_RS528" + }, + "preamble_support": false, + + "global_defaults": { + "l3_alpm_template": 1, + "l3_alpm_hit_mode": 1, + "ipv6_lpm_128b_enable": 1, + "pktio_driver_type": 1, + "qos_map_multi_get_mode": 1, + "rx_cosq_mapping_management_mode": 0, + "l3_iif_reservation_skip": 0 + }, + "sai_overrides": { + "sai_field_group_auto_prioritize": 1, + "bcm_tunnel_term_compatible_mode": 1, + "sai_l2_cpu_fdb_event_suppress": 1, + "sai_port_phy_time_sync_en": 1, + "sai_stats_support_mask": "0x2", + "sai_disable_internal_port_serdes": 1 + }, + "flex_counter_settings": { + "global_flexctr_ing_action_num_reserved": 20, + "global_flexctr_ing_pool_num_reserved": 8, + "global_flexctr_ing_op_profile_num_reserved": 20, + "global_flexctr_ing_group_num_reserved": 2, + "global_flexctr_egr_action_num_reserved": 8, + "global_flexctr_egr_pool_num_reserved": 5, + "global_flexctr_egr_op_profile_num_reserved": 10, + "global_flexctr_egr_group_num_reserved": 1 + }, + "ctr_eflex_config": { + "CTR_ING_EFLEX_OPERMODE_PIPEUNIQUE": 1, + "CTR_ING_EFLEX_OPERMODE_PIPE_INSTANCE_UNIQUE": 1, + "CTR_EGR_EFLEX_OPERMODE_PIPEUNIQUE": 1, + "CTR_EGR_EFLEX_OPERMODE_PIPE_INSTANCE_UNIQUE": 1 + }, + "dlb_defaults": { + "ecmp_dlb_port_speeds": 1, + "l3_ecmp_member_secondary_mem_size": 4096 + }, + "dlb_ecmp_config_defaults": { + "PATH_QUALITY_PROFILE_MODE": "WIDE" + }, + + "port_config_defaults": {"PORT_SYSTEM_PROFILE_OPERMODE_PIPEUNIQUE": 1}, + "fp_config_defaults": {"FP_ING_OPERMODE": "GLOBAL_PIPE_AWARE"}, + "tm_thd_config_defaults": {"THRESHOLD_MODE": "LOSSY"}, + + "pass_through_settings": [ + {"source": "port_config_defaults", "target_table": "PORT_CONFIG"}, + {"source": "fp_config_defaults", "target_table": "FP_CONFIG"}, + {"source": "tm_thd_config_defaults", "target_table": "TM_THD_CONFIG"}, + {"source": "flex_counter_settings", "target_table": "global"}, + {"source": "ctr_eflex_config", "target_table": "CTR_EFLEX_CONFIG"} + ], + + "conditional_settings": [ + { + "name": "mmu_lossless", + "description": "Enables MMU lossless mode for PFC and RDMA workloads.", + "condition": {"source": "asic_config_params", "param": "mmu_lossless", "equals": true}, + "apply": { + "TM_THD_CONFIG": { + "THRESHOLD_MODE": "LOSSY_AND_LOSSLESS", + "SKIP_BUFFER_RESERVATION": 1 + } + } + }, + { + "name": "exact_match", + "description": "Enables exact-match forwarding table entries.", + "condition": {"source": "asic_config_params", "param": "exact_match", "equals": true}, + "apply": { + "global": {"fpem_mem_entries_width": 1, "fpem_mem_entries": 65536} + } + }, + { + "name": "dlb_config", + "description": "Enables Dynamic Load Balancing settings for ECMP groups.", + "condition": {"source": "features", "param": "generate_dlb_config", "equals": true}, + "apply_from": {"source": "dlb_defaults", "target_table": "global"} + }, + { + "name": "dlb_ecmp_config", + "description": "Emits the DLB_ECMP_CONFIG path-quality profile settings.", + "condition": {"source": "features", "param": "generate_dlb_ecmp_config", "equals": true}, + "apply_from": {"source": "dlb_ecmp_config_defaults", "target_table": "DLB_ECMP_CONFIG"} + }, + { + "name": "autoload_board_settings", + "description": "Disables board auto-loading by setting AUTOLOAD_BOARD_SETTINGS to 0.", + "condition": {"source": "features", "param": "generate_autoload_board_settings", "equals": true}, + "apply": { + "DEVICE_CONFIG": {"AUTOLOAD_BOARD_SETTINGS": 0} + } + } + ] +} diff --git a/fboss/lib/asic_config_v3/vendors/broadcom/xgs/sai_common.json b/fboss/lib/asic_config_v3/vendors/broadcom/xgs/sai_common.json new file mode 100644 index 0000000000000..337b0eeb6a160 --- /dev/null +++ b/fboss/lib/asic_config_v3/vendors/broadcom/xgs/sai_common.json @@ -0,0 +1,20 @@ +{ + "global": { + "sai_common_hash_crc": "0x1", + "sai_disable_srcmacqedstmac_ctrl": "0x1", + "sai_acl_qset_optimization": "0x1", + "sai_optimized_mmu": "0x1", + "sai_pkt_rx_custom_cfg": "1", + "sai_pkt_rx_pkt_size": "16512", + "sai_pkt_rx_cfg_ppc": "16", + "sai_async_fdb_nbr_enable": "0x1", + "sai_pfc_defaults_disable": "0x1", + "sai_ifp_enable_on_cpu_tx": "0x1", + "sai_vfp_smac_drop_filter_disable": "1", + "sai_macro_flow_based_hash": "1", + "sai_mmu_qgroups_default": "1", + "sai_dis_ctr_incr_on_port_ln_dn": "0", + "custom_feature_mesh_topology_sync_mode": "1", + "sai_ecmp_group_members_increment": "1" + } +} diff --git a/fboss/lib/asic_config_v3/vendors/broadcom/xgs/sdk_common.json b/fboss/lib/asic_config_v3/vendors/broadcom/xgs/sdk_common.json new file mode 100644 index 0000000000000..0135c342b01ed --- /dev/null +++ b/fboss/lib/asic_config_v3/vendors/broadcom/xgs/sdk_common.json @@ -0,0 +1,8 @@ +{ + "global": { + "pcie_host_intf_timeout_purge_enable": "0", + "qos_map_multi_get_mode": "1", + "macro_flow_hash_shuffle_random_seed": "34345645", + "bcm_linkscan_interval": "25000" + } +} diff --git a/fboss/lib/oss/run-helper.py b/fboss/lib/oss/run-helper.py index d271185e3752a..574fb1597191a 100644 --- a/fboss/lib/oss/run-helper.py +++ b/fboss/lib/oss/run-helper.py @@ -13,25 +13,36 @@ """ import argparse +import json import os import subprocess import sys from pathlib import Path -from typing import List, Tuple -def get_command_line_args() -> Tuple[str, List[str]]: +def get_command_line_args() -> tuple[str, dict[str, str], list[str]]: parser = argparse.ArgumentParser( description="OSS FBOSS build and run helper script." ) parser.add_argument("--target", type=str, required=True, help="Target to build") + parser.add_argument( + "--extra-cmake-defines", + type=str, + default="{}", + help=("JSON object of additional cmake defines"), + ) args, unknown_args = parser.parse_known_args() - return args.target, unknown_args + extra_defines = json.loads(args.extra_cmake_defines) + if not isinstance(extra_defines, dict): + print("--extra-cmake-defines must be a JSON object", file=sys.stderr) + sys.exit(1) + + return args.target, extra_defines, unknown_args def main() -> None: - target, command_line_args = get_command_line_args() + target, extra_cmake_defines, command_line_args = get_command_line_args() run_path = os.getcwd() parents = Path(__file__).parents @@ -40,14 +51,14 @@ def main() -> None: "Please run the script from the root of the FBOSS repository", file=sys.stderr, ) - exit(1) + sys.exit(1) expected_path = parents[3].absolute().as_posix() if run_path != expected_path: error_string = f"""Please executed the script from the root of the FBOSS repository Expected run path: {expected_path} Current run path: {run_path}""" print(error_string, file=sys.stderr) - exit(1) + sys.exit(1) get_deps_paths = [ expected_path + "/opensource/fbcode_builder/getdeps.py", @@ -60,7 +71,7 @@ def main() -> None: get_deps_path = maybe_path if get_deps_path is None: print("Could not find getdeps.py", file=sys.stderr) - exit(1) + sys.exit(1) else: print(get_deps_path) @@ -70,11 +81,16 @@ def main() -> None: if is_facebook_machine: get_deps_path = f"{proxy_env_vars} {get_deps_path}" + cmake_defines = {"CMAKE_BUILD_TYPE": "MinSizeRel", "CMAKE_CXX_STANDARD": "20"} + cmake_defines.update(extra_cmake_defines) + cmake_defines_json = json.dumps(cmake_defines) + print(f"Starting build for {target}") subprocess.run( f"""{get_deps_path} build """ - + '--allow-system-packages --num-jobs 32 --extra-cmake-defines=\'{"CMAKE_BUILD_TYPE": "MinSizeRel", "CMAKE_CXX_STANDARD": "20"}\' --cmake-target' + + f"--allow-system-packages --num-jobs 32 --extra-cmake-defines='{cmake_defines_json}' --src-dir {expected_path} --cmake-target" + f" {target} fboss", + check=False, shell=True, ) @@ -82,29 +98,32 @@ def main() -> None: show_build_dir_proc = subprocess.run( f"""{get_deps_path} show-build-dir fboss""", + check=False, shell=True, capture_output=True, text=True, ) - fboss_oss_target = ( - show_build_dir_proc.stdout.rstrip() - + f"/{target}" - + " " - + " ".join(command_line_args) - ) + build_dir = show_build_dir_proc.stdout.rstrip() + target_basename = target.removesuffix(".GEN_PY_EXE") + output_path = f"{build_dir}/{target_basename}" + + # Thrift python output is a directory; everything else is a native executable + python_prefix = "python3 " if os.path.isdir(output_path) else "" + fboss_oss_target = f"{python_prefix}{output_path} {' '.join(command_line_args)}" result = subprocess.run( fboss_oss_target, + check=False, shell=True, ) if result.returncode != 0: print(f"Failed to run target {target}", file=sys.stderr) - exit(1) + sys.exit(1) print("Configs have been written to specified output directory") - exit(0) + sys.exit(0) if __name__ == "__main__":