/* SPDX-License-Identifier: AGPL-3.0-or-later */ /** * @file ft_tt_coord.c * @brief Implementation of logical <-> NoC0 / translated coordinate mapping, * classification and enabled checks, driven entirely by the grid's * architecture table and ARC telemetry masks. * * Translation model (public docs, BlackholeA0/NoC/Coordinates.md): * - Nodes inside the NoC grid keep their raw position in translated space, * EXCEPT the Tensix columns: enabled columns pack, in NoC0 order, onto the * first entries of the full Tensix column list, and harvested columns take the * remaining (top) entries. Rows are never renumbered. * - DRAM, the in-use PCIe tile and Ethernet get alias ranges OUTSIDE the grid * (origins in the arch table). Enabled Ethernet channels pack in channel order; * on a part with a harvested DRAM bank the alias layout is not modelled and the * translated DRAM accessors refuse. * - With translation reported disabled, TRANSLATED is NOC0. * * @status Complete; covered by tests/test_coord.c, tests/test_harvesting.c and * tests/test_arch.c. * @issues None * @todo None */ #include "ft_tt_coord.h" #include #include #include #include "ft_tt_memmap.h" /* ------------------------------------------------------------------------- */ /* Grid validation and mask helpers */ /* ------------------------------------------------------------------------- */ /** Resolve a grid to its architecture: EINVAL for NULL, EUNAVAIL when ARC has * not filled it or it names no architecture. No shape check (check_grid's own front door). */ static int resolve_arch_only(const ft_tt_grid_info *grid, const ft_tt_arch **out_arch) { if (!grid) return FT_TT_EINVAL; if (!grid->valid || !grid->arch) return FT_TT_EUNAVAIL; *out_arch = grid->arch; return FT_TT_OK; } static uint32_t enabled_tensix_columns(const ft_tt_grid_info *grid, const ft_tt_arch *arch); static bool dram_harvest_beyond_limit(const ft_tt_grid_info *grid, const ft_tt_arch *arch); /** * Resolve a grid for a lookup. A grid ft_tt_coord_check_grid() refuses answers NOTHING * (EIO, counts 0, classify NONE): a coordinate derived from an impossible harvest picture * would address a tile that may not exist, and a wrong NoC target can wedge the card. * Silent here; ft_tt_coord_check_grid() is the call that explains the refusal. */ static int resolve_grid_arch(const ft_tt_grid_info *grid, const ft_tt_arch **out_arch) { const ft_tt_arch *arch = NULL; int rc = resolve_arch_only(grid, &arch); if (rc != FT_TT_OK) return rc; if (enabled_tensix_columns(grid, arch) == 0u || dram_harvest_beyond_limit(grid, arch)) return FT_TT_EIO; *out_arch = arch; return FT_TT_OK; } static bool is_known_system(ft_tt_coord_system system) { return system == FT_TT_COORD_NOC0 || system == FT_TT_COORD_TRANSLATED; } /** Shared front door for the grid-based accessors. */ static int begin_grid_lookup(const ft_tt_grid_info *grid, ft_tt_coord_system system, const ft_tt_noc_coord *out_coord, const ft_tt_arch **out_arch) { if (!out_coord || !is_known_system(system)) return FT_TT_EINVAL; return resolve_grid_arch(grid, out_arch); } /** TRANSLATED only means something when firmware enabled translation. */ static bool translation_applies(const ft_tt_grid_info *grid, ft_tt_coord_system system) { return system == FT_TT_COORD_TRANSLATED && grid->noc_translation_enabled != 0u; } static uint32_t low_bits_mask(uint32_t count) { return count >= 32u ? 0xFFFFFFFFu : (1u << count) - 1u; } static uint32_t count_set_bits(uint32_t value) { uint32_t count = 0u; while (value) { value &= value - 1u; count++; } return count; } static bool bit_is_set(uint32_t word, uint32_t bit) { return bit < 32u && (word & (1u << bit)) != 0u; } static void store_coord(ft_tt_noc_coord *out_coord, uint32_t x, uint32_t y) { out_coord->x = x; out_coord->y = y; } /* ------------------------------------------------------------------------- */ /* Enabled-unit enumeration */ /* ------------------------------------------------------------------------- */ static bool tensix_column_enabled(const ft_tt_arch *arch, uint32_t enabled_col, uint32_t column) { return bit_is_set(enabled_col, arch->tensix.telemetry_bit_by_column[column]); } static uint32_t enabled_tensix_columns(const ft_tt_grid_info *grid, const ft_tt_arch *arch) { uint32_t column; uint32_t count = 0u; for (column = 0; column < arch->tensix.column_count; column++) { if (tensix_column_enabled(arch, grid->tensix_enabled_col, column)) count++; } return count; } /** Grid column of the `logical`-th enabled column. False when out of range. */ static bool nth_enabled_tensix_column(const ft_tt_grid_info *grid, const ft_tt_arch *arch, uint32_t logical, uint32_t *out_column) { uint32_t column; for (column = 0; column < arch->tensix.column_count; column++) { if (!tensix_column_enabled(arch, grid->tensix_enabled_col, column)) continue; if (logical == 0u) { *out_column = column; return true; } logical--; } return false; } /** Index of the `logical`-th set bit of `enabled` below `limit`. Banks and * Ethernet channels are reported in their own index order. */ static bool nth_enabled_index(uint32_t enabled, uint32_t limit, uint32_t logical, uint32_t *out_index) { uint32_t index; for (index = 0; index < limit; index++) { if (!bit_is_set(enabled, index)) continue; if (logical == 0u) { *out_index = index; return true; } logical--; } return false; } static uint32_t enabled_dram_banks(const ft_tt_grid_info *grid, const ft_tt_arch *arch) { return count_set_bits(grid->enabled_gddr & low_bits_mask(arch->dram.bank_count)); } static uint32_t enabled_eth_channels(const ft_tt_grid_info *grid, const ft_tt_arch *arch) { return count_set_bits(grid->enabled_eth & low_bits_mask(arch->eth.channel_count)); } static bool l2cpu_tile_enabled(const ft_tt_grid_info *grid, const ft_tt_arch *arch, uint32_t tile) { return bit_is_set(grid->enabled_l2cpu, arch->fixed.l2cpu_telemetry_bit_by_tile[tile]); } static uint32_t enabled_l2cpu_tiles(const ft_tt_grid_info *grid, const ft_tt_arch *arch) { uint32_t tile; uint32_t count = 0u; for (tile = 0; tile < arch->fixed.l2cpu_count; tile++) { if (l2cpu_tile_enabled(grid, arch, tile)) count++; } return count; } static bool dram_harvest_beyond_limit(const ft_tt_grid_info *grid, const ft_tt_arch *arch) { return arch->dram.bank_count - enabled_dram_banks(grid, arch) > arch->dram.max_harvested_banks; } /* ------------------------------------------------------------------------- */ /* Grid-level checks and counts */ /* ------------------------------------------------------------------------- */ int ft_tt_coord_check_grid(const ft_tt_grid_info *grid) { const ft_tt_arch *arch = NULL; int rc = resolve_arch_only(grid, &arch); if (rc != FT_TT_OK) return rc; if (enabled_tensix_columns(grid, arch) == 0u) { ft_tt_log_error("grid refused: %s reports no enabled Tensix columns", arch->name); return FT_TT_EIO; } if (dram_harvest_beyond_limit(grid, arch)) { ft_tt_log_error("grid refused: %s reports %u of %u DRAM banks enabled; at most %u may " "be harvested", arch->name, enabled_dram_banks(grid, arch), arch->dram.bank_count, arch->dram.max_harvested_banks); return FT_TT_EIO; } return FT_TT_OK; } int ft_tt_coord_check_kernel_build_assumptions(const ft_tt_grid_info *grid) { int rc = ft_tt_coord_check_grid(grid); uint32_t cores; uint32_t banks; if (rc != FT_TT_OK) return rc; cores = ft_tt_coord_tensix_core_count(grid); banks = ft_tt_coord_dram_bank_count(grid); if (cores != FT_TT_KERNEL_BUILD_L1_BANK_COUNT || banks != FT_TT_KERNEL_BUILD_DRAM_BANK_COUNT) { ft_tt_log_error("card has %u Tensix cores / %u DRAM banks, but the kernels were built " "for %u / %u — refusing to run them", cores, banks, FT_TT_KERNEL_BUILD_L1_BANK_COUNT, FT_TT_KERNEL_BUILD_DRAM_BANK_COUNT); return FT_TT_EIO; } return FT_TT_OK; } uint32_t ft_tt_coord_tensix_column_count(const ft_tt_grid_info *grid) { const ft_tt_arch *arch = NULL; if (resolve_grid_arch(grid, &arch) != FT_TT_OK) return 0u; return enabled_tensix_columns(grid, arch); } uint32_t ft_tt_coord_tensix_row_count(const ft_tt_grid_info *grid) { const ft_tt_arch *arch = NULL; if (resolve_grid_arch(grid, &arch) != FT_TT_OK) return 0u; return arch->tensix.row_count; } uint32_t ft_tt_coord_tensix_core_count(const ft_tt_grid_info *grid) { return ft_tt_coord_tensix_column_count(grid) * ft_tt_coord_tensix_row_count(grid); } uint32_t ft_tt_coord_dram_bank_count(const ft_tt_grid_info *grid) { const ft_tt_arch *arch = NULL; if (resolve_grid_arch(grid, &arch) != FT_TT_OK) return 0u; return enabled_dram_banks(grid, arch); } uint32_t ft_tt_coord_dram_ports_per_bank(const ft_tt_grid_info *grid) { const ft_tt_arch *arch = NULL; if (resolve_grid_arch(grid, &arch) != FT_TT_OK) return 0u; return arch->dram.ports_per_bank; } uint32_t ft_tt_coord_eth_channel_count(const ft_tt_grid_info *grid) { const ft_tt_arch *arch = NULL; if (resolve_grid_arch(grid, &arch) != FT_TT_OK) return 0u; return enabled_eth_channels(grid, arch); } uint32_t ft_tt_coord_l2cpu_count(const ft_tt_grid_info *grid) { const ft_tt_arch *arch = NULL; if (resolve_grid_arch(grid, &arch) != FT_TT_OK) return 0u; return enabled_l2cpu_tiles(grid, arch); } int ft_tt_coord_noc_size(const ft_tt_grid_info *grid, uint32_t *out_size_x, uint32_t *out_size_y) { const ft_tt_arch *arch = NULL; int rc; if (!out_size_x || !out_size_y) return FT_TT_EINVAL; rc = resolve_grid_arch(grid, &arch); if (rc != FT_TT_OK) return rc; *out_size_x = arch->noc.size_x; *out_size_y = arch->noc.size_y; return FT_TT_OK; } int ft_tt_coord_tensix_harvest_mask(const ft_tt_arch *arch, uint32_t tensix_enabled_col, uint32_t *out_harvest_mask) { uint32_t column; uint32_t harvested = 0u; if (!arch || !out_harvest_mask) return FT_TT_EINVAL; if (!arch->tensix.telemetry_bit_by_column || arch->tensix.column_count >= 32u) return FT_TT_EIO; for (column = 0; column < arch->tensix.column_count; column++) { if (!tensix_column_enabled(arch, tensix_enabled_col, column)) harvested |= 1u << column; } *out_harvest_mask = harvested; return FT_TT_OK; } /* ------------------------------------------------------------------------- */ /* Per-unit coordinates */ /* ------------------------------------------------------------------------- */ int ft_tt_coord_tensix_at(const ft_tt_grid_info *grid, uint32_t logical_x, uint32_t logical_y, ft_tt_coord_system system, ft_tt_noc_coord *out_coord) { const ft_tt_arch *arch = NULL; uint32_t column = 0u; int rc = begin_grid_lookup(grid, system, out_coord, &arch); if (rc != FT_TT_OK) return rc; if (logical_y >= arch->tensix.row_count || !nth_enabled_tensix_column(grid, arch, logical_x, &column)) return FT_TT_EINVAL; /* Translated: survivors take the low slots of the column list in order. */ if (translation_applies(grid, system)) column = logical_x; store_coord(out_coord, arch->tensix.noc_x_by_column[column], arch->tensix.noc_y_by_row[logical_y]); return FT_TT_OK; } int ft_tt_coord_tensix_by_index(const ft_tt_grid_info *grid, uint32_t index, ft_tt_coord_system system, ft_tt_noc_coord *out_coord) { const ft_tt_arch *arch = NULL; uint32_t columns; int rc = begin_grid_lookup(grid, system, out_coord, &arch); if (rc != FT_TT_OK) return rc; columns = enabled_tensix_columns(grid, arch); if (columns == 0u) return FT_TT_EINVAL; return ft_tt_coord_tensix_at(grid, index % columns, index / columns, system, out_coord); } int ft_tt_coord_dram_port(const ft_tt_grid_info *grid, uint32_t logical_bank, uint32_t port, ft_tt_coord_system system, ft_tt_noc_coord *out_coord) { const ft_tt_arch *arch = NULL; const ft_tt_arch_xy *entry; uint32_t bank = 0u; uint32_t banks_per_column; int rc = begin_grid_lookup(grid, system, out_coord, &arch); if (rc != FT_TT_OK) return rc; if (arch->dram.bank_count == 0u || !arch->dram.noc0) return FT_TT_EUNAVAIL; if (dram_harvest_beyond_limit(grid, arch)) return FT_TT_EIO; if (port >= arch->dram.ports_per_bank || !nth_enabled_index(grid->enabled_gddr, arch->dram.bank_count, logical_bank, &bank)) return FT_TT_EINVAL; if (!translation_applies(grid, system)) { entry = &arch->dram.noc0[bank * arch->dram.ports_per_bank + port]; store_coord(out_coord, entry->x, entry->y); return FT_TT_OK; } /* The harvested-bank alias layout is not modelled: refuse, never guess. */ if (enabled_dram_banks(grid, arch) != arch->dram.bank_count) return FT_TT_EUNAVAIL; banks_per_column = arch->dram.bank_count / arch->dram.translated_column_count; store_coord(out_coord, arch->dram.translated_origin_x + bank / banks_per_column, arch->dram.translated_origin_y + (bank % banks_per_column) * arch->dram.ports_per_bank + port); return FT_TT_OK; } int ft_tt_coord_dram_bank(const ft_tt_grid_info *grid, uint32_t logical_bank, ft_tt_coord_system system, ft_tt_noc_coord *out_coord) { return ft_tt_coord_dram_port(grid, logical_bank, 0u, system, out_coord); } int ft_tt_coord_eth_channel(const ft_tt_grid_info *grid, uint32_t logical_channel, ft_tt_coord_system system, ft_tt_noc_coord *out_coord) { const ft_tt_arch *arch = NULL; uint32_t channel = 0u; int rc = begin_grid_lookup(grid, system, out_coord, &arch); if (rc != FT_TT_OK) return rc; if (!nth_enabled_index(grid->enabled_eth, arch->eth.channel_count, logical_channel, &channel)) return FT_TT_EINVAL; /* Translated: enabled channels pack along one row in channel order. */ if (translation_applies(grid, system)) store_coord(out_coord, arch->eth.translated_origin_x + logical_channel, arch->eth.translated_origin_y); else store_coord(out_coord, arch->eth.noc0[channel].x, arch->eth.noc0[channel].y); return FT_TT_OK; } int ft_tt_coord_arc(const ft_tt_arch *arch, ft_tt_coord_system system, ft_tt_noc_coord *out_coord) { if (!arch || !out_coord || !is_known_system(system)) return FT_TT_EINVAL; store_coord(out_coord, arch->fixed.arc.x, arch->fixed.arc.y); return FT_TT_OK; } int ft_tt_coord_pcie(const ft_tt_arch *arch, uint32_t index, ft_tt_coord_system system, ft_tt_noc_coord *out_coord) { if (!arch || !out_coord || !is_known_system(system)) return FT_TT_EINVAL; if (index >= arch->fixed.pcie_count || !arch->fixed.pcie_noc0) return FT_TT_EINVAL; if (system == FT_TT_COORD_TRANSLATED) return FT_TT_EUNAVAIL; store_coord(out_coord, arch->fixed.pcie_noc0[index].x, arch->fixed.pcie_noc0[index].y); return FT_TT_OK; } int ft_tt_coord_l2cpu(const ft_tt_grid_info *grid, uint32_t logical_index, ft_tt_coord_system system, ft_tt_noc_coord *out_coord) { const ft_tt_arch *arch = NULL; uint32_t tile; int rc = begin_grid_lookup(grid, system, out_coord, &arch); if (rc != FT_TT_OK) return rc; for (tile = 0; tile < arch->fixed.l2cpu_count; tile++) { if (!l2cpu_tile_enabled(grid, arch, tile)) continue; if (logical_index > 0u) { logical_index--; continue; } if (translation_applies(grid, system)) return FT_TT_EUNAVAIL; store_coord(out_coord, arch->fixed.l2cpu_noc0[tile].x, arch->fixed.l2cpu_noc0[tile].y); return FT_TT_OK; } return FT_TT_EINVAL; } /* ------------------------------------------------------------------------- */ /* Classification */ /* ------------------------------------------------------------------------- */ /** Index of (x, y) in a coordinate list, or -1. */ static int find_xy(const ft_tt_arch_xy *list, uint32_t count, uint32_t x, uint32_t y) { uint32_t index; if (!list) return -1; for (index = 0; index < count; index++) { if (list[index].x == x && list[index].y == y) return (int)index; } return -1; } /** Index of `value` in a byte list, or -1. */ static int find_byte(const uint8_t *list, uint32_t count, uint32_t value) { uint32_t index; for (index = 0; index < count; index++) { if (list[index] == value) return (int)index; } return -1; } static bool is_tensix_position(const ft_tt_arch *arch, uint32_t x, uint32_t y) { return find_byte(arch->tensix.noc_x_by_column, arch->tensix.column_count, x) >= 0 && find_byte(arch->tensix.noc_y_by_row, arch->tensix.row_count, y) >= 0; } /** What sits at raw NoC #0 position (x, y). */ static ft_tt_core_type classify_noc0(const ft_tt_arch *arch, uint32_t x, uint32_t y) { const ft_tt_arch_fixed_blocks *fixed = &arch->fixed; if (x >= arch->noc.size_x || y >= arch->noc.size_y) return FT_TT_CORE_NONE; if (is_tensix_position(arch, x, y)) return FT_TT_CORE_TENSIX; if (find_xy(arch->dram.noc0, arch->dram.bank_count * arch->dram.ports_per_bank, x, y) >= 0) return FT_TT_CORE_DRAM; if (find_xy(arch->eth.noc0, arch->eth.channel_count, x, y) >= 0) return FT_TT_CORE_ETH; if (fixed->arc.x == x && fixed->arc.y == y) return FT_TT_CORE_ARC; if (fixed->security_count > 0u && fixed->security.x == x && fixed->security.y == y) return FT_TT_CORE_SECURITY; if (find_xy(fixed->pcie_noc0, fixed->pcie_count, x, y) >= 0) return FT_TT_CORE_PCIE; if (find_xy(fixed->l2cpu_noc0, fixed->l2cpu_count, x, y) >= 0) return FT_TT_CORE_L2CPU; if (find_xy(fixed->router_noc0, fixed->router_count, x, y) >= 0) return FT_TT_CORE_ROUTER; return FT_TT_CORE_NONE; } /** Slot of (x, y) in the translated DRAM alias block, or -1. */ static int dram_alias_slot(const ft_tt_arch *arch, uint32_t x, uint32_t y) { const ft_tt_arch_dram *dram = &arch->dram; uint32_t banks_per_column; if (dram->bank_count == 0u || dram->translated_column_count == 0u || dram->ports_per_bank == 0u) return -1; banks_per_column = dram->bank_count / dram->translated_column_count; if (x < dram->translated_origin_x || x >= dram->translated_origin_x + dram->translated_column_count || y < dram->translated_origin_y || y >= dram->translated_origin_y + banks_per_column * dram->ports_per_bank) return -1; return (int)((x - dram->translated_origin_x) * banks_per_column + (y - dram->translated_origin_y) / dram->ports_per_bank); } /** Slot of (x, y) in the translated Ethernet alias row, or -1. */ static int eth_alias_slot(const ft_tt_arch *arch, uint32_t x, uint32_t y) { if (y != arch->eth.translated_origin_y || x < arch->eth.translated_origin_x || x >= arch->eth.translated_origin_x + arch->eth.channel_count) return -1; return (int)(x - arch->eth.translated_origin_x); } /** * What sits at translated (x, y) with translation enabled. Inside the grid only * Tensix columns are renumbered, and they renumber onto the same set of column * positions, so the NoC #0 answer holds; outside it are the alias ranges. */ static ft_tt_core_type classify_translated(const ft_tt_arch *arch, uint32_t x, uint32_t y) { if (x < arch->noc.size_x && y < arch->noc.size_y) return classify_noc0(arch, x, y); if (dram_alias_slot(arch, x, y) >= 0) return FT_TT_CORE_DRAM; if (arch->fixed.pcie_count > 0u && arch->fixed.pcie_translated.x == x && arch->fixed.pcie_translated.y == y) return FT_TT_CORE_PCIE; if (eth_alias_slot(arch, x, y) >= 0) return FT_TT_CORE_ETH; return FT_TT_CORE_NONE; } ft_tt_core_type ft_tt_coord_classify(const ft_tt_grid_info *grid, ft_tt_coord_system system, uint32_t x, uint32_t y) { const ft_tt_arch *arch = NULL; if (!is_known_system(system) || resolve_grid_arch(grid, &arch) != FT_TT_OK) return FT_TT_CORE_NONE; if (translation_applies(grid, system)) return classify_translated(arch, x, y); return classify_noc0(arch, x, y); } const char *ft_tt_core_type_string(ft_tt_core_type type) { switch (type) { case FT_TT_CORE_NONE: return "none"; case FT_TT_CORE_TENSIX: return "tensix"; case FT_TT_CORE_DRAM: return "dram"; case FT_TT_CORE_ETH: return "eth"; case FT_TT_CORE_ARC: return "arc"; case FT_TT_CORE_PCIE: return "pcie"; case FT_TT_CORE_L2CPU: return "l2cpu"; case FT_TT_CORE_SECURITY: return "security"; case FT_TT_CORE_ROUTER: return "router"; default: return "unknown"; } } /* ------------------------------------------------------------------------- */ /* Enabled checks */ /* ------------------------------------------------------------------------- */ /** Enabled check for a raw NoC #0 node already classified as `type`. */ static bool noc0_node_enabled(const ft_tt_grid_info *grid, const ft_tt_arch *arch, ft_tt_core_type type, uint32_t x, uint32_t y) { int index; switch (type) { case FT_TT_CORE_TENSIX: index = find_byte(arch->tensix.noc_x_by_column, arch->tensix.column_count, x); return tensix_column_enabled(arch, grid->tensix_enabled_col, (uint32_t)index); case FT_TT_CORE_DRAM: index = find_xy(arch->dram.noc0, arch->dram.bank_count * arch->dram.ports_per_bank, x, y); return bit_is_set(grid->enabled_gddr, (uint32_t)index / arch->dram.ports_per_bank); case FT_TT_CORE_ETH: index = find_xy(arch->eth.noc0, arch->eth.channel_count, x, y); return bit_is_set(grid->enabled_eth, (uint32_t)index); case FT_TT_CORE_L2CPU: index = find_xy(arch->fixed.l2cpu_noc0, arch->fixed.l2cpu_count, x, y); return l2cpu_tile_enabled(grid, arch, (uint32_t)index); case FT_TT_CORE_NONE: return false; default: return true; /* ARC, security, PCIe and routers are never harvested */ } } /** Enabled check for translated (x, y) with translation on. Harvested units sit * at the top of each renumbered range, so a slot is live iff it is below the * enabled count. */ static bool translated_node_enabled(const ft_tt_grid_info *grid, const ft_tt_arch *arch, uint32_t x, uint32_t y) { ft_tt_core_type type = classify_translated(arch, x, y); int slot; if (x < arch->noc.size_x && y < arch->noc.size_y) { if (type != FT_TT_CORE_TENSIX) return noc0_node_enabled(grid, arch, type, x, y); slot = find_byte(arch->tensix.noc_x_by_column, arch->tensix.column_count, x); return (uint32_t)slot < enabled_tensix_columns(grid, arch); } switch (type) { case FT_TT_CORE_DRAM: return (uint32_t)dram_alias_slot(arch, x, y) < enabled_dram_banks(grid, arch); case FT_TT_CORE_ETH: return (uint32_t)eth_alias_slot(arch, x, y) < enabled_eth_channels(grid, arch); case FT_TT_CORE_PCIE: return true; default: return false; } } bool ft_tt_coord_is_enabled(const ft_tt_grid_info *grid, ft_tt_coord_system system, uint32_t x, uint32_t y) { const ft_tt_arch *arch = NULL; if (!is_known_system(system) || resolve_grid_arch(grid, &arch) != FT_TT_OK) return false; if (translation_applies(grid, system)) return translated_node_enabled(grid, arch, x, y); return noc0_node_enabled(grid, arch, classify_noc0(arch, x, y), x, y); }