From 7dc57fd045727ed266896ac2b259970d31d3d16d Mon Sep 17 00:00:00 2001 From: Kur0x Date: Tue, 28 Jul 2026 10:52:56 +0800 Subject: [PATCH 1/3] feat(ccu): add core microcode planning --- src/comm/CMakeLists.txt | 21 + src/comm/ccu/tilexr_ccu_abi_constants.h | 18 + src/comm/ccu/tilexr_ccu_barrier_program.cpp | 273 ++++++ src/comm/ccu/tilexr_ccu_barrier_program.h | 56 ++ src/comm/ccu/tilexr_ccu_memory_program.cpp | 203 ++++ src/comm/ccu/tilexr_ccu_memory_program.h | 59 ++ src/comm/ccu/tilexr_ccu_microcode.cpp | 298 ++++++ src/comm/ccu/tilexr_ccu_microcode.h | 100 ++ src/comm/ccu/tilexr_ccu_producer_plan.cpp | 456 +++++++++ src/comm/ccu/tilexr_ccu_producer_plan.h | 99 ++ .../ccu/tilexr_ccu_resource_allocator.cpp | 377 +++++++ src/comm/ccu/tilexr_ccu_resource_allocator.h | 130 +++ src/comm/ccu/tilexr_ccu_runtime.cpp | 97 ++ src/comm/ccu/tilexr_ccu_runtime.h | 47 + src/comm/ccu/tilexr_ccu_specs.cpp | 178 ++++ src/comm/ccu/tilexr_ccu_specs.h | 91 ++ tests/ccu/test_tilexr_ccu_barrier_program.py | 649 +++++++++++++ tests/ccu/test_tilexr_ccu_memory_program.py | 315 ++++++ tests/ccu/test_tilexr_ccu_microcode.py | 572 +++++++++++ tests/ccu/test_tilexr_ccu_producer_plan.py | 461 +++++++++ .../ccu/test_tilexr_ccu_resource_allocator.py | 917 ++++++++++++++++++ tests/ccu/test_tilexr_ccu_runtime_boundary.py | 114 +++ tests/ccu/test_tilexr_ccu_specs.py | 224 +++++ 23 files changed, 5755 insertions(+) create mode 100644 src/comm/ccu/tilexr_ccu_abi_constants.h create mode 100644 src/comm/ccu/tilexr_ccu_barrier_program.cpp create mode 100644 src/comm/ccu/tilexr_ccu_barrier_program.h create mode 100644 src/comm/ccu/tilexr_ccu_memory_program.cpp create mode 100644 src/comm/ccu/tilexr_ccu_memory_program.h create mode 100644 src/comm/ccu/tilexr_ccu_microcode.cpp create mode 100644 src/comm/ccu/tilexr_ccu_microcode.h create mode 100644 src/comm/ccu/tilexr_ccu_producer_plan.cpp create mode 100644 src/comm/ccu/tilexr_ccu_producer_plan.h create mode 100644 src/comm/ccu/tilexr_ccu_resource_allocator.cpp create mode 100644 src/comm/ccu/tilexr_ccu_resource_allocator.h create mode 100644 src/comm/ccu/tilexr_ccu_runtime.cpp create mode 100644 src/comm/ccu/tilexr_ccu_runtime.h create mode 100644 src/comm/ccu/tilexr_ccu_specs.cpp create mode 100644 src/comm/ccu/tilexr_ccu_specs.h create mode 100644 tests/ccu/test_tilexr_ccu_barrier_program.py create mode 100644 tests/ccu/test_tilexr_ccu_memory_program.py create mode 100644 tests/ccu/test_tilexr_ccu_microcode.py create mode 100644 tests/ccu/test_tilexr_ccu_producer_plan.py create mode 100644 tests/ccu/test_tilexr_ccu_resource_allocator.py create mode 100644 tests/ccu/test_tilexr_ccu_runtime_boundary.py create mode 100644 tests/ccu/test_tilexr_ccu_specs.py diff --git a/src/comm/CMakeLists.txt b/src/comm/CMakeLists.txt index e0fd5253..16c39a5b 100644 --- a/src/comm/CMakeLists.txt +++ b/src/comm/CMakeLists.txt @@ -105,6 +105,21 @@ set(TILEXR_SOURCE_FILE tilexr_comm.cpp tilexr_internal.cpp tilexr_internal.h tilexr_log.h + ccu/tilexr_ccu_abi_constants.h + ccu/tilexr_ccu_barrier_program.h + ccu/tilexr_ccu_barrier_program.cpp + ccu/tilexr_ccu_memory_program.h + ccu/tilexr_ccu_memory_program.cpp + ccu/tilexr_ccu_microcode.h + ccu/tilexr_ccu_microcode.cpp + ccu/tilexr_ccu_producer_plan.h + ccu/tilexr_ccu_producer_plan.cpp + ccu/tilexr_ccu_resource_allocator.h + ccu/tilexr_ccu_resource_allocator.cpp + ccu/tilexr_ccu_runtime.h + ccu/tilexr_ccu_runtime.cpp + ccu/tilexr_ccu_specs.h + ccu/tilexr_ccu_specs.cpp tools/socket/tilexr_sock_exchange.h tools/socket/tilexr_sock_exchange.cpp udma/tilexr_hccp_defs.h @@ -121,6 +136,11 @@ set(TILEXR_SOURCE_FILE tilexr_comm.cpp ) add_library(tile-comm SHARED ${TILEXR_SOURCE_FILE}) +set_target_properties(tile-comm PROPERTIES + BUILD_WITH_INSTALL_RPATH FALSE + SKIP_BUILD_RPATH TRUE + INSTALL_RPATH "" + SKIP_INSTALL_RPATH TRUE) if(TILEXR_HAVE_PTO_SDMA) target_compile_definitions(tile-comm PRIVATE TILEXR_HAVE_PTO_SDMA=1) @@ -136,6 +156,7 @@ target_include_directories(tile-comm ${ASCEND_HOME_PATH}/${ARCH}-linux/pkg_inc/ ${ASCEND_HOME_PATH}/${ARCH}-linux/pkg_inc/profiling/ ${ASCEND_HOME_PATH}/${ARCH}-linux/pkg_inc/runtime/ + ${ASCEND_HOME_PATH}/${ARCH}-linux/pkg_inc/runtime/runtime/ ${ASCEND_HOME_PATH}/${ARCH}-linux/include/ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/src/comm/ccu/tilexr_ccu_abi_constants.h b/src/comm/ccu/tilexr_ccu_abi_constants.h new file mode 100644 index 00000000..10b1692d --- /dev/null +++ b/src/comm/ccu/tilexr_ccu_abi_constants.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2026 TileXR Project + * Licensed under the Apache License, Version 2.0 + */ + +#ifndef TILEXR_CCU_ABI_CONSTANTS_H +#define TILEXR_CCU_ABI_CONSTANTS_H + +#include + +namespace TileXR { + +constexpr uint32_t TILEXR_CCU_EID_BYTES = 16; +constexpr uint32_t TILEXR_CCU_REMOTE_CCU_VA_SHIFT = 23; + +} // namespace TileXR + +#endif // TILEXR_CCU_ABI_CONSTANTS_H diff --git a/src/comm/ccu/tilexr_ccu_barrier_program.cpp b/src/comm/ccu/tilexr_ccu_barrier_program.cpp new file mode 100644 index 00000000..319caeea --- /dev/null +++ b/src/comm/ccu/tilexr_ccu_barrier_program.cpp @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2026 TileXR Project + * Licensed under the Apache License, Version 2.0 + */ + +#include "ccu/tilexr_ccu_barrier_program.h" + +namespace TileXR { +namespace { + +void ResetReport(TileXRCcuBarrierProgramReport* report) +{ + if (report != nullptr) { + *report = TileXRCcuBarrierProgramReport{}; + } +} + +int Fail( + std::vector* program, + TileXRCcuBarrierProgramReport* report, + const std::string& message) +{ + if (program != nullptr) { + program->clear(); + } + if (report != nullptr) { + report->message = message; + } + return TILEXR_ERROR_PARA_CHECK_FAIL; +} + +bool MissingPostResource(const TileXRCcuBarrierSyncSpec& spec) +{ + return spec.remoteXn == 0 || spec.localXn == 0 || spec.channelId == 0 || + spec.remoteNotifyCke == 0 || spec.remoteNotifyMask == 0; +} + +bool MissingSyncCkePostResource(const TileXRCcuBarrierSyncSpec& spec) +{ + return spec.channelId == 0 || spec.remoteNotifyCke == 0 || spec.remoteNotifyMask == 0 || + spec.sourceCke == 0 || spec.sourceCkeMask == 0; +} + +bool MissingWaitResource(const TileXRCcuBarrierSyncSpec& spec) +{ + return spec.localWaitCke == 0 || spec.localWaitMask == 0; +} + +void FillReport( + size_t specCount, + size_t totalInstructionCount, + TileXRCcuBarrierProgramReport* report, + bool hasWaitInstructions = true) +{ + if (report == nullptr) { + return; + } + report->postInstructionCount = static_cast(specCount); + report->waitInstructionCount = hasWaitInstructions ? static_cast(specCount) : 0U; + report->totalInstructionCount = static_cast(totalInstructionCount); + report->message = "ok"; +} + +bool LoadBeforePostOnly(TileXRCcuBarrierMode mode) +{ + return mode == TileXRCcuBarrierMode::SyncXnLoadPostOnly; +} + +bool PostOnly(TileXRCcuBarrierMode mode) +{ + return mode == TileXRCcuBarrierMode::SyncXnPostOnly || + mode == TileXRCcuBarrierMode::SyncXnLoadPostOnly || + mode == TileXRCcuBarrierMode::SyncCkePostOnly || + mode == TileXRCcuBarrierMode::LocalCkePostOnly; +} + +bool SyncCkeMode(TileXRCcuBarrierMode mode) +{ + return mode == TileXRCcuBarrierMode::SyncCke || + mode == TileXRCcuBarrierMode::SyncCkeSetWait || + mode == TileXRCcuBarrierMode::SyncCkePostOnly; +} + +bool SyncCkeSetWaitMode(TileXRCcuBarrierMode mode) +{ + return mode == TileXRCcuBarrierMode::SyncCkeSetWait; +} + +void AddSourceCkeInitInstructions( + const std::vector& specs, + std::vector* program) +{ + std::vector initialized; + for (const auto& spec : specs) { + bool seen = false; + for (uint16_t cke : initialized) { + if (cke == spec.sourceCke) { + seen = true; + break; + } + } + if (seen) { + continue; + } + TileXRCcuCkeSpec init; + init.ckeId = spec.sourceCke; + init.mask = spec.sourceCkeMask; + init.clearWait = true; + TileXRCcuInstr instr; + if (TileXRCcuEncodeSetCke(init, &instr) == TILEXR_SUCCESS) { + program->push_back(instr); + initialized.push_back(spec.sourceCke); + } + } +} + +int AddLocalCkeDiagnosticInstructions( + const std::vector& specs, + std::vector* program, + TileXRCcuBarrierProgramReport* report) +{ + for (const auto& spec : specs) { + TileXRCcuCkeSpec post; + post.ckeId = spec.localWaitCke; + post.mask = spec.localWaitMask; + post.clearWait = false; + TileXRCcuInstr instr; + if (TileXRCcuEncodeSetCke(post, &instr) != TILEXR_SUCCESS) { + return Fail(program, report, "failed to encode local CKE diagnostic set instruction"); + } + program->push_back(instr); + } + + for (const auto& spec : specs) { + TileXRCcuCkeSpec wait; + wait.waitCkeId = spec.localWaitCke; + wait.waitMask = spec.localWaitMask; + wait.clearWait = spec.clearLocalWait; + TileXRCcuInstr instr; + if (TileXRCcuEncodeClearCke(wait, &instr) != TILEXR_SUCCESS) { + return Fail(program, report, "failed to encode local CKE diagnostic wait instruction"); + } + program->push_back(instr); + } + + return TILEXR_SUCCESS; +} + +int AddLocalCkePostOnlyDiagnosticInstructions( + const std::vector& specs, + std::vector* program, + TileXRCcuBarrierProgramReport* report) +{ + for (const auto& spec : specs) { + TileXRCcuCkeSpec post; + post.ckeId = spec.localWaitCke; + post.mask = spec.localWaitMask; + post.clearWait = false; + TileXRCcuInstr instr; + if (TileXRCcuEncodeSetCke(post, &instr) != TILEXR_SUCCESS) { + return Fail(program, report, "failed to encode local CKE post-only diagnostic set instruction"); + } + program->push_back(instr); + } + return TILEXR_SUCCESS; +} + +} // namespace + +int TileXRCcuBuildBarrierProgram( + const std::vector& specs, + std::vector* program, + TileXRCcuBarrierProgramReport* report, + TileXRCcuBarrierMode mode) +{ + ResetReport(report); + if (program == nullptr) { + return Fail(program, report, "missing output CCU barrier program"); + } + program->clear(); + if (specs.empty()) { + return Fail(program, report, "missing CCU barrier sync specs"); + } + + for (const auto& spec : specs) { + if (mode != TileXRCcuBarrierMode::LocalCke && + mode != TileXRCcuBarrierMode::LocalCkePostOnly && + (SyncCkeMode(mode) ? MissingSyncCkePostResource(spec) : + MissingPostResource(spec))) { + return Fail(program, report, "missing remote XN post resource for CCU barrier program"); + } + if (!PostOnly(mode) && MissingWaitResource(spec)) { + return Fail(program, report, "missing local wait CKE resource for CCU barrier program"); + } + } + + program->reserve(specs.size() * (SyncCkeMode(mode) ? 3U : 2U)); + if (mode == TileXRCcuBarrierMode::LocalCke) { + const int ret = AddLocalCkeDiagnosticInstructions(specs, program, report); + if (ret != TILEXR_SUCCESS) { + return ret; + } + FillReport(specs.size(), program->size(), report); + return TILEXR_SUCCESS; + } + if (mode == TileXRCcuBarrierMode::LocalCkePostOnly) { + const int ret = AddLocalCkePostOnlyDiagnosticInstructions(specs, program, report); + if (ret != TILEXR_SUCCESS) { + return ret; + } + FillReport(specs.size(), program->size(), report, false); + return TILEXR_SUCCESS; + } + if (SyncCkeMode(mode)) { + AddSourceCkeInitInstructions(specs, program); + } + + for (const auto& spec : specs) { + TileXRCcuInstr instr; + if (SyncCkeMode(mode)) { + TileXRCcuSyncCkeSpec post; + post.remoteCke = spec.remoteNotifyCke; + post.localCke = spec.sourceCke; + post.localCkeMask = spec.remoteNotifyMask; + post.channelId = spec.channelId; + if (TileXRCcuEncodeSyncCke(post, &instr) != TILEXR_SUCCESS) { + return Fail(program, report, "failed to encode CCU barrier SyncCKE post instruction"); + } + } else { + if (LoadBeforePostOnly(mode)) { + if (TileXRCcuEncodeLoadImdToXn(spec.localXn, 1U, 0, &instr) != TILEXR_SUCCESS) { + return Fail(program, report, "failed to encode CCU barrier local XN load instruction"); + } + program->push_back(instr); + } + TileXRCcuSyncXnSpec post; + post.remoteXn = spec.remoteXn; + post.localXn = spec.localXn; + post.channelId = spec.channelId; + post.notifyCke = spec.remoteNotifyCke; + post.notifyMask = spec.remoteNotifyMask; + if (TileXRCcuEncodeSyncXn(post, &instr) != TILEXR_SUCCESS) { + return Fail(program, report, "failed to encode CCU barrier post instruction"); + } + } + program->push_back(instr); + } + + if (PostOnly(mode)) { + FillReport(specs.size(), program->size(), report, false); + return TILEXR_SUCCESS; + } + + for (const auto& spec : specs) { + TileXRCcuCkeSpec wait; + wait.waitCkeId = spec.localWaitCke; + wait.waitMask = spec.localWaitMask; + wait.clearWait = spec.clearLocalWait; + TileXRCcuInstr instr; + const int ret = SyncCkeMode(mode) && !SyncCkeSetWaitMode(mode) ? + TileXRCcuEncodeClearCke(wait, &instr) : + TileXRCcuEncodeSetCke(wait, &instr); + if (ret != TILEXR_SUCCESS) { + return Fail(program, report, "failed to encode CCU barrier wait/clear instruction"); + } + program->push_back(instr); + } + + FillReport(specs.size(), program->size(), report); + return TILEXR_SUCCESS; +} + +} // namespace TileXR diff --git a/src/comm/ccu/tilexr_ccu_barrier_program.h b/src/comm/ccu/tilexr_ccu_barrier_program.h new file mode 100644 index 00000000..394b4680 --- /dev/null +++ b/src/comm/ccu/tilexr_ccu_barrier_program.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2026 TileXR Project + * Licensed under the Apache License, Version 2.0 + */ + +#ifndef TILEXR_CCU_BARRIER_PROGRAM_H +#define TILEXR_CCU_BARRIER_PROGRAM_H + +#include "ccu/tilexr_ccu_microcode.h" + +#include +#include +#include + +namespace TileXR { + +enum class TileXRCcuBarrierMode { + SyncXn = 0, + SyncCke = 1, + LocalCke = 2, + SyncXnPostOnly = 3, + SyncXnLoadPostOnly = 4, + SyncCkePostOnly = 5, + LocalCkePostOnly = 6, + SyncCkeSetWait = 7, +}; + +struct TileXRCcuBarrierSyncSpec { + uint16_t remoteXn = 0; + uint16_t localXn = 0; + uint16_t channelId = 0; + uint16_t remoteNotifyCke = 0; + uint16_t remoteNotifyMask = 0; + uint16_t localWaitCke = 0; + uint16_t localWaitMask = 0; + uint16_t sourceCke = 0; + uint16_t sourceCkeMask = 0; + bool clearLocalWait = true; +}; + +struct TileXRCcuBarrierProgramReport { + uint32_t postInstructionCount = 0; + uint32_t waitInstructionCount = 0; + uint32_t totalInstructionCount = 0; + std::string message; +}; + +int TileXRCcuBuildBarrierProgram( + const std::vector& specs, + std::vector* program, + TileXRCcuBarrierProgramReport* report, + TileXRCcuBarrierMode mode = TileXRCcuBarrierMode::SyncXn); + +} // namespace TileXR + +#endif // TILEXR_CCU_BARRIER_PROGRAM_H diff --git a/src/comm/ccu/tilexr_ccu_memory_program.cpp b/src/comm/ccu/tilexr_ccu_memory_program.cpp new file mode 100644 index 00000000..a1559abf --- /dev/null +++ b/src/comm/ccu/tilexr_ccu_memory_program.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2026 TileXR Project + * Licensed under the Apache License, Version 2.0 + */ + +#include "ccu/tilexr_ccu_memory_program.h" + +namespace TileXR { +namespace { + +constexpr uint64_t TILEXR_CCU_TOKEN_VALID_SHIFT = 52ULL; +constexpr uint64_t TILEXR_CCU_TOKEN_ID_SHIFT = 32ULL; +constexpr uint64_t TILEXR_CCU_TOKEN_ID_MASK = 0xfffffULL; +constexpr uint64_t TILEXR_CCU_TOKEN_VALUE_MASK = 0xffffffffULL; + +void ResetReport(TileXRCcuMemoryProgramReport* report) +{ + if (report != nullptr) { + *report = TileXRCcuMemoryProgramReport{}; + } +} + +int Fail( + std::vector* program, + TileXRCcuMemoryProgramReport* report, + const std::string& message) +{ + if (program != nullptr) { + program->clear(); + } + if (report != nullptr) { + report->message = message; + } + return TILEXR_ERROR_PARA_CHECK_FAIL; +} + +int ValidateSpec( + const TileXRCcuMemoryCopySpec& spec, + std::vector* program, + TileXRCcuMemoryProgramReport* report) +{ + if (program == nullptr) { + return Fail(program, report, "missing output CCU memory copy program"); + } + if (spec.localGsa == 0 || spec.localXn == 0 || spec.remoteGsa == 0 || spec.remoteXn == 0 || + spec.lengthXn == 0) { + return Fail(program, report, "missing CCU memory copy GSA/XN resources"); + } + if (spec.localAddr == 0 || spec.localToken == 0 || spec.remoteAddr == 0 || spec.remoteToken == 0) { + return Fail(program, report, "missing CCU memory copy address/token inputs"); + } + if (spec.lengthBytes == 0) { + return Fail(program, report, "missing CCU memory copy length"); + } + if (spec.channelId == 0) { + return Fail(program, report, "missing CCU memory copy channel"); + } + if (spec.completionCke == 0 || spec.completionMask == 0) { + return Fail(program, report, "missing CCU memory copy completion CKE"); + } + if (spec.reduceDataType > 0xfU || spec.reduceOpCode > 0xfU) { + return Fail(program, report, "CCU memory copy reduce fields exceed v1 encoding width"); + } + return TILEXR_SUCCESS; +} + +int AppendLoadImmediates( + const TileXRCcuMemoryCopySpec& spec, + std::vector* program, + TileXRCcuMemoryProgramReport* report) +{ + TileXRCcuInstr instr; + if (TileXRCcuEncodeLoadImdToGsa(spec.localGsa, spec.localAddr, &instr) != TILEXR_SUCCESS) { + return Fail(program, report, "failed to encode local memory address GSA load"); + } + program->push_back(instr); + + if (TileXRCcuEncodeLoadImdToXn(spec.localXn, spec.localToken, 1U, &instr) != TILEXR_SUCCESS) { + return Fail(program, report, "failed to encode local memory token XN load"); + } + program->push_back(instr); + + if (TileXRCcuEncodeLoadImdToGsa(spec.remoteGsa, spec.remoteAddr, &instr) != TILEXR_SUCCESS) { + return Fail(program, report, "failed to encode remote memory address GSA load"); + } + program->push_back(instr); + + if (TileXRCcuEncodeLoadImdToXn(spec.remoteXn, spec.remoteToken, 1U, &instr) != TILEXR_SUCCESS) { + return Fail(program, report, "failed to encode remote memory token XN load"); + } + program->push_back(instr); + + if (TileXRCcuEncodeLoadImdToXn(spec.lengthXn, spec.lengthBytes, 0, &instr) != TILEXR_SUCCESS) { + return Fail(program, report, "failed to encode memory copy length XN load"); + } + program->push_back(instr); + return TILEXR_SUCCESS; +} + +int AppendTransfer( + const TileXRCcuMemoryCopySpec& spec, + std::vector* program, + TileXRCcuMemoryProgramReport* report) +{ + TileXRCcuMemTransferSpec transfer; + transfer.localGsa = spec.localGsa; + transfer.localXn = spec.localXn; + transfer.remoteGsa = spec.remoteGsa; + transfer.remoteXn = spec.remoteXn; + transfer.lengthXn = spec.lengthXn; + transfer.channelId = spec.channelId; + transfer.reduceDataType = spec.reduceDataType; + transfer.reduceOpCode = spec.reduceOpCode; + transfer.setCkeId = spec.completionCke; + transfer.setCkeMask = spec.completionMask; + transfer.clearWait = true; + transfer.lengthFromXn = true; + transfer.reduceEnabled = spec.reduceEnabled; + + TileXRCcuInstr instr; + const int ret = spec.direction == TileXRCcuMemoryCopyDirection::RemoteToLocal ? + TileXRCcuEncodeTransRmtMemToLocMem(transfer, &instr) : + TileXRCcuEncodeTransLocMemToRmtMem(transfer, &instr); + if (ret != TILEXR_SUCCESS) { + return Fail(program, report, "failed to encode CCU memory transfer instruction"); + } + program->push_back(instr); + return TILEXR_SUCCESS; +} + +int AppendCompletionWait( + const TileXRCcuMemoryCopySpec& spec, + std::vector* program, + TileXRCcuMemoryProgramReport* report) +{ + TileXRCcuCkeSpec wait; + wait.waitCkeId = spec.completionCke; + wait.waitMask = spec.completionMask; + wait.clearWait = true; + + TileXRCcuInstr instr; + if (TileXRCcuEncodeClearCke(wait, &instr) != TILEXR_SUCCESS) { + return Fail(program, report, "failed to encode CCU memory copy completion wait"); + } + program->push_back(instr); + return TILEXR_SUCCESS; +} + +void FillReport(const std::vector& program, TileXRCcuMemoryProgramReport* report) +{ + if (report == nullptr) { + return; + } + report->loadInstructionCount = 5; + report->transferInstructionCount = 1; + report->waitInstructionCount = 1; + report->totalInstructionCount = static_cast(program.size()); + report->message = "ok"; +} + +} // namespace + +uint64_t TileXRCcuPackMemoryToken(uint32_t tokenId, uint32_t tokenValue, bool valid) +{ + const uint64_t validBits = valid ? 1ULL : 0ULL; + return (validBits << TILEXR_CCU_TOKEN_VALID_SHIFT) | + ((static_cast(tokenId) & TILEXR_CCU_TOKEN_ID_MASK) << TILEXR_CCU_TOKEN_ID_SHIFT) | + (static_cast(tokenValue) & TILEXR_CCU_TOKEN_VALUE_MASK); +} + +int TileXRCcuBuildMemoryCopyProgram( + const TileXRCcuMemoryCopySpec& spec, + std::vector* program, + TileXRCcuMemoryProgramReport* report) +{ + ResetReport(report); + if (program != nullptr) { + program->clear(); + } + int ret = ValidateSpec(spec, program, report); + if (ret != TILEXR_SUCCESS) { + return ret; + } + + program->reserve(7); + ret = AppendLoadImmediates(spec, program, report); + if (ret != TILEXR_SUCCESS) { + return ret; + } + ret = AppendTransfer(spec, program, report); + if (ret != TILEXR_SUCCESS) { + return ret; + } + ret = AppendCompletionWait(spec, program, report); + if (ret != TILEXR_SUCCESS) { + return ret; + } + + FillReport(*program, report); + return TILEXR_SUCCESS; +} + +} // namespace TileXR diff --git a/src/comm/ccu/tilexr_ccu_memory_program.h b/src/comm/ccu/tilexr_ccu_memory_program.h new file mode 100644 index 00000000..19640b5e --- /dev/null +++ b/src/comm/ccu/tilexr_ccu_memory_program.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2026 TileXR Project + * Licensed under the Apache License, Version 2.0 + */ + +#ifndef TILEXR_CCU_MEMORY_PROGRAM_H +#define TILEXR_CCU_MEMORY_PROGRAM_H + +#include "ccu/tilexr_ccu_microcode.h" + +#include +#include +#include + +namespace TileXR { + +enum class TileXRCcuMemoryCopyDirection { + RemoteToLocal = 0, + LocalToRemote = 1, +}; + +struct TileXRCcuMemoryCopySpec { + TileXRCcuMemoryCopyDirection direction = TileXRCcuMemoryCopyDirection::RemoteToLocal; + uint16_t localGsa = 0; + uint16_t localXn = 0; + uint16_t remoteGsa = 0; + uint16_t remoteXn = 0; + uint16_t lengthXn = 0; + uint64_t localAddr = 0; + uint64_t localToken = 0; + uint64_t remoteAddr = 0; + uint64_t remoteToken = 0; + uint64_t lengthBytes = 0; + uint16_t channelId = 0; + uint16_t completionCke = 0; + uint16_t completionMask = 0; + uint16_t reduceDataType = 0; + uint16_t reduceOpCode = 0; + bool reduceEnabled = false; +}; + +struct TileXRCcuMemoryProgramReport { + uint32_t loadInstructionCount = 0; + uint32_t transferInstructionCount = 0; + uint32_t waitInstructionCount = 0; + uint32_t totalInstructionCount = 0; + std::string message; +}; + +uint64_t TileXRCcuPackMemoryToken(uint32_t tokenId, uint32_t tokenValue, bool valid); + +int TileXRCcuBuildMemoryCopyProgram( + const TileXRCcuMemoryCopySpec& spec, + std::vector* program, + TileXRCcuMemoryProgramReport* report); + +} // namespace TileXR + +#endif // TILEXR_CCU_MEMORY_PROGRAM_H diff --git a/src/comm/ccu/tilexr_ccu_microcode.cpp b/src/comm/ccu/tilexr_ccu_microcode.cpp new file mode 100644 index 00000000..44abfdbe --- /dev/null +++ b/src/comm/ccu/tilexr_ccu_microcode.cpp @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2026 TileXR Project + * Licensed under the Apache License, Version 2.0 + */ + +#include "ccu/tilexr_ccu_microcode.h" + +namespace TileXR { +namespace { + +constexpr uint64_t TILEXR_CCU_LOAD_SQE_ARGS_TO_X_HEADER = 0x0001U; +constexpr uint64_t TILEXR_CCU_LOAD_IMD_TO_GSA_HEADER = 0x0002U; +constexpr uint64_t TILEXR_CCU_LOAD_IMD_TO_XN_HEADER = 0x0003U; +constexpr uint64_t TILEXR_CCU_SET_CKE_HEADER = 0x0802U; +constexpr uint64_t TILEXR_CCU_CLEAR_CKE_HEADER = 0x0804U; +constexpr uint64_t TILEXR_CCU_TRANS_RMT_MEM_TO_LOC_MEM_HEADER = 0x1008U; +constexpr uint64_t TILEXR_CCU_TRANS_LOC_MEM_TO_RMT_MEM_HEADER = 0x1009U; +constexpr uint64_t TILEXR_CCU_SYNC_CKE_HEADER = 0x100bU; +constexpr uint64_t TILEXR_CCU_SYNC_XN_HEADER = 0x100dU; +constexpr uint64_t TILEXR_CCU_SYNC_XN_TRACE_FLAG = 0x0001000000000000ULL; + +void ClearInstr(TileXRCcuInstr* instr) +{ + for (auto& word : instr->words) { + word = 0; + } +} + +int ValidateInstrOutput(TileXRCcuInstr* instr) +{ + if (instr == nullptr) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + ClearInstr(instr); + return TILEXR_SUCCESS; +} + +uint64_t PackSlots(uint16_t slot0, uint16_t slot1, uint16_t slot2, uint16_t slot3) +{ + return static_cast(slot0) | + (static_cast(slot1) << 16U) | + (static_cast(slot2) << 32U) | + (static_cast(slot3) << 48U); +} + +uint16_t ClearTypeBit(bool clearWait) +{ + return clearWait ? 1U : 0U; +} + +uint16_t TransferControlSlot(const TileXRCcuMemTransferSpec& spec) +{ + constexpr uint16_t udfType = 0; + return static_cast(udfType | + (static_cast(spec.reduceDataType) << 8U) | + (static_cast(spec.reduceOpCode) << 12U)); +} + +uint16_t TransferFlagSlot(const TileXRCcuMemTransferSpec& spec) +{ + return static_cast( + (spec.clearWait ? 1U : 0U) | + (spec.lengthFromXn ? 2U : 0U) | + (spec.reduceEnabled ? 4U : 0U)); +} + +int ValidateTransferSpec(const TileXRCcuMemTransferSpec& spec) +{ + if (spec.localGsa == 0 || spec.localXn == 0 || spec.remoteGsa == 0 || spec.remoteXn == 0 || + spec.lengthXn == 0 || spec.channelId == 0 || spec.reduceDataType > 0xfU || spec.reduceOpCode > 0xfU) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + if ((spec.setCkeId == 0) != (spec.setCkeMask == 0) || + (spec.waitCkeId == 0) != (spec.waitCkeMask == 0)) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + return TILEXR_SUCCESS; +} + +void WriteLe16(uint8_t* bytes, size_t offset, uint16_t value) +{ + bytes[offset] = static_cast(value & 0xffU); + bytes[offset + 1U] = static_cast((value >> 8U) & 0xffU); +} + +void WriteLe64(uint8_t* bytes, size_t offset, uint64_t value) +{ + for (size_t i = 0; i < sizeof(uint64_t); ++i) { + bytes[offset + i] = static_cast((value >> (i * 8U)) & 0xffU); + } +} + +uint64_t ReadLe64(const uint8_t* bytes, size_t offset) +{ + uint64_t value = 0; + for (size_t i = 0; i < sizeof(uint64_t); ++i) { + value |= static_cast(bytes[offset + i]) << (i * 8U); + } + return value; +} + +} // namespace + +int TileXRCcuEncodeLoadSqeArgsToX(uint16_t xnId, uint32_t sqeArgId, TileXRCcuInstr* instr) +{ + if (ValidateInstrOutput(instr) != TILEXR_SUCCESS) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + if (xnId == 0 || sqeArgId >= TILEXR_CCU_SQE_ARGS_LEN) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + + instr->words[0] = PackSlots(static_cast(TILEXR_CCU_LOAD_SQE_ARGS_TO_X_HEADER), xnId, + static_cast(sqeArgId), 0); + return TILEXR_SUCCESS; +} + +int TileXRCcuEncodeLoadImdToXn(uint16_t xnId, uint64_t immediate, uint16_t secFlag, TileXRCcuInstr* instr) +{ + if (ValidateInstrOutput(instr) != TILEXR_SUCCESS) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + if (xnId == 0) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + + uint8_t bytes[sizeof(TileXRCcuInstr)] = {}; + WriteLe16(bytes, 0, static_cast(TILEXR_CCU_LOAD_IMD_TO_XN_HEADER)); + WriteLe16(bytes, 2, xnId); + WriteLe64(bytes, 4, immediate); + WriteLe16(bytes, 12, secFlag); + for (size_t i = 0; i < 4U; ++i) { + instr->words[i] = ReadLe64(bytes, i * sizeof(uint64_t)); + } + return TILEXR_SUCCESS; +} + +int TileXRCcuEncodeLoadImdToGsa(uint16_t gsaId, uint64_t immediate, TileXRCcuInstr* instr) +{ + if (ValidateInstrOutput(instr) != TILEXR_SUCCESS) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + if (gsaId == 0) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + + uint8_t bytes[sizeof(TileXRCcuInstr)] = {}; + WriteLe16(bytes, 0, static_cast(TILEXR_CCU_LOAD_IMD_TO_GSA_HEADER)); + WriteLe16(bytes, 2, gsaId); + WriteLe64(bytes, 4, immediate); + for (size_t i = 0; i < 4U; ++i) { + instr->words[i] = ReadLe64(bytes, i * sizeof(uint64_t)); + } + return TILEXR_SUCCESS; +} + +int TileXRCcuEncodeSyncXn(const TileXRCcuSyncXnSpec& spec, TileXRCcuInstr* instr) +{ + if (ValidateInstrOutput(instr) != TILEXR_SUCCESS) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + if (spec.remoteXn == 0 || spec.localXn == 0 || spec.channelId == 0 || spec.notifyCke == 0 || + spec.notifyMask == 0) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + + instr->words[0] = PackSlots(static_cast(TILEXR_CCU_SYNC_XN_HEADER), spec.remoteXn, spec.localXn, 0); + instr->words[1] = PackSlots(spec.channelId, spec.notifyCke, spec.notifyMask, 0); + instr->words[2] = spec.clearWait ? TILEXR_CCU_SYNC_XN_TRACE_FLAG : 0; + instr->words[3] = PackSlots(spec.setCkeId, spec.setCkeMask, spec.waitCkeId, spec.waitCkeMask); + return TILEXR_SUCCESS; +} + +int TileXRCcuEncodeSyncCke(const TileXRCcuSyncCkeSpec& spec, TileXRCcuInstr* instr) +{ + if (ValidateInstrOutput(instr) != TILEXR_SUCCESS) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + if (spec.remoteCke == 0 || spec.localCke == 0 || spec.localCkeMask == 0 || spec.channelId == 0) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + + instr->words[0] = PackSlots( + static_cast(TILEXR_CCU_SYNC_CKE_HEADER), + spec.remoteCke, + spec.localCke, + spec.localCkeMask); + instr->words[1] = PackSlots(spec.channelId, 0, 0, 0); + instr->words[2] = PackSlots(0, 0, 0, ClearTypeBit(spec.clearWait)); + instr->words[3] = PackSlots(spec.setCkeId, spec.setCkeMask, spec.waitCkeId, spec.waitCkeMask); + return TILEXR_SUCCESS; +} + +int TileXRCcuEncodeSetCke(const TileXRCcuCkeSpec& spec, TileXRCcuInstr* instr) +{ + if (ValidateInstrOutput(instr) != TILEXR_SUCCESS) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + if ((spec.ckeId == 0 || spec.mask == 0) && (spec.waitCkeId == 0 || spec.waitMask == 0)) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + + instr->words[0] = PackSlots(static_cast(TILEXR_CCU_SET_CKE_HEADER), ClearTypeBit(spec.clearWait), + spec.ckeId, spec.mask); + instr->words[1] = PackSlots(spec.waitCkeId, spec.waitMask, 0, 0); + return TILEXR_SUCCESS; +} + +int TileXRCcuEncodeClearCke(const TileXRCcuCkeSpec& spec, TileXRCcuInstr* instr) +{ + if (ValidateInstrOutput(instr) != TILEXR_SUCCESS) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + if ((spec.ckeId == 0 || spec.mask == 0) && (spec.waitCkeId == 0 || spec.waitMask == 0)) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + + instr->words[0] = PackSlots(static_cast(TILEXR_CCU_CLEAR_CKE_HEADER), ClearTypeBit(spec.clearWait), + spec.ckeId, spec.mask); + instr->words[1] = PackSlots(spec.waitCkeId, spec.waitMask, 0, 0); + return TILEXR_SUCCESS; +} + +int TileXRCcuEncodeTransRmtMemToLocMem(const TileXRCcuMemTransferSpec& spec, TileXRCcuInstr* instr) +{ + if (ValidateInstrOutput(instr) != TILEXR_SUCCESS || ValidateTransferSpec(spec) != TILEXR_SUCCESS) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + + instr->words[0] = PackSlots( + static_cast(TILEXR_CCU_TRANS_RMT_MEM_TO_LOC_MEM_HEADER), + spec.localGsa, + spec.localXn, + spec.remoteGsa); + instr->words[1] = PackSlots(spec.remoteXn, spec.lengthXn, spec.channelId, TransferControlSlot(spec)); + instr->words[2] = PackSlots(0, 0, 0, TransferFlagSlot(spec)); + instr->words[3] = PackSlots(spec.setCkeId, spec.setCkeMask, spec.waitCkeId, spec.waitCkeMask); + return TILEXR_SUCCESS; +} + +int TileXRCcuEncodeTransLocMemToRmtMem(const TileXRCcuMemTransferSpec& spec, TileXRCcuInstr* instr) +{ + if (ValidateInstrOutput(instr) != TILEXR_SUCCESS || ValidateTransferSpec(spec) != TILEXR_SUCCESS) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + + instr->words[0] = PackSlots( + static_cast(TILEXR_CCU_TRANS_LOC_MEM_TO_RMT_MEM_HEADER), + spec.remoteGsa, + spec.remoteXn, + spec.localGsa); + instr->words[1] = PackSlots(spec.localXn, spec.lengthXn, spec.channelId, TransferControlSlot(spec)); + instr->words[2] = PackSlots(0, 0, 0, TransferFlagSlot(spec)); + instr->words[3] = PackSlots(spec.setCkeId, spec.setCkeMask, spec.waitCkeId, spec.waitCkeMask); + return TILEXR_SUCCESS; +} + +int TileXRCcuBuildSqeLoadProgram(uint16_t firstXnId, uint32_t argCount, std::vector* program) +{ + if (program == nullptr || firstXnId == 0 || argCount == 0 || argCount > TILEXR_CCU_SQE_ARGS_LEN) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + + program->clear(); + program->reserve(argCount); + for (uint32_t argId = 0; argId < argCount; ++argId) { + TileXRCcuInstr instr; + const uint32_t xnId = static_cast(firstXnId) + argId; + if (xnId > UINT16_MAX || TileXRCcuEncodeLoadSqeArgsToX(static_cast(xnId), argId, &instr) != + TILEXR_SUCCESS) { + program->clear(); + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + program->push_back(instr); + } + return TILEXR_SUCCESS; +} + +int TileXRCcuBuildSyncProgram(const std::vector& specs, std::vector* program) +{ + if (program == nullptr || specs.empty()) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + + program->clear(); + program->reserve(specs.size()); + for (const auto& spec : specs) { + TileXRCcuInstr instr; + if (TileXRCcuEncodeSyncXn(spec, &instr) != TILEXR_SUCCESS) { + program->clear(); + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + program->push_back(instr); + } + return TILEXR_SUCCESS; +} + +} // namespace TileXR diff --git a/src/comm/ccu/tilexr_ccu_microcode.h b/src/comm/ccu/tilexr_ccu_microcode.h new file mode 100644 index 00000000..fe8c0328 --- /dev/null +++ b/src/comm/ccu/tilexr_ccu_microcode.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2026 TileXR Project + * Licensed under the Apache License, Version 2.0 + */ + +#ifndef TILEXR_CCU_MICROCODE_H +#define TILEXR_CCU_MICROCODE_H + +#include "ccu/tilexr_ccu_runtime.h" + +#include +#include + +namespace TileXR { + +struct TileXRCcuInstr { + uint64_t words[4] = {}; +}; + +struct TileXRCcuSyncXnSpec { + uint16_t remoteXn = 0; + uint16_t localXn = 0; + uint16_t channelId = 0; + uint16_t notifyCke = 0; + uint16_t notifyMask = 0; + uint16_t setCkeId = 0; + uint16_t setCkeMask = 0; + uint16_t waitCkeId = 0; + uint16_t waitCkeMask = 0; + bool clearWait = true; +}; + +struct TileXRCcuSyncCkeSpec { + uint16_t remoteCke = 0; + uint16_t localCke = 0; + uint16_t localCkeMask = 0; + uint16_t channelId = 0; + uint16_t setCkeId = 0; + uint16_t setCkeMask = 0; + uint16_t waitCkeId = 0; + uint16_t waitCkeMask = 0; + bool clearWait = true; +}; + +struct TileXRCcuCkeSpec { + uint16_t ckeId = 0; + uint16_t mask = 0; + uint16_t waitCkeId = 0; + uint16_t waitMask = 0; + bool clearWait = true; +}; + +struct TileXRCcuMemTransferSpec { + uint16_t localGsa = 0; + uint16_t localXn = 0; + uint16_t remoteGsa = 0; + uint16_t remoteXn = 0; + uint16_t lengthXn = 0; + uint16_t channelId = 0; + uint16_t reduceDataType = 0; + uint16_t reduceOpCode = 0; + uint16_t setCkeId = 0; + uint16_t setCkeMask = 0; + uint16_t waitCkeId = 0; + uint16_t waitCkeMask = 0; + bool clearWait = true; + bool lengthFromXn = true; + bool reduceEnabled = false; +}; + +int TileXRCcuEncodeLoadSqeArgsToX(uint16_t xnId, uint32_t sqeArgId, TileXRCcuInstr* instr); + +int TileXRCcuEncodeLoadImdToXn(uint16_t xnId, uint64_t immediate, uint16_t secFlag, TileXRCcuInstr* instr); + +int TileXRCcuEncodeLoadImdToGsa(uint16_t gsaId, uint64_t immediate, TileXRCcuInstr* instr); + +int TileXRCcuEncodeSyncXn(const TileXRCcuSyncXnSpec& spec, TileXRCcuInstr* instr); + +int TileXRCcuEncodeSyncCke(const TileXRCcuSyncCkeSpec& spec, TileXRCcuInstr* instr); + +int TileXRCcuEncodeSetCke(const TileXRCcuCkeSpec& spec, TileXRCcuInstr* instr); + +int TileXRCcuEncodeClearCke(const TileXRCcuCkeSpec& spec, TileXRCcuInstr* instr); + +int TileXRCcuEncodeTransRmtMemToLocMem(const TileXRCcuMemTransferSpec& spec, TileXRCcuInstr* instr); + +int TileXRCcuEncodeTransLocMemToRmtMem(const TileXRCcuMemTransferSpec& spec, TileXRCcuInstr* instr); + +int TileXRCcuBuildSqeLoadProgram( + uint16_t firstXnId, + uint32_t argCount, + std::vector* program); + +int TileXRCcuBuildSyncProgram( + const std::vector& specs, + std::vector* program); + +} // namespace TileXR + +#endif // TILEXR_CCU_MICROCODE_H diff --git a/src/comm/ccu/tilexr_ccu_producer_plan.cpp b/src/comm/ccu/tilexr_ccu_producer_plan.cpp new file mode 100644 index 00000000..d87cd503 --- /dev/null +++ b/src/comm/ccu/tilexr_ccu_producer_plan.cpp @@ -0,0 +1,456 @@ +/* + * Copyright (c) 2026 TileXR Project + * Licensed under the Apache License, Version 2.0 + */ + +#include "ccu/tilexr_ccu_producer_plan.h" + +#include +#include +#include + +namespace TileXR { +namespace { + +constexpr uint32_t TILEXR_CCU_HCOMM_TASK1_PRELUDE_LOAD_ARG_COUNT = 2U; + +void ResetReport(TileXRCcuProducerPlanReport* report) +{ + if (report == nullptr) { + return; + } + report->syncResourceCount = 0; + report->taskCount = 0; + report->instructionCount = 0; + report->message.clear(); +} + +int Fail(TileXRCcuProducerPlanReport* report, const std::string& message) +{ + if (report != nullptr) { + report->message = message; + } + return TILEXR_ERROR_PARA_CHECK_FAIL; +} + +bool ContainsId(const TileXRCcuRange& range, uint8_t dieId, uint16_t id) +{ + if (range.dieId != dieId || range.num == 0) { + return false; + } + const uint32_t begin = range.startId; + const uint32_t end = begin + range.num; + return id >= begin && id < end; +} + +bool InstructionWindowContains(const TileXRCcuInstructionWindow& window, uint8_t dieId, uint16_t start, uint16_t count) +{ + if (window.dieId != dieId || window.missionCount == 0 || count == 0) { + return false; + } + const uint32_t begin = window.missionStartId; + const uint32_t end = begin + window.missionCount; + const uint32_t taskBegin = start; + const uint32_t taskEnd = taskBegin + count; + return taskBegin >= begin && taskEnd <= end; +} + +uint16_t EffectiveLocalWaitCke(const TileXRCcuSyncResource& resource) +{ + return resource.localWaitCke == 0 ? resource.notifyCke : resource.localWaitCke; +} + +uint16_t EffectiveRemoteNotifyMask(const TileXRCcuSyncResource& resource) +{ + return resource.remoteNotifyMask == 0 ? 1U : resource.remoteNotifyMask; +} + +uint16_t EffectiveLocalWaitMask(const TileXRCcuSyncResource& resource) +{ + return resource.localWaitMask == 0 ? 1U : resource.localWaitMask; +} + +uint16_t EffectiveSourceCkeMask(const TileXRCcuSyncResource& resource) +{ + return resource.sourceCkeMask == 0 ? 0xffffU : resource.sourceCkeMask; +} + +bool SyncCkeMode(TileXRCcuBarrierMode mode) +{ + return mode == TileXRCcuBarrierMode::SyncCke || + mode == TileXRCcuBarrierMode::SyncCkeSetWait || + mode == TileXRCcuBarrierMode::SyncCkePostOnly; +} + +bool SyncXnMode(TileXRCcuBarrierMode mode) +{ + return mode == TileXRCcuBarrierMode::SyncXn || + mode == TileXRCcuBarrierMode::SyncXnPostOnly || + mode == TileXRCcuBarrierMode::SyncXnLoadPostOnly; +} + +bool RequiresHcommStyleTask1Prelude(const TileXRCcuProducerPlan& plan) +{ + return plan.taskWindows.size() > 1 && SyncXnMode(plan.barrierMode); +} + +uint32_t SqeLoadXnOffset(uint32_t argId) +{ + return argId; +} + +uint32_t HcommStyleTask1PreludeLoadXnOffset(uint32_t argId) +{ + return argId; +} + +uint16_t HcommStylePreludeReserveXn(const TileXRCcuProducerPlan& plan) +{ + return static_cast( + static_cast(plan.kernelLocalXn.startId) + TILEXR_CCU_SQE_ARGS_LEN); +} + +bool HasKernelLocalGsa(const TileXRCcuProducerPlan& plan) +{ + return plan.kernelLocalGsa.dieId == plan.mission.dieId && plan.kernelLocalGsa.startId != 0 && + plan.kernelLocalGsa.num != 0; +} + +int AppendSqeLoadProgram( + const TileXRCcuProducerPlan& plan, + uint32_t argCount, + std::vector* program, + TileXRCcuProducerPlanReport* report) +{ + if (program == nullptr || plan.kernelLocalXn.startId == 0 || argCount == 0 || argCount > TILEXR_CCU_SQE_ARGS_LEN) { + return Fail(report, "invalid SQE argument load microcode request"); + } + + program->clear(); + program->reserve(argCount); + for (uint32_t argId = 0; argId < argCount; ++argId) { + const uint32_t xnId = static_cast(plan.kernelLocalXn.startId) + SqeLoadXnOffset(argId); + if (xnId > std::numeric_limits::max() || + !ContainsId(plan.kernelLocalXn, plan.mission.dieId, static_cast(xnId))) { + program->clear(); + return Fail(report, "SQE argument load XN is outside the kernel-local XN repository range"); + } + + TileXRCcuInstr instr; + if (TileXRCcuEncodeLoadSqeArgsToX(static_cast(xnId), argId, &instr) != TILEXR_SUCCESS) { + program->clear(); + return Fail(report, "failed to encode SQE argument load microcode"); + } + program->push_back(instr); + } + return TILEXR_SUCCESS; +} + +int ValidateMission(const TileXRCcuProducerPlan& plan, TileXRCcuProducerPlanReport* report) +{ + if (!plan.mission.installed || plan.mission.key == 0) { + return Fail(report, "missing installed mission/key for CCU producer plan"); + } + if (!ContainsId(plan.kernelLocalMission, plan.mission.dieId, plan.mission.missionId)) { + return Fail(report, "mission id is outside the kernel-local mission repository range"); + } + return TILEXR_SUCCESS; +} + +int ValidateInstructionWindow(const TileXRCcuProducerPlan& plan, TileXRCcuProducerPlanReport* report) +{ + if (plan.instructionWindow.repositoryCount == 0 || plan.instructionWindow.missionCount == 0) { + return Fail(report, "missing instruction repository window for CCU producer plan"); + } + if (plan.instructionWindow.dieId != plan.mission.dieId) { + return Fail(report, "instruction repository die does not match mission die"); + } + const uint32_t repositoryEnd = plan.instructionWindow.repositoryStartId + plan.instructionWindow.repositoryCount; + const uint32_t missionEnd = plan.instructionWindow.missionStartId + plan.instructionWindow.missionCount; + if (plan.instructionWindow.missionStartId < plan.instructionWindow.repositoryStartId || missionEnd > repositoryEnd) { + return Fail(report, "mission instruction window is outside the instruction repository range"); + } + return TILEXR_SUCCESS; +} + +int ValidateKernelLocalRepositories(const TileXRCcuProducerPlan& plan, TileXRCcuProducerPlanReport* report) +{ + if (plan.kernelLocalXn.dieId != plan.mission.dieId || plan.kernelLocalXn.num == 0) { + return Fail(report, "missing kernel-local XN repository range"); + } + if (plan.kernelLocalCke.dieId != plan.mission.dieId || plan.kernelLocalCke.num == 0) { + return Fail(report, "missing kernel-local CKE repository range"); + } + return TILEXR_SUCCESS; +} + +int ValidateSyncResources(const TileXRCcuProducerPlan& plan, TileXRCcuProducerPlanReport* report) +{ + if (plan.syncResources.empty()) { + return Fail(report, "missing CCU sync resources"); + } + std::set channelIds; + for (const auto& resource : plan.syncResources) { + if (resource.dieId != plan.mission.dieId) { + return Fail(report, "sync resource die does not match mission die"); + } + if (resource.channelId == 0) { + return Fail(report, "missing channel id for sync resource"); + } + if (!channelIds.insert(resource.channelId).second) { + return Fail(report, "duplicate channel id for sync resource"); + } + if (!ContainsId(plan.kernelLocalXn, resource.dieId, resource.localXn)) { + return Fail(report, "local XN is outside the kernel-local XN repository range"); + } + if (resource.remoteXn == 0) { + return Fail(report, "missing channel-bound remote XN"); + } + if (resource.notifyCke == 0) { + return Fail(report, "missing remote notify CKE resource"); + } + if (EffectiveRemoteNotifyMask(resource) == 0) { + return Fail(report, "missing remote notify CKE mask"); + } + const uint16_t localWaitCke = EffectiveLocalWaitCke(resource); + if (localWaitCke == 0) { + return Fail(report, "missing local wait CKE resource"); + } + if (EffectiveLocalWaitMask(resource) == 0) { + return Fail(report, "missing local wait CKE mask"); + } + if (resource.localWaitCke != 0 && !ContainsId(plan.kernelLocalCke, resource.dieId, localWaitCke)) { + return Fail(report, "local wait CKE is outside the kernel-local CKE repository range"); + } + if (SyncCkeMode(plan.barrierMode)) { + if (resource.sourceCke == 0) { + return Fail(report, "missing source CKE resource for SyncCKE barrier"); + } + if (EffectiveSourceCkeMask(resource) == 0) { + return Fail(report, "missing source CKE mask for SyncCKE barrier"); + } + if (!ContainsId(plan.kernelLocalCke, resource.dieId, resource.sourceCke)) { + return Fail(report, "source CKE is outside the kernel-local CKE repository range"); + } + } + if (resource.bindingCount == 0) { + return Fail(report, "missing channel variable binding for sync resource"); + } + } + return TILEXR_SUCCESS; +} + +int ValidateTasks(const TileXRCcuProducerPlan& plan, TileXRCcuProducerPlanReport* report) +{ + if (plan.taskWindows.empty()) { + return Fail(report, "missing CCU task windows"); + } + for (const auto& window : plan.taskWindows) { + if (window.dieId != plan.mission.dieId) { + return Fail(report, "task die does not match mission die"); + } + if (!InstructionWindowContains(plan.instructionWindow, window.dieId, window.instStartId, window.instCnt)) { + return Fail(report, "task instruction range is outside the loaded mission instruction window"); + } + if (window.argSize != 1 && window.argSize != TILEXR_CCU_SQE_ARGS_LEN) { + return Fail(report, "task argSize must match a supported CCU SQE payload shape"); + } + if (window.args.size() > TILEXR_CCU_SQE_ARGS_LEN) { + return Fail(report, "task args exceed the CCU SQE payload capacity"); + } + } + return TILEXR_SUCCESS; +} + +void FillReport(const TileXRCcuProducerPlan& plan, TileXRCcuProducerPlanReport* report) +{ + if (report == nullptr) { + return; + } + report->syncResourceCount = static_cast(plan.syncResources.size()); + report->taskCount = static_cast(plan.taskWindows.size()); + report->instructionCount = plan.instructionWindow.repositoryCount; + report->message = "ok"; +} + +int AppendHcommStyleTask1Prelude( + const TileXRCcuProducerPlan& plan, + std::vector* program, + TileXRCcuProducerPlanReport* report) +{ + if (!RequiresHcommStyleTask1Prelude(plan)) { + return TILEXR_SUCCESS; + } + + const uint16_t reserveXn = HcommStylePreludeReserveXn(plan); + if (!ContainsId(plan.kernelLocalXn, plan.mission.dieId, reserveXn)) { + return Fail(report, "missing reserve XN for hcomm-style task1 prelude"); + } + + for (uint32_t argId = 0; argId < TILEXR_CCU_HCOMM_TASK1_PRELUDE_LOAD_ARG_COUNT; ++argId) { + const uint32_t xnId = + static_cast(plan.kernelLocalXn.startId) + HcommStyleTask1PreludeLoadXnOffset(argId); + if (xnId > std::numeric_limits::max() || + !ContainsId(plan.kernelLocalXn, plan.mission.dieId, static_cast(xnId))) { + return Fail(report, "missing load-arg XN for hcomm-style task1 prelude"); + } + TileXRCcuInstr instr; + if (TileXRCcuEncodeLoadSqeArgsToX(static_cast(xnId), argId, &instr) != TILEXR_SUCCESS) { + return Fail(report, "failed to encode hcomm-style task1 load-arg prelude"); + } + program->push_back(instr); + } + + TileXRCcuInstr reserveXnInstr; + if (TileXRCcuEncodeLoadImdToXn(reserveXn, 0, 0, &reserveXnInstr) != TILEXR_SUCCESS) { + return Fail(report, "failed to encode hcomm-style task1 reserve XN prelude"); + } + program->push_back(reserveXnInstr); + + TileXRCcuInstr reserveAddrInstr; + if (HasKernelLocalGsa(plan)) { + if (TileXRCcuEncodeLoadImdToGsa(plan.kernelLocalGsa.startId, 0, &reserveAddrInstr) != TILEXR_SUCCESS) { + return Fail(report, "failed to encode hcomm-style task1 reserve GSA prelude"); + } + } else if (TileXRCcuEncodeLoadImdToXn(reserveXn, 0, 0, &reserveAddrInstr) != TILEXR_SUCCESS) { + return Fail(report, "failed to encode hcomm-style task1 nop prelude"); + } + program->push_back(reserveAddrInstr); + + const TileXRCcuSyncResource& firstResource = plan.syncResources.front(); + TileXRCcuCkeSpec notifyInit; + notifyInit.ckeId = firstResource.notifyCke; + notifyInit.mask = EffectiveRemoteNotifyMask(firstResource); + notifyInit.clearWait = false; + TileXRCcuInstr notifyInstr; + if (TileXRCcuEncodeSetCke(notifyInit, ¬ifyInstr) != TILEXR_SUCCESS) { + return Fail(report, "failed to encode hcomm-style task1 notify CKE prelude"); + } + program->push_back(notifyInstr); + return TILEXR_SUCCESS; +} + +} // namespace + +int TileXRCcuValidateProducerPlan(const TileXRCcuProducerPlan& plan, TileXRCcuProducerPlanReport* report) +{ + ResetReport(report); + + int ret = ValidateMission(plan, report); + if (ret != TILEXR_SUCCESS) { + return ret; + } + ret = ValidateInstructionWindow(plan, report); + if (ret != TILEXR_SUCCESS) { + return ret; + } + ret = ValidateKernelLocalRepositories(plan, report); + if (ret != TILEXR_SUCCESS) { + return ret; + } + ret = ValidateSyncResources(plan, report); + if (ret != TILEXR_SUCCESS) { + return ret; + } + ret = ValidateTasks(plan, report); + if (ret != TILEXR_SUCCESS) { + return ret; + } + + FillReport(plan, report); + return TILEXR_SUCCESS; +} + +int TileXRCcuBuildTasks( + const TileXRCcuProducerPlan& plan, + std::vector* tasks, + TileXRCcuProducerPlanReport* report) +{ + if (tasks == nullptr) { + ResetReport(report); + return Fail(report, "missing output task vector"); + } + + int ret = TileXRCcuValidateProducerPlan(plan, report); + if (ret != TILEXR_SUCCESS) { + return ret; + } + + tasks->clear(); + tasks->reserve(plan.taskWindows.size()); + for (const auto& window : plan.taskWindows) { + TileXRCcuTask task; + task.dieId = window.dieId; + task.missionId = plan.mission.missionId; + task.timeout = TILEXR_CCU_DEFAULT_TASK_TIMEOUT_SEC; + task.instStartId = window.instStartId; + task.instCnt = window.instCnt; + task.key = plan.mission.key; + task.argSize = window.argSize; + std::copy(window.args.begin(), window.args.end(), task.args); + tasks->push_back(task); + } + + return TILEXR_SUCCESS; +} + +int TileXRCcuBuildMicrocode( + const TileXRCcuProducerPlan& plan, + TileXRCcuProgram* program, + TileXRCcuProducerPlanReport* report) +{ + if (program == nullptr) { + ResetReport(report); + return Fail(report, "missing output CCU program"); + } + + int ret = TileXRCcuValidateProducerPlan(plan, report); + if (ret != TILEXR_SUCCESS) { + return ret; + } + + program->sqeLoad.clear(); + program->sync.clear(); + if (plan.taskWindows.size() > 1) { + if (AppendSqeLoadProgram(plan, TILEXR_CCU_SQE_ARGS_LEN, &program->sqeLoad, report) != TILEXR_SUCCESS) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + } + + std::vector barrierSpecs; + barrierSpecs.reserve(plan.syncResources.size()); + for (const auto& resource : plan.syncResources) { + TileXRCcuBarrierSyncSpec spec; + spec.remoteXn = resource.remoteXn; + spec.localXn = resource.localXn; + spec.channelId = resource.channelId; + spec.remoteNotifyCke = resource.notifyCke; + spec.remoteNotifyMask = EffectiveRemoteNotifyMask(resource); + spec.localWaitCke = EffectiveLocalWaitCke(resource); + spec.localWaitMask = EffectiveLocalWaitMask(resource); + spec.sourceCke = resource.sourceCke; + spec.sourceCkeMask = EffectiveSourceCkeMask(resource); + spec.clearLocalWait = true; + barrierSpecs.push_back(spec); + } + + TileXRCcuBarrierProgramReport barrierReport; + if (AppendHcommStyleTask1Prelude(plan, &program->sync, report) != TILEXR_SUCCESS) { + program->sqeLoad.clear(); + program->sync.clear(); + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + + std::vector barrierProgram; + if (TileXRCcuBuildBarrierProgram(barrierSpecs, &barrierProgram, &barrierReport, plan.barrierMode) != + TILEXR_SUCCESS) { + program->sqeLoad.clear(); + program->sync.clear(); + return Fail(report, barrierReport.message.empty() ? "failed to build sync microcode" : barrierReport.message); + } + program->sync.insert(program->sync.end(), barrierProgram.begin(), barrierProgram.end()); + + return TILEXR_SUCCESS; +} + +} // namespace TileXR diff --git a/src/comm/ccu/tilexr_ccu_producer_plan.h b/src/comm/ccu/tilexr_ccu_producer_plan.h new file mode 100644 index 00000000..af4403c2 --- /dev/null +++ b/src/comm/ccu/tilexr_ccu_producer_plan.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2026 TileXR Project + * Licensed under the Apache License, Version 2.0 + */ + +#ifndef TILEXR_CCU_PRODUCER_PLAN_H +#define TILEXR_CCU_PRODUCER_PLAN_H + +#include "ccu/tilexr_ccu_barrier_program.h" +#include "ccu/tilexr_ccu_runtime.h" + +#include +#include +#include + +namespace TileXR { + +struct TileXRCcuRange { + uint8_t dieId = 0; + uint16_t startId = 0; + uint16_t num = 0; +}; + +struct TileXRCcuMission { + uint8_t dieId = 0; + uint8_t missionId = 0; + uint32_t key = 0; + bool installed = false; +}; + +struct TileXRCcuInstructionWindow { + uint8_t dieId = 0; + uint16_t repositoryStartId = 0; + uint16_t repositoryCount = 0; + uint16_t missionStartId = 0; + uint16_t missionCount = 0; +}; + +struct TileXRCcuSyncResource { + uint8_t dieId = 0; + uint16_t localXn = 0; + uint16_t remoteXn = 0; + uint16_t notifyCke = 0; + uint16_t channelId = 0; + uint16_t bindingCount = 0; + uint16_t localWaitCke = 0; + uint16_t localWaitMask = 1; + uint16_t remoteNotifyMask = 1; + uint16_t sourceCke = 0; + uint16_t sourceCkeMask = 0xffff; +}; + +struct TileXRCcuTaskWindow { + uint8_t dieId = 0; + uint16_t instStartId = 0; + uint16_t instCnt = 0; + uint32_t argSize = 0; + std::vector args; +}; + +struct TileXRCcuProducerPlan { + TileXRCcuMission mission; + TileXRCcuRange kernelLocalXn; + TileXRCcuRange kernelLocalGsa; + TileXRCcuRange kernelLocalCke; + TileXRCcuRange kernelLocalMission; + TileXRCcuInstructionWindow instructionWindow; + std::vector syncResources; + std::vector taskWindows; + TileXRCcuBarrierMode barrierMode = TileXRCcuBarrierMode::SyncXn; +}; + +struct TileXRCcuProgram { + std::vector sqeLoad; + std::vector sync; +}; + +struct TileXRCcuProducerPlanReport { + uint32_t syncResourceCount = 0; + uint32_t taskCount = 0; + uint32_t instructionCount = 0; + std::string message; +}; + +int TileXRCcuValidateProducerPlan(const TileXRCcuProducerPlan& plan, TileXRCcuProducerPlanReport* report); + +int TileXRCcuBuildTasks( + const TileXRCcuProducerPlan& plan, + std::vector* tasks, + TileXRCcuProducerPlanReport* report); + +int TileXRCcuBuildMicrocode( + const TileXRCcuProducerPlan& plan, + TileXRCcuProgram* program, + TileXRCcuProducerPlanReport* report); + +} // namespace TileXR + +#endif // TILEXR_CCU_PRODUCER_PLAN_H diff --git a/src/comm/ccu/tilexr_ccu_resource_allocator.cpp b/src/comm/ccu/tilexr_ccu_resource_allocator.cpp new file mode 100644 index 00000000..e9bb407b --- /dev/null +++ b/src/comm/ccu/tilexr_ccu_resource_allocator.cpp @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2026 TileXR Project + * Licensed under the Apache License, Version 2.0 + */ + +#include "ccu/tilexr_ccu_resource_allocator.h" + +#include +#include + +namespace TileXR { +namespace { + +constexpr const char* TILEXR_CCU_HCOMM_DERIVED_PROVIDER = "tilexr-hcomm-derived-resource-allocator"; +constexpr uint32_t TILEXR_CCU_HCOMM_TASK1_PRELUDE_INSTRUCTION_COUNT = 5U; +constexpr uint32_t TILEXR_CCU_HCOMM_TASK1_PRELUDE_RESERVED_XN_COUNT = 1U; + +void ResetReport(TileXRCcuResourceAllocatorReport* report) +{ + if (report != nullptr) { + *report = TileXRCcuResourceAllocatorReport{}; + } +} + +int Fail(TileXRCcuResourceAllocatorReport* report, const std::string& message) +{ + if (report != nullptr) { + report->message = message; + } + return TILEXR_ERROR_PARA_CHECK_FAIL; +} + +bool AddWouldOverflow(uint16_t start, uint16_t count) +{ + return static_cast(start) + count > static_cast(std::numeric_limits::max()) + 1U; +} + +bool ContainsRange(uint16_t outerStart, uint16_t outerCount, uint16_t innerStart, uint32_t innerCount) +{ + if (outerCount == 0 || innerCount == 0) { + return false; + } + const uint32_t outerBegin = outerStart; + const uint32_t outerEnd = outerBegin + outerCount; + const uint32_t innerBegin = innerStart; + const uint32_t innerEnd = innerBegin + innerCount; + return innerBegin >= outerBegin && innerEnd <= outerEnd; +} + +TileXRCcuRange MakeRange(uint8_t dieId, uint16_t startId, uint16_t count) +{ + TileXRCcuRange range; + range.dieId = dieId; + range.startId = startId; + range.num = count; + return range; +} + +void FillReport( + const TileXRCcuResourceAllocation& allocation, + const TileXRCcuResourceRequest& request, + TileXRCcuResourceAllocatorReport* report) +{ + if (report == nullptr) { + return; + } + report->missionAllocated = allocation.mission.num; + report->repositoryAllocated = allocation.repository.num; + report->localXnAllocated = allocation.localXn.num; + report->localGsaAllocated = allocation.localGsa.num; + report->remoteXnAllocated = allocation.remoteXn.num; + report->notifyCkeAllocated = allocation.notifyCke.num; + report->channelBindingsAllocated = request.syncResourceCount * request.bindingsPerSyncResource; + report->localWaitCkeAllocated = allocation.localWaitCke.num; + report->remoteNotifyCkeAllocated = allocation.remoteNotifyCke.num; + report->sourceCkeAllocated = allocation.sourceCke.num; + report->message = "ok"; +} + +uint16_t CheckedU16(uint32_t value) +{ + return static_cast(std::min(value, std::numeric_limits::max())); +} + +bool SyncXnMode(TileXRCcuBarrierMode mode) +{ + return mode == TileXRCcuBarrierMode::SyncXn || + mode == TileXRCcuBarrierMode::SyncXnPostOnly || + mode == TileXRCcuBarrierMode::SyncXnLoadPostOnly; +} + +uint32_t RequiredSqeLoadXnCount(uint32_t sqeArgCount, bool hcommStyleTask1Prelude) +{ + if (sqeArgCount == 0) { + return 0; + } + + uint32_t count = hcommStyleTask1Prelude ? + std::max(sqeArgCount, TILEXR_CCU_SQE_ARGS_LEN) + + TILEXR_CCU_HCOMM_TASK1_PRELUDE_RESERVED_XN_COUNT : + sqeArgCount; + return count; +} + +} // namespace + +bool TileXRCcuResourceAllocator::HasCapacity(const Cursor& cursor, uint32_t count) const +{ + return count <= std::numeric_limits::max() && + static_cast(cursor.used) + count <= cursor.count; +} + +uint16_t TileXRCcuResourceAllocator::CursorNext(const Cursor& cursor) const +{ + return static_cast(cursor.start + cursor.used); +} + +int TileXRCcuResourceAllocator::Init(const TileXRCcuResourceSpec& spec) +{ + const uint16_t missionInstructionStart = + spec.missionInstructionStartId == 0 ? spec.instructionStartId : spec.missionInstructionStartId; + const uint16_t localWaitCkeStart = + spec.localWaitCkeCount == 0 ? spec.ckeStartId : spec.localWaitCkeStartId; + const uint16_t localWaitCkeCount = + spec.localWaitCkeCount == 0 ? spec.ckeCount : spec.localWaitCkeCount; + const uint16_t remoteNotifyCkeStart = + spec.remoteNotifyCkeCount == 0 ? spec.ckeStartId : spec.remoteNotifyCkeStartId; + const uint16_t remoteNotifyCkeCount = + spec.remoteNotifyCkeCount == 0 ? spec.ckeCount : spec.remoteNotifyCkeCount; + const bool splitRemoteXn = spec.remoteXnCount != 0; + + if (spec.missionKey == 0 || spec.missionCount == 0 || spec.instructionCount == 0 || + spec.xnCount == 0 || localWaitCkeCount == 0 || remoteNotifyCkeCount == 0 || + spec.channelCount == 0 || + AddWouldOverflow(spec.missionStartId, spec.missionCount) || + AddWouldOverflow(spec.instructionStartId, spec.instructionCount) || + !ContainsRange(spec.instructionStartId, spec.instructionCount, missionInstructionStart, 1) || + AddWouldOverflow(spec.xnStartId, spec.xnCount) || + (spec.gsaCount != 0 && (spec.gsaStartId == 0 || AddWouldOverflow(spec.gsaStartId, spec.gsaCount))) || + (splitRemoteXn && (spec.remoteXnStartId == 0 || AddWouldOverflow(spec.remoteXnStartId, spec.remoteXnCount))) || + AddWouldOverflow(localWaitCkeStart, localWaitCkeCount) || + AddWouldOverflow(remoteNotifyCkeStart, remoteNotifyCkeCount) || + AddWouldOverflow(spec.channelStartId, spec.channelCount)) { + initialized_ = false; + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + + spec_ = spec; + mission_ = {spec.missionStartId, spec.missionCount, 0}; + repository_ = {spec.instructionStartId, spec.instructionCount, 0}; + xn_ = {spec.xnStartId, spec.xnCount, 0}; + gsa_ = {spec.gsaStartId, spec.gsaCount, 0}; + remoteXn_ = splitRemoteXn ? Cursor{spec.remoteXnStartId, spec.remoteXnCount, 0} : Cursor{}; + localWaitCke_ = {localWaitCkeStart, localWaitCkeCount, 0}; + remoteNotifyCke_ = {remoteNotifyCkeStart, remoteNotifyCkeCount, 0}; + channel_ = {spec.channelStartId, spec.channelCount, 0}; + nextReceiptId_ = 1; + active_.clear(); + initialized_ = true; + return TILEXR_SUCCESS; +} + +int TileXRCcuResourceAllocator::Allocate( + const TileXRCcuResourceRequest& request, + TileXRCcuProducerPlan* plan, + TileXRCcuResourceAllocation* allocation, + TileXRCcuResourceAllocatorReport* report) +{ + ResetReport(report); + if (!initialized_) { + return Fail(report, "CCU resource allocator is not initialized"); + } + if (plan == nullptr || allocation == nullptr) { + return Fail(report, "missing CCU resource allocation output"); + } + *plan = TileXRCcuProducerPlan{}; + *allocation = TileXRCcuResourceAllocation{}; + + if (request.sqeArgCount > TILEXR_CCU_SQE_ARGS_LEN) { + return Fail(report, "invalid SQE argument count"); + } + if (request.syncResourceCount == 0 || request.syncInstructionCount == 0 || + request.bindingsPerSyncResource == 0) { + return Fail(report, "invalid CCU sync resource request"); + } + if (request.syncResourceCount > std::numeric_limits::max() / 2U) { + return Fail(report, "barrier sync instruction window is too small for CCU post and wait microcode"); + } + const bool syncCkeMode = request.barrierMode == TileXRCcuBarrierMode::SyncCke || + request.barrierMode == TileXRCcuBarrierMode::SyncCkeSetWait || + request.barrierMode == TileXRCcuBarrierMode::SyncCkePostOnly; + const bool hcommStyleTask1Prelude = request.sqeArgCount != 0 && SyncXnMode(request.barrierMode); + const bool postOnly = request.barrierMode == TileXRCcuBarrierMode::SyncXnPostOnly || + request.barrierMode == TileXRCcuBarrierMode::SyncCkePostOnly || + request.barrierMode == TileXRCcuBarrierMode::LocalCkePostOnly; + const uint32_t requiredPostWaitInstructionCount = + postOnly ? request.syncResourceCount : request.syncResourceCount * 2U; + const uint32_t sourceCkeInitCount = syncCkeMode ? 1U : 0U; + const uint32_t sourceCkeResourceCount = syncCkeMode ? 1U : 0U; + const uint32_t task1PreludeInstructionCount = + hcommStyleTask1Prelude ? TILEXR_CCU_HCOMM_TASK1_PRELUDE_INSTRUCTION_COUNT : 0U; + const uint32_t requiredBarrierInstructionCount = + requiredPostWaitInstructionCount + sourceCkeInitCount + task1PreludeInstructionCount; + if (request.syncInstructionCount < requiredBarrierInstructionCount) { + return Fail(report, + hcommStyleTask1Prelude ? + "barrier sync instruction window is too small for hcomm-style task1 prelude and CCU post/wait microcode" : + "barrier sync instruction window is too small for CCU post and wait microcode"); + } + + const uint32_t localSqeXnCount = RequiredSqeLoadXnCount(request.sqeArgCount, hcommStyleTask1Prelude); + const uint32_t localXnCount = std::max(localSqeXnCount, request.syncResourceCount); + const uint32_t remoteXnCount = request.syncResourceCount; + const uint32_t localGsaCount = hcommStyleTask1Prelude && spec_.gsaCount != 0 ? 1U : 0U; + const uint32_t totalXnCount = localXnCount + remoteXnCount; + const uint32_t localWaitCkeCount = request.syncResourceCount; + const uint32_t remoteNotifyCkeCount = request.syncResourceCount; + const uint32_t localCkeCount = localWaitCkeCount + sourceCkeResourceCount; + const uint16_t repositoryStart = CursorNext(repository_); + const uint16_t missionInstructionStart = + spec_.missionInstructionStartId == 0 ? repositoryStart : spec_.missionInstructionStartId; + if (!ContainsRange(repositoryStart, repository_.count, missionInstructionStart, 1)) { + return Fail(report, "mission instruction start is outside instruction repository resources"); + } + const uint32_t repositoryPrefixCount = + static_cast(missionInstructionStart) - static_cast(repositoryStart); + const uint32_t missionInstructionCount = request.sqeArgCount + request.syncInstructionCount; + const uint32_t repositoryCount = repositoryPrefixCount + missionInstructionCount; + const uint32_t channelCount = request.syncResourceCount; + + if (!HasCapacity(mission_, 1)) { + return Fail(report, "insufficient mission resources"); + } + if (!HasCapacity(repository_, repositoryCount)) { + return Fail(report, "insufficient instruction repository resources"); + } + const bool splitRemoteXn = remoteXn_.count != 0; + if (!HasCapacity(xn_, splitRemoteXn ? localXnCount : totalXnCount)) { + return Fail(report, "insufficient XN resources"); + } + if (localGsaCount != 0 && !HasCapacity(gsa_, localGsaCount)) { + return Fail(report, "insufficient GSA resources"); + } + if (splitRemoteXn && !HasCapacity(remoteXn_, remoteXnCount)) { + return Fail(report, "insufficient remote XN resources"); + } + if (!HasCapacity(localWaitCke_, localCkeCount)) { + return Fail(report, "insufficient CKE resources"); + } + if (!HasCapacity(remoteNotifyCke_, remoteNotifyCkeCount)) { + return Fail(report, "insufficient remote notify CKE resources"); + } + if (!HasCapacity(channel_, channelCount)) { + return Fail(report, "insufficient channel resources"); + } + + const uint16_t missionStart = CursorNext(mission_); + const uint16_t localXnStart = CursorNext(xn_); + const uint16_t localGsaStart = CursorNext(gsa_); + const uint16_t remoteXnStart = splitRemoteXn ? + CursorNext(remoteXn_) : + static_cast(localXnStart + localXnCount); + const uint16_t localWaitCkeStart = CursorNext(localWaitCke_); + const uint16_t sourceCkeStart = static_cast(localWaitCkeStart + localWaitCkeCount); + const uint16_t remoteNotifyCkeStart = CursorNext(remoteNotifyCke_); + const uint16_t channelStart = CursorNext(channel_); + + TileXRCcuResourceAllocation result; + result.receiptId = nextReceiptId_++; + result.packageProvider = TILEXR_CCU_HCOMM_DERIVED_PROVIDER; + result.mission = MakeRange(spec_.dieId, missionStart, 1); + result.repository = MakeRange(spec_.dieId, repositoryStart, CheckedU16(repositoryCount)); + result.localXn = MakeRange(spec_.dieId, localXnStart, CheckedU16(localXnCount)); + result.localGsa = MakeRange(spec_.dieId, localGsaStart, CheckedU16(localGsaCount)); + result.remoteXn = MakeRange(spec_.dieId, remoteXnStart, CheckedU16(remoteXnCount)); + result.notifyCke = MakeRange(spec_.dieId, remoteNotifyCkeStart, CheckedU16(remoteNotifyCkeCount)); + result.channels = MakeRange(spec_.dieId, channelStart, CheckedU16(channelCount)); + result.localWaitCke = MakeRange(spec_.dieId, localWaitCkeStart, CheckedU16(localWaitCkeCount)); + result.remoteNotifyCke = result.notifyCke; + result.sourceCke = MakeRange(spec_.dieId, sourceCkeStart, CheckedU16(sourceCkeResourceCount)); + + TileXRCcuProducerPlan generated; + generated.barrierMode = request.barrierMode; + generated.mission = {spec_.dieId, static_cast(missionStart), spec_.missionKey, true}; + generated.kernelLocalMission = result.mission; + generated.kernelLocalXn = result.localXn; + generated.kernelLocalGsa = result.localGsa; + generated.kernelLocalCke = MakeRange(spec_.dieId, localWaitCkeStart, CheckedU16(localCkeCount)); + generated.instructionWindow = { + spec_.dieId, + result.repository.startId, + result.repository.num, + missionInstructionStart, + CheckedU16(missionInstructionCount), + }; + + for (uint32_t i = 0; i < request.syncResourceCount; ++i) { + TileXRCcuSyncResource resource; + resource.dieId = spec_.dieId; + resource.localXn = static_cast(result.localXn.startId + i); + resource.remoteXn = static_cast(static_cast(result.remoteXn.startId) + i); + resource.notifyCke = static_cast(static_cast(result.remoteNotifyCke.startId) + i); + resource.channelId = static_cast(static_cast(result.channels.startId) + i); + resource.bindingCount = CheckedU16(request.bindingsPerSyncResource); + resource.localWaitCke = static_cast(result.localWaitCke.startId + i); + resource.localWaitMask = 1; + resource.remoteNotifyMask = 1; + if (syncCkeMode) { + resource.sourceCke = result.sourceCke.startId; + resource.sourceCkeMask = 0xffff; + } + generated.syncResources.push_back(resource); + } + + if (request.sqeArgCount != 0) { + TileXRCcuTaskWindow sqeLoadTask; + sqeLoadTask.dieId = spec_.dieId; + sqeLoadTask.instStartId = missionInstructionStart; + sqeLoadTask.instCnt = CheckedU16(request.sqeArgCount); + sqeLoadTask.argSize = TILEXR_CCU_SQE_ARGS_LEN; + generated.taskWindows.push_back(sqeLoadTask); + } + + TileXRCcuTaskWindow syncTask; + syncTask.dieId = spec_.dieId; + syncTask.instStartId = static_cast(missionInstructionStart + request.sqeArgCount); + syncTask.instCnt = CheckedU16(request.syncInstructionCount); + syncTask.argSize = TILEXR_CCU_SQE_ARGS_LEN; + generated.taskWindows.push_back(syncTask); + + TileXRCcuProducerPlanReport planReport; + if (TileXRCcuValidateProducerPlan(generated, &planReport) != TILEXR_SUCCESS) { + return Fail(report, planReport.message); + } + + mission_.used = static_cast(mission_.used + result.mission.num); + repository_.used = static_cast(repository_.used + result.repository.num); + xn_.used = static_cast(xn_.used + result.localXn.num + (splitRemoteXn ? 0 : result.remoteXn.num)); + gsa_.used = static_cast(gsa_.used + result.localGsa.num); + if (splitRemoteXn) { + remoteXn_.used = static_cast(remoteXn_.used + result.remoteXn.num); + } + localWaitCke_.used = static_cast(localWaitCke_.used + result.localWaitCke.num + result.sourceCke.num); + remoteNotifyCke_.used = static_cast(remoteNotifyCke_.used + result.remoteNotifyCke.num); + channel_.used = static_cast(channel_.used + result.channels.num); + + ActiveAllocation active; + active.allocation = result; + active.missionUsed = result.mission.num; + active.repositoryUsed = result.repository.num; + active.localXnUsed = result.localXn.num; + active.localGsaUsed = result.localGsa.num; + active.remoteXnUsed = result.remoteXn.num; + active.notifyCkeUsed = result.notifyCke.num; + active.channelUsed = result.channels.num; + active.localWaitCkeUsed = result.localWaitCke.num; + active.remoteNotifyCkeUsed = result.remoteNotifyCke.num; + active.sourceCkeUsed = result.sourceCke.num; + active_[result.receiptId] = active; + + *plan = generated; + *allocation = result; + FillReport(result, request, report); + return TILEXR_SUCCESS; +} + +int TileXRCcuResourceAllocator::Release(uint64_t receiptId) +{ + const auto it = active_.find(receiptId); + if (it == active_.end()) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + active_.erase(it); + return TILEXR_SUCCESS; +} + +} // namespace TileXR diff --git a/src/comm/ccu/tilexr_ccu_resource_allocator.h b/src/comm/ccu/tilexr_ccu_resource_allocator.h new file mode 100644 index 00000000..706ff2b4 --- /dev/null +++ b/src/comm/ccu/tilexr_ccu_resource_allocator.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2026 TileXR Project + * Licensed under the Apache License, Version 2.0 + */ + +#ifndef TILEXR_CCU_RESOURCE_ALLOCATOR_H +#define TILEXR_CCU_RESOURCE_ALLOCATOR_H + +#include "ccu/tilexr_ccu_producer_plan.h" + +#include +#include +#include + +namespace TileXR { + +struct TileXRCcuResourceSpec { + uint8_t dieId = 0; + uint32_t missionKey = 0; + uint16_t missionStartId = 0; + uint16_t missionCount = 0; + uint16_t instructionStartId = 0; + uint16_t missionInstructionStartId = 0; + uint16_t instructionCount = 0; + uint16_t xnStartId = 0; + uint16_t xnCount = 0; + uint16_t gsaStartId = 0; + uint16_t gsaCount = 0; + uint16_t remoteXnStartId = 0; + uint16_t remoteXnCount = 0; + uint16_t ckeStartId = 0; + uint16_t ckeCount = 0; + uint16_t channelStartId = 0; + uint16_t channelCount = 0; + uint16_t localWaitCkeStartId = 0; + uint16_t localWaitCkeCount = 0; + uint16_t remoteNotifyCkeStartId = 0; + uint16_t remoteNotifyCkeCount = 0; +}; + +struct TileXRCcuResourceRequest { + uint32_t sqeArgCount = TILEXR_CCU_SQE_ARGS_LEN; + uint32_t syncResourceCount = 0; + uint32_t syncInstructionCount = 0; + uint32_t bindingsPerSyncResource = 1; + TileXRCcuBarrierMode barrierMode = TileXRCcuBarrierMode::SyncXn; +}; + +struct TileXRCcuResourceAllocation { + uint64_t receiptId = 0; + std::string packageProvider; + TileXRCcuRange mission; + TileXRCcuRange repository; + TileXRCcuRange localXn; + TileXRCcuRange localGsa; + TileXRCcuRange remoteXn; + TileXRCcuRange notifyCke; + TileXRCcuRange channels; + TileXRCcuRange localWaitCke; + TileXRCcuRange remoteNotifyCke; + TileXRCcuRange sourceCke; +}; + +struct TileXRCcuResourceAllocatorReport { + uint32_t missionAllocated = 0; + uint32_t repositoryAllocated = 0; + uint32_t localXnAllocated = 0; + uint32_t localGsaAllocated = 0; + uint32_t remoteXnAllocated = 0; + uint32_t notifyCkeAllocated = 0; + uint32_t channelBindingsAllocated = 0; + uint32_t localWaitCkeAllocated = 0; + uint32_t remoteNotifyCkeAllocated = 0; + uint32_t sourceCkeAllocated = 0; + std::string message; +}; + +class TileXRCcuResourceAllocator { +public: + int Init(const TileXRCcuResourceSpec& spec); + + int Allocate( + const TileXRCcuResourceRequest& request, + TileXRCcuProducerPlan* plan, + TileXRCcuResourceAllocation* allocation, + TileXRCcuResourceAllocatorReport* report); + + int Release(uint64_t receiptId); + +private: + struct Cursor { + uint16_t start = 0; + uint16_t count = 0; + uint16_t used = 0; + }; + + bool HasCapacity(const Cursor& cursor, uint32_t count) const; + uint16_t CursorNext(const Cursor& cursor) const; + + struct ActiveAllocation { + TileXRCcuResourceAllocation allocation; + uint16_t missionUsed = 0; + uint16_t repositoryUsed = 0; + uint16_t localXnUsed = 0; + uint16_t localGsaUsed = 0; + uint16_t remoteXnUsed = 0; + uint16_t notifyCkeUsed = 0; + uint16_t channelUsed = 0; + uint16_t localWaitCkeUsed = 0; + uint16_t remoteNotifyCkeUsed = 0; + uint16_t sourceCkeUsed = 0; + }; + + TileXRCcuResourceSpec spec_; + Cursor mission_; + Cursor repository_; + Cursor xn_; + Cursor gsa_; + Cursor remoteXn_; + Cursor localWaitCke_; + Cursor remoteNotifyCke_; + Cursor channel_; + uint64_t nextReceiptId_ = 1; + bool initialized_ = false; + std::map active_; +}; + +} // namespace TileXR + +#endif // TILEXR_CCU_RESOURCE_ALLOCATOR_H diff --git a/src/comm/ccu/tilexr_ccu_runtime.cpp b/src/comm/ccu/tilexr_ccu_runtime.cpp new file mode 100644 index 00000000..138b7cbd --- /dev/null +++ b/src/comm/ccu/tilexr_ccu_runtime.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2026 TileXR Project + * Licensed under the Apache License, Version 2.0 + */ + +#include "ccu/tilexr_ccu_runtime.h" + +#include + +#include + +namespace TileXR { + +static_assert(RT_CCU_SQE_ARGS_LEN == TILEXR_CCU_SQE_ARGS_LEN, "TileXR CCU SQE arg count must match CANN runtime"); + +namespace { + +TileXRCcuTask CopyRuntimeTask(const rtCcuTaskInfo_t& runtimeTask) +{ + TileXRCcuTask task {}; + task.dieId = runtimeTask.dieId; + task.missionId = runtimeTask.missionId; + task.timeout = runtimeTask.timeout; + task.instStartId = runtimeTask.instStartId; + task.instCnt = runtimeTask.instCnt; + task.key = runtimeTask.key; + task.argSize = runtimeTask.argSize; + std::memcpy(task.args, runtimeTask.args, sizeof(task.args)); + return task; +} + +} // namespace + +int TileXRCcuValidateTask(const TileXRCcuTask& task) +{ + if (task.instCnt == RT_CCU_INST_CNT_INVALID) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + if (task.instStartId >= RT_CCU_INST_START_MAX) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + if (static_cast(task.instStartId) + static_cast(task.instCnt) > RT_CCU_INST_START_MAX) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + if (task.argSize != 1 && task.argSize != TILEXR_CCU_SQE_ARGS_LEN) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + return TILEXR_SUCCESS; +} + +int TileXRCcuSubmitTaskWithReport( + const TileXRCcuTask& task, + void* stream, + TileXRCcuRuntimeSubmitReport* report) +{ + if (report != nullptr) { + *report = TileXRCcuRuntimeSubmitReport{}; + } + if (stream == nullptr) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + + int ret = TileXRCcuValidateTask(task); + if (ret != TILEXR_SUCCESS) { + return ret; + } + + rtCcuTaskInfo_t runtimeTask {}; + runtimeTask.dieId = task.dieId; + runtimeTask.missionId = task.missionId; + runtimeTask.timeout = task.timeout; + runtimeTask.instStartId = task.instStartId; + runtimeTask.instCnt = task.instCnt; + runtimeTask.key = task.key; + runtimeTask.argSize = task.argSize; + std::memcpy(runtimeTask.args, task.args, sizeof(runtimeTask.args)); + + const TileXRCcuTask finalTask = CopyRuntimeTask(runtimeTask); + if (report != nullptr) { + report->finalTaskCaptured = true; + report->finalTask = finalTask; + } + + rtError_t launchRet = rtCCULaunch(&runtimeTask, stream); + if (report != nullptr) { + report->runtimeLaunchAttempted = true; + report->runtimeRet = static_cast(launchRet); + } + return launchRet == RT_ERROR_NONE ? TILEXR_SUCCESS : TILEXR_ERROR_MKIRT; +} + +int TileXRCcuSubmitTask(const TileXRCcuTask& task, void* stream) +{ + return TileXRCcuSubmitTaskWithReport(task, stream, nullptr); +} + +} // namespace TileXR diff --git a/src/comm/ccu/tilexr_ccu_runtime.h b/src/comm/ccu/tilexr_ccu_runtime.h new file mode 100644 index 00000000..7d406990 --- /dev/null +++ b/src/comm/ccu/tilexr_ccu_runtime.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2026 TileXR Project + * Licensed under the Apache License, Version 2.0 + */ + +#ifndef TILEXR_CCU_RUNTIME_H +#define TILEXR_CCU_RUNTIME_H + +#include + +#include "tilexr_types.h" + +namespace TileXR { + +constexpr uint32_t TILEXR_CCU_SQE_ARGS_LEN = 13U; +constexpr uint16_t TILEXR_CCU_DEFAULT_TASK_TIMEOUT_SEC = 120U; + +struct TileXRCcuTask { + uint8_t dieId = 0; + uint8_t missionId = 0; + uint16_t timeout = 0; + uint16_t instStartId = 0; + uint16_t instCnt = 0; + uint32_t key = 0; + uint32_t argSize = 0; + uint64_t args[TILEXR_CCU_SQE_ARGS_LEN] = {}; +}; + +struct TileXRCcuRuntimeSubmitReport { + bool runtimeLaunchAttempted = false; + int32_t runtimeRet = 0; + bool finalTaskCaptured = false; + TileXRCcuTask finalTask; +}; + +int TileXRCcuValidateTask(const TileXRCcuTask& task); + +int TileXRCcuSubmitTaskWithReport( + const TileXRCcuTask& task, + void* stream, + TileXRCcuRuntimeSubmitReport* report); + +int TileXRCcuSubmitTask(const TileXRCcuTask& task, void* stream); + +} // namespace TileXR + +#endif // TILEXR_CCU_RUNTIME_H diff --git a/src/comm/ccu/tilexr_ccu_specs.cpp b/src/comm/ccu/tilexr_ccu_specs.cpp new file mode 100644 index 00000000..06cdcdec --- /dev/null +++ b/src/comm/ccu/tilexr_ccu_specs.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2026 TileXR Project + * Licensed under the Apache License, Version 2.0 + */ + +#include "ccu/tilexr_ccu_specs.h" + +#include + +namespace TileXR { +namespace { + +constexpr uint32_t TILEXR_CCU_MOVE_16_BITS = 16; +constexpr uint32_t TILEXR_CCU_MOVE_24_BITS = 24; + +void ResetReport(TileXRCcuSpecsReport* report) +{ + if (report == nullptr) { + return; + } + *report = TileXRCcuSpecsReport{}; +} + +int Fail(TileXRCcuSpecsReport* report, const std::string& message) +{ + if (report != nullptr) { + report->message = message; + } + return TILEXR_ERROR_PARA_CHECK_FAIL; +} + +uint32_t Low16PlusOne(uint32_t value) +{ + return (value & 0x0000FFFFU) + 1U; +} + +uint32_t High16PlusOne(uint32_t value) +{ + return ((value >> TILEXR_CCU_MOVE_16_BITS) & 0x0000FFFFU) + 1U; +} + +bool FitsU16(uint32_t value) +{ + return value <= std::numeric_limits::max(); +} + +bool WindowOverflows(uint16_t start, uint32_t count) +{ + return count == 0 || count > std::numeric_limits::max() || + static_cast(start) + count > + static_cast(std::numeric_limits::max()) + 1U; +} + +void FillReport(const TileXRCcuSpecInfo& info, TileXRCcuSpecsReport* report) +{ + if (report == nullptr) { + return; + } + report->instructionNum = info.instructionNum; + report->xnNum = info.xnNum; + report->ckeNum = info.ckeNum; + report->channelNum = info.channelNum; + report->missionNum = info.missionNum; + report->message = "ok"; +} + +} // namespace + +int TileXRCcuDecodeBasicInfo( + const TileXRCcuBasicInfo& basicInfo, + TileXRCcuSpecInfo* specInfo, + TileXRCcuSpecsReport* report) +{ + ResetReport(report); + if (specInfo == nullptr) { + return Fail(report, "missing output CCU spec info"); + } + *specInfo = TileXRCcuSpecInfo{}; + + if (basicInfo.missionKey == 0) { + return Fail(report, "missing CCU mission key in basic info"); + } + if (basicInfo.resourceAddr == 0) { + return Fail(report, "missing CCU resource address in basic info"); + } + if (basicInfo.resourceAddr > std::numeric_limits::max() - TILEXR_CCU_V1_XN_RESOURCE_OFFSET) { + return Fail(report, "CCU XN base address overflows"); + } + + TileXRCcuSpecInfo decoded; + decoded.dieId = basicInfo.dieId; + decoded.msId = basicInfo.msId; + decoded.missionKey = basicInfo.missionKey; + decoded.resourceAddr = basicInfo.resourceAddr; + decoded.xnBaseAddr = basicInfo.resourceAddr + TILEXR_CCU_V1_XN_RESOURCE_OFFSET; + + decoded.instructionNum = Low16PlusOne(basicInfo.caps.cap0); + decoded.xnNum = High16PlusOne(basicInfo.caps.cap1); + decoded.gsaNum = Low16PlusOne(basicInfo.caps.cap1); + decoded.msNum = High16PlusOne(basicInfo.caps.cap2); + decoded.ckeNum = Low16PlusOne(basicInfo.caps.cap2); + decoded.jettyNum = High16PlusOne(basicInfo.caps.cap3); + decoded.channelNum = Low16PlusOne(basicInfo.caps.cap3); + decoded.pfeNum = (basicInfo.caps.cap4 & 0x000000FFU) + 1U; + decoded.missionNum = ((basicInfo.caps.cap0 >> TILEXR_CCU_MOVE_16_BITS) & 0x000000FFU) + 1U; + decoded.loopEngineNum = ((basicInfo.caps.cap0 >> TILEXR_CCU_MOVE_24_BITS) & 0x000000FFU) + 1U; + + *specInfo = decoded; + FillReport(decoded, report); + return TILEXR_SUCCESS; +} + +int TileXRCcuBuildResourceSpec( + const TileXRCcuSpecInfo& specInfo, + uint16_t missionStartId, + uint16_t instructionStartId, + uint16_t xnStartId, + uint16_t ckeStartId, + uint16_t channelStartId, + TileXRCcuResourceSpec* resourceSpec, + TileXRCcuSpecsReport* report, + uint16_t gsaStartId) +{ + ResetReport(report); + if (resourceSpec == nullptr) { + return Fail(report, "missing output CCU resource spec"); + } + *resourceSpec = TileXRCcuResourceSpec{}; + + if (specInfo.missionKey == 0) { + return Fail(report, "missing CCU mission key in spec info"); + } + if (!FitsU16(specInfo.missionNum) || !FitsU16(specInfo.instructionNum) || !FitsU16(specInfo.xnNum) || + !FitsU16(specInfo.gsaNum) || + !FitsU16(specInfo.ckeNum) || !FitsU16(specInfo.channelNum)) { + return Fail(report, "CCU resource count exceeds TileXR resource window capacity"); + } + if (WindowOverflows(missionStartId, specInfo.missionNum)) { + return Fail(report, "mission resource window overflows"); + } + if (WindowOverflows(instructionStartId, specInfo.instructionNum)) { + return Fail(report, "instruction resource window overflows"); + } + if (WindowOverflows(xnStartId, specInfo.xnNum)) { + return Fail(report, "XN resource window overflows"); + } + if (gsaStartId != 0 && WindowOverflows(gsaStartId, specInfo.gsaNum)) { + return Fail(report, "GSA resource window overflows"); + } + if (WindowOverflows(ckeStartId, specInfo.ckeNum)) { + return Fail(report, "CKE resource window overflows"); + } + if (WindowOverflows(channelStartId, specInfo.channelNum)) { + return Fail(report, "channel resource window overflows"); + } + + TileXRCcuResourceSpec result; + result.dieId = specInfo.dieId; + result.missionKey = specInfo.missionKey; + result.missionStartId = missionStartId; + result.missionCount = static_cast(specInfo.missionNum); + result.instructionStartId = instructionStartId; + result.instructionCount = static_cast(specInfo.instructionNum); + result.xnStartId = xnStartId; + result.xnCount = static_cast(specInfo.xnNum); + result.gsaStartId = gsaStartId; + result.gsaCount = gsaStartId == 0 ? 0 : static_cast(specInfo.gsaNum); + result.ckeStartId = ckeStartId; + result.ckeCount = static_cast(specInfo.ckeNum); + result.channelStartId = channelStartId; + result.channelCount = static_cast(specInfo.channelNum); + + *resourceSpec = result; + FillReport(specInfo, report); + return TILEXR_SUCCESS; +} + +} // namespace TileXR diff --git a/src/comm/ccu/tilexr_ccu_specs.h b/src/comm/ccu/tilexr_ccu_specs.h new file mode 100644 index 00000000..75674e41 --- /dev/null +++ b/src/comm/ccu/tilexr_ccu_specs.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2026 TileXR Project + * Licensed under the Apache License, Version 2.0 + */ + +#ifndef TILEXR_CCU_SPECS_H +#define TILEXR_CCU_SPECS_H + +#include "ccu/tilexr_ccu_resource_allocator.h" + +#include +#include + +namespace TileXR { + +constexpr uint64_t TILEXR_CCU_V1_CCUM_OFFSET = 0x800000ULL; +constexpr uint64_t TILEXR_CCU_RESOURCE_INS_RESERVE_SIZE = 0x100000ULL; +constexpr uint64_t TILEXR_CCU_V1_RESOURCE_GSA_RESERVE_SIZE = 0x8000ULL; +constexpr uint64_t TILEXR_CCU_V1_XN_RESOURCE_OFFSET = + TILEXR_CCU_V1_CCUM_OFFSET + TILEXR_CCU_RESOURCE_INS_RESERVE_SIZE + TILEXR_CCU_V1_RESOURCE_GSA_RESERVE_SIZE; +constexpr uint64_t TILEXR_CCU_RESOURCE_WINDOW_BYTES = 72ULL * 1024ULL * 1024ULL; + +struct TileXRCcuCaps { + uint32_t cap0 = 0; + uint32_t cap1 = 0; + uint32_t cap2 = 0; + uint32_t cap3 = 0; + uint32_t cap4 = 0; +}; + +struct TileXRCcuMsidTokenInfo { + uint32_t tokenId = 0; + uint32_t tokenValue = 0; + bool valid = false; +}; + +struct TileXRCcuBasicInfo { + uint8_t dieId = 0; + uint32_t msId = 0; + TileXRCcuMsidTokenInfo msidToken; + uint32_t missionKey = 0; + uint64_t resourceAddr = 0; + TileXRCcuCaps caps; +}; + +struct TileXRCcuSpecInfo { + uint8_t dieId = 0; + uint32_t msId = 0; + uint32_t missionKey = 0; + uint64_t resourceAddr = 0; + uint64_t xnBaseAddr = 0; + uint32_t loopEngineNum = 0; + uint32_t missionNum = 0; + uint32_t instructionNum = 0; + uint32_t xnNum = 0; + uint32_t gsaNum = 0; + uint32_t msNum = 0; + uint32_t ckeNum = 0; + uint32_t jettyNum = 0; + uint32_t channelNum = 0; + uint32_t pfeNum = 0; +}; + +struct TileXRCcuSpecsReport { + uint32_t instructionNum = 0; + uint32_t xnNum = 0; + uint32_t ckeNum = 0; + uint32_t channelNum = 0; + uint32_t missionNum = 0; + std::string message; +}; + +int TileXRCcuDecodeBasicInfo( + const TileXRCcuBasicInfo& basicInfo, + TileXRCcuSpecInfo* specInfo, + TileXRCcuSpecsReport* report); + +int TileXRCcuBuildResourceSpec( + const TileXRCcuSpecInfo& specInfo, + uint16_t missionStartId, + uint16_t instructionStartId, + uint16_t xnStartId, + uint16_t ckeStartId, + uint16_t channelStartId, + TileXRCcuResourceSpec* resourceSpec, + TileXRCcuSpecsReport* report, + uint16_t gsaStartId = 0); + +} // namespace TileXR + +#endif // TILEXR_CCU_SPECS_H diff --git a/tests/ccu/test_tilexr_ccu_barrier_program.py b/tests/ccu/test_tilexr_ccu_barrier_program.py new file mode 100644 index 00000000..254a1b82 --- /dev/null +++ b/tests/ccu/test_tilexr_ccu_barrier_program.py @@ -0,0 +1,649 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2026 TileXR Project +# + +import shutil +import subprocess +import tempfile +import textwrap +import unittest +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[2] +BARRIER_HEADER = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_barrier_program.h" +BARRIER_SOURCE = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_barrier_program.cpp" +MICROCODE_SOURCE = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_microcode.cpp" +PRODUCER_SOURCE = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_producer_plan.cpp" +COMM_CMAKE = REPO_ROOT / "src" / "comm" / "CMakeLists.txt" +INCLUDE_DIR = REPO_ROOT / "src" / "include" +COMM_DIR = REPO_ROOT / "src" / "comm" + + +PRIVATE_CCU_PRODUCER_NEEDLES = [ + "#include + #include + + using namespace TileXR; + + int main() + { + std::vector specs; + specs.push_back({2361, 1961, 2, 364, 1, 332, 1}); + specs.push_back({2362, 1962, 3, 365, 1, 333, 1}); + + std::vector program; + TileXRCcuBarrierProgramReport report; + if (TileXRCcuBuildBarrierProgram(specs, &program, &report) != TILEXR_SUCCESS) { + std::cerr << "barrier build failed: " << report.message << "\n"; + return 1; + } + if (program.size() != 4 || report.postInstructionCount != 2 || + report.waitInstructionCount != 2 || report.totalInstructionCount != 4 || + report.message != "ok") { + std::cerr << "unexpected barrier report\n"; + return 2; + } + if (program[0].words[0] != 0x000007a90939100dULL || + program[0].words[1] != 0x00000001016c0002ULL || + program[0].words[2] != 0x0001000000000000ULL || + program[0].words[3] != 0) { + std::cerr << "unexpected first post instruction\n"; + return 3; + } + if (program[1].words[0] != 0x000007aa093a100dULL || + program[1].words[1] != 0x00000001016d0003ULL || + program[1].words[2] != 0x0001000000000000ULL || + program[1].words[3] != 0) { + std::cerr << "unexpected second post instruction\n"; + return 4; + } + if (program[2].words[0] != 0x0000000000010802ULL || + program[2].words[1] != 0x000000000001014cULL || + program[2].words[2] != 0 || + program[2].words[3] != 0) { + std::cerr << "unexpected first wait/clear instruction\n"; + return 5; + } + if (program[3].words[0] != 0x0000000000010802ULL || + program[3].words[1] != 0x000000000001014dULL || + program[3].words[2] != 0 || + program[3].words[3] != 0) { + std::cerr << "unexpected second wait/clear instruction\n"; + return 6; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_barrier_program_rejects_incomplete_resource_bindings(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_barrier_program.h" + + #include + #include + + using namespace TileXR; + + int main() + { + std::vector program(1); + TileXRCcuBarrierProgramReport report; + if (TileXRCcuBuildBarrierProgram({}, &program, &report) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "empty barrier specs accepted\n"; + return 1; + } + if (!program.empty() || report.message.find("missing") == std::string::npos) { + std::cerr << "empty barrier diagnostic/report mismatch: " << report.message << "\n"; + return 2; + } + + std::vector specs; + specs.push_back({2361, 1961, 2, 364, 1, 0, 1}); + if (TileXRCcuBuildBarrierProgram(specs, &program, &report) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "missing local wait CKE accepted\n"; + return 3; + } + if (!program.empty() || report.message.find("local wait CKE") == std::string::npos) { + std::cerr << "weak missing local wait diagnostic: " << report.message << "\n"; + return 4; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_barrier_program_can_emit_hcomm_like_synccke_post_and_clear_wait(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_barrier_program.h" + + #include + #include + + using namespace TileXR; + + int main() + { + TileXRCcuBarrierSyncSpec spec; + spec.channelId = 2; + spec.remoteNotifyCke = 0x330; + spec.remoteNotifyMask = 1; + spec.localWaitCke = 0x220; + spec.localWaitMask = 1; + spec.sourceCke = 0x221; + spec.sourceCkeMask = 0xffff; + + std::vector program; + TileXRCcuBarrierProgramReport report; + if (TileXRCcuBuildBarrierProgram( + std::vector{spec}, + &program, + &report, + TileXRCcuBarrierMode::SyncCke) != TILEXR_SUCCESS) { + std::cerr << "sync_cke barrier build failed: " << report.message << "\n"; + return 1; + } + if (program.size() != 3 || report.postInstructionCount != 1 || + report.waitInstructionCount != 1 || report.totalInstructionCount != 3) { + std::cerr << "unexpected sync_cke barrier report\n"; + return 2; + } + if (program[0].words[0] != 0xffff022100010802ULL || + program[0].words[1] != 0 || + program[0].words[2] != 0 || + program[0].words[3] != 0) { + std::cerr << "source CKE init mismatch\n"; + return 3; + } + if (program[1].words[0] != 0x000102210330100bULL || + program[1].words[1] != 0x0000000000000002ULL || + program[1].words[2] != 0x0001000000000000ULL || + program[1].words[3] != 0) { + std::cerr << "SyncCKE post mismatch\n"; + return 4; + } + if (program[2].words[0] != 0x0000000000010804ULL || + program[2].words[1] != 0x0000000000010220ULL || + program[2].words[2] != 0 || + program[2].words[3] != 0) { + std::cerr << "ClearCKE wait mismatch\n"; + return 5; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_barrier_program_can_emit_synccke_post_and_set_wait(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_barrier_program.h" + + #include + #include + + using namespace TileXR; + + int main() + { + TileXRCcuBarrierSyncSpec spec; + spec.channelId = 2; + spec.remoteNotifyCke = 0x330; + spec.remoteNotifyMask = 1; + spec.localWaitCke = 0x220; + spec.localWaitMask = 1; + spec.sourceCke = 0x221; + spec.sourceCkeMask = 0xffff; + + std::vector program; + TileXRCcuBarrierProgramReport report; + if (TileXRCcuBuildBarrierProgram( + std::vector{spec}, + &program, + &report, + TileXRCcuBarrierMode::SyncCkeSetWait) != TILEXR_SUCCESS) { + std::cerr << "sync_cke_set_wait barrier build failed: " << report.message << "\n"; + return 1; + } + if (program.size() != 3 || report.postInstructionCount != 1 || + report.waitInstructionCount != 1 || report.totalInstructionCount != 3) { + std::cerr << "unexpected sync_cke_set_wait barrier report\n"; + return 2; + } + if (program[0].words[0] != 0xffff022100010802ULL || + program[0].words[1] != 0 || + program[0].words[2] != 0 || + program[0].words[3] != 0) { + std::cerr << "source CKE init mismatch\n"; + return 3; + } + if (program[1].words[0] != 0x000102210330100bULL || + program[1].words[1] != 0x0000000000000002ULL || + program[1].words[2] != 0x0001000000000000ULL || + program[1].words[3] != 0) { + std::cerr << "SyncCKE post mismatch\n"; + return 4; + } + if (program[2].words[0] != 0x0000000000010802ULL || + program[2].words[1] != 0x0000000000010220ULL || + program[2].words[2] != 0 || + program[2].words[3] != 0) { + std::cerr << "SetCKE wait mismatch\n"; + return 5; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_barrier_program_can_emit_synccke_post_only_diagnostic(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_barrier_program.h" + + #include + #include + + using namespace TileXR; + + int main() + { + TileXRCcuBarrierSyncSpec spec; + spec.channelId = 2; + spec.remoteNotifyCke = 0x330; + spec.remoteNotifyMask = 1; + spec.sourceCke = 0x221; + spec.sourceCkeMask = 0xffff; + + std::vector program; + TileXRCcuBarrierProgramReport report; + if (TileXRCcuBuildBarrierProgram( + std::vector{spec}, + &program, + &report, + TileXRCcuBarrierMode::SyncCkePostOnly) != TILEXR_SUCCESS) { + std::cerr << "sync_cke_post_only barrier build failed: " << report.message << "\n"; + return 1; + } + if (program.size() != 2 || report.postInstructionCount != 1 || + report.waitInstructionCount != 0 || report.totalInstructionCount != 2) { + std::cerr << "unexpected sync_cke_post_only barrier report\n"; + return 2; + } + if (program[0].words[0] != 0xffff022100010802ULL || + program[0].words[1] != 0 || + program[0].words[2] != 0 || + program[0].words[3] != 0) { + std::cerr << "source CKE init mismatch\n"; + return 3; + } + if (program[1].words[0] != 0x000102210330100bULL || + program[1].words[1] != 0x0000000000000002ULL || + program[1].words[2] != 0x0001000000000000ULL || + program[1].words[3] != 0) { + std::cerr << "SyncCKE post-only mismatch\n"; + return 4; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_barrier_program_can_emit_local_cke_completion_diagnostic(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_barrier_program.h" + + #include + #include + + using namespace TileXR; + + int main() + { + TileXRCcuBarrierSyncSpec spec; + spec.localWaitCke = 0x220; + spec.localWaitMask = 1; + + std::vector program; + TileXRCcuBarrierProgramReport report; + if (TileXRCcuBuildBarrierProgram( + std::vector{spec}, + &program, + &report, + TileXRCcuBarrierMode::LocalCke) != TILEXR_SUCCESS) { + std::cerr << "local CKE diagnostic build failed: " << report.message << "\n"; + return 1; + } + if (program.size() != 2 || report.postInstructionCount != 1 || + report.waitInstructionCount != 1 || report.totalInstructionCount != 2) { + std::cerr << "unexpected local CKE diagnostic report\n"; + return 2; + } + if (program[0].words[0] != 0x0001022000000802ULL || + program[0].words[1] != 0 || + program[0].words[2] != 0 || + program[0].words[3] != 0) { + std::cerr << "local CKE set mismatch\n"; + return 3; + } + if (program[1].words[0] != 0x0000000000010804ULL || + program[1].words[1] != 0x0000000000010220ULL || + program[1].words[2] != 0 || + program[1].words[3] != 0) { + std::cerr << "local CKE clear/wait mismatch\n"; + return 4; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_barrier_program_can_emit_local_cke_post_only_diagnostic(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_barrier_program.h" + + #include + #include + + using namespace TileXR; + + int main() + { + TileXRCcuBarrierSyncSpec spec; + spec.localWaitCke = 0x220; + spec.localWaitMask = 1; + + std::vector program; + TileXRCcuBarrierProgramReport report; + if (TileXRCcuBuildBarrierProgram( + std::vector{spec}, + &program, + &report, + TileXRCcuBarrierMode::LocalCkePostOnly) != TILEXR_SUCCESS) { + std::cerr << "local CKE post-only diagnostic build failed: " << report.message << "\n"; + return 1; + } + if (program.size() != 1 || report.postInstructionCount != 1 || + report.waitInstructionCount != 0 || report.totalInstructionCount != 1) { + std::cerr << "unexpected local CKE post-only diagnostic report\n"; + return 2; + } + if (program[0].words[0] != 0x0001022000000802ULL || + program[0].words[1] != 0 || + program[0].words[2] != 0 || + program[0].words[3] != 0) { + std::cerr << "local CKE post-only set mismatch\n"; + return 3; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_barrier_program_can_load_local_xn_before_sync_xn_post_only_diagnostic(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_barrier_program.h" + + #include + #include + + using namespace TileXR; + + int main() + { + TileXRCcuBarrierSyncSpec spec; + spec.remoteXn = 0x240; + spec.localXn = 0x120; + spec.channelId = 2; + spec.remoteNotifyCke = 0x330; + spec.remoteNotifyMask = 1; + + std::vector program; + TileXRCcuBarrierProgramReport report; + if (TileXRCcuBuildBarrierProgram( + std::vector{spec}, + &program, + &report, + TileXRCcuBarrierMode::SyncXnLoadPostOnly) != TILEXR_SUCCESS) { + std::cerr << "sync_xn_load_post_only diagnostic build failed: " << report.message << "\n"; + return 1; + } + if (program.size() != 2 || report.postInstructionCount != 1 || + report.waitInstructionCount != 0 || report.totalInstructionCount != 2) { + std::cerr << "unexpected sync_xn_load_post_only report\n"; + return 2; + } + if (program[0].words[0] != 0x0000000101200003ULL || + program[0].words[1] != 0 || + program[0].words[2] != 0 || + program[0].words[3] != 0) { + std::cerr << "local XN load immediate mismatch\n"; + return 3; + } + if (program[1].words[0] != 0x000001200240100dULL || + program[1].words[1] != 0x0000000103300002ULL || + program[1].words[2] != 0x0001000000000000ULL || + program[1].words[3] != 0) { + std::cerr << "SyncXn post mismatch\n"; + return 4; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_producer_microcode_uses_barrier_post_and_wait_program(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_producer_plan.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuProducerPlan plan; + plan.mission = {1, 6, 0x059b0f03U, true}; + plan.kernelLocalXn = {1, 1961, 62}; + plan.kernelLocalCke = {1, 332, 3}; + plan.kernelLocalMission = {1, 6, 1}; + plan.instructionWindow = {1, 475, 170, 489, 156}; + plan.syncResources.push_back({1, 1961, 2361, 332, 2, 3}); + plan.syncResources.push_back({1, 1962, 2362, 333, 3, 3}); + plan.taskWindows.push_back({1, 489, 13, 13, {}}); + plan.taskWindows.push_back({1, 502, 143, 13, {}}); + + TileXRCcuProgram program; + TileXRCcuProducerPlanReport report; + if (TileXRCcuBuildMicrocode(plan, &program, &report) != TILEXR_SUCCESS) { + std::cerr << "microcode build failed: " << report.message << "\n"; + return 1; + } + if (program.sqeLoad.size() != 13 || program.sync.size() != 9) { + std::cerr << "unexpected producer barrier microcode size\n"; + return 2; + } + if (program.sync[5].words[0] != 0x000007a90939100dULL || + program.sync[5].words[1] != 0x00000001014c0002ULL || + program.sync[7].words[0] != 0x0000000000010802ULL || + program.sync[7].words[1] != 0x000000000001014cULL) { + std::cerr << "unexpected first producer barrier pair\n"; + return 3; + } + if (program.sync[6].words[0] != 0x000007aa093a100dULL || + program.sync[6].words[1] != 0x00000001014d0003ULL || + program.sync[8].words[0] != 0x0000000000010802ULL || + program.sync[8].words[1] != 0x000000000001014dULL) { + std::cerr << "unexpected second producer barrier pair\n"; + return 4; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_barrier_program_is_wired_and_has_no_private_hcomm_surface(self): + cmake = COMM_CMAKE.read_text(encoding="utf-8") + header = BARRIER_HEADER.read_text(encoding="utf-8") + source = BARRIER_SOURCE.read_text(encoding="utf-8") + producer = PRODUCER_SOURCE.read_text(encoding="utf-8") + + self.assertIn("ccu/tilexr_ccu_barrier_program.h", cmake) + self.assertIn("ccu/tilexr_ccu_barrier_program.cpp", cmake) + self.assertIn("struct TileXRCcuBarrierSyncSpec", header) + self.assertIn("enum class TileXRCcuBarrierMode", header) + self.assertIn("LocalCke", header) + self.assertIn("LocalCkePostOnly", header) + self.assertIn("SyncXnPostOnly", header) + self.assertIn("SyncXnLoadPostOnly", header) + self.assertIn("SyncCkePostOnly", header) + self.assertIn("SyncCkeSetWait", header) + self.assertIn("struct TileXRCcuBarrierProgramReport", header) + self.assertIn("TileXRCcuBuildBarrierProgram", header) + self.assertIn("TileXRCcuBarrierMode::LocalCke", source) + self.assertIn("TileXRCcuBarrierMode::LocalCkePostOnly", source) + self.assertIn("TileXRCcuBarrierMode::SyncXnPostOnly", source) + self.assertIn("TileXRCcuBarrierMode::SyncXnLoadPostOnly", source) + self.assertIn("TileXRCcuBarrierMode::SyncCkePostOnly", source) + self.assertIn("TileXRCcuBarrierMode::SyncCkeSetWait", source) + self.assertIn("TileXRCcuEncodeLoadImdToXn", source) + self.assertIn("TileXRCcuEncodeSyncXn", source) + self.assertIn("TileXRCcuEncodeSyncCke", source) + self.assertIn("TileXRCcuEncodeSetCke", source) + self.assertIn("TileXRCcuEncodeClearCke", source) + self.assertIn("TileXRCcuBuildBarrierProgram", producer) + + combined = header + "\n" + source + "\n" + producer + for needle in PRIVATE_CCU_PRODUCER_NEEDLES: + with self.subTest(needle=needle): + self.assertNotIn(needle, combined) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/ccu/test_tilexr_ccu_memory_program.py b/tests/ccu/test_tilexr_ccu_memory_program.py new file mode 100644 index 00000000..29868acb --- /dev/null +++ b/tests/ccu/test_tilexr_ccu_memory_program.py @@ -0,0 +1,315 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2026 TileXR Project +# + +import shutil +import subprocess +import tempfile +import textwrap +import unittest +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[2] +MEMORY_HEADER = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_memory_program.h" +MEMORY_SOURCE = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_memory_program.cpp" +MICROCODE_SOURCE = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_microcode.cpp" +COMM_CMAKE = REPO_ROOT / "src" / "comm" / "CMakeLists.txt" +INCLUDE_DIR = REPO_ROOT / "src" / "include" +COMM_DIR = REPO_ROOT / "src" / "comm" + + +PRIVATE_CCU_PRODUCER_NEEDLES = [ + "#include + #include + + using namespace TileXR; + + int main() + { + if (TileXRCcuPackMemoryToken(0x12345, 0x89abcdefU, true) != + 0x0011234589abcdefULL) { + std::cerr << "packed token mismatch\n"; + return 1; + } + + TileXRCcuMemoryCopySpec spec; + spec.direction = TileXRCcuMemoryCopyDirection::LocalToRemote; + spec.localGsa = 0x101; + spec.localXn = 0x102; + spec.remoteGsa = 0x201; + spec.remoteXn = 0x202; + spec.lengthXn = 0x301; + spec.localAddr = 0x1122334455667788ULL; + spec.localToken = TileXRCcuPackMemoryToken(0x12345, 0x89abcdefU, true); + spec.remoteAddr = 0x8877665544332211ULL; + spec.remoteToken = TileXRCcuPackMemoryToken(0x23456, 0x76543210U, true); + spec.lengthBytes = 0x80; + spec.channelId = 0x12; + spec.completionCke = 0x401; + spec.completionMask = 1; + + std::vector program; + TileXRCcuMemoryProgramReport report; + if (TileXRCcuBuildMemoryCopyProgram(spec, &program, &report) != TILEXR_SUCCESS) { + std::cerr << "memory copy program build failed: " << report.message << "\n"; + return 2; + } + if (program.size() != 7 || report.loadInstructionCount != 5 || + report.transferInstructionCount != 1 || report.waitInstructionCount != 1 || + report.totalInstructionCount != 7 || report.message != "ok") { + std::cerr << "unexpected memory program report\n"; + return 3; + } + if (program[0].words[0] != 0x5566778801010002ULL || + program[0].words[1] != 0x0000000011223344ULL || + program[1].words[0] != 0x89abcdef01020003ULL || + program[1].words[1] != 0x0000000100112345ULL || + program[2].words[0] != 0x4433221102010002ULL || + program[2].words[1] != 0x0000000088776655ULL || + program[3].words[0] != 0x7654321002020003ULL || + program[3].words[1] != 0x0000000100123456ULL || + program[4].words[0] != 0x0000008003010003ULL || + program[4].words[1] != 0) { + std::cerr << "unexpected load immediate program\n"; + return 4; + } + if (program[5].words[0] != 0x0101020202011009ULL || + program[5].words[1] != 0x0000001203010102ULL || + program[5].words[2] != 0x0003000000000000ULL || + program[5].words[3] != 0x0000000000010401ULL) { + std::cerr << "unexpected loc->rmt transfer instruction\n"; + return 5; + } + if (program[6].words[0] != 0x0000000000010804ULL || + program[6].words[1] != 0x0000000000010401ULL || + program[6].words[2] != 0 || + program[6].words[3] != 0) { + std::cerr << "unexpected completion wait instruction\n"; + return 6; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_memory_program_builds_hcomm_style_rmt_to_loc_copy(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_memory_program.h" + + #include + #include + + using namespace TileXR; + + int main() + { + TileXRCcuMemoryCopySpec spec; + spec.direction = TileXRCcuMemoryCopyDirection::RemoteToLocal; + spec.localGsa = 0x101; + spec.localXn = 0x102; + spec.remoteGsa = 0x201; + spec.remoteXn = 0x202; + spec.lengthXn = 0x301; + spec.localAddr = 0x1122334455667788ULL; + spec.localToken = TileXRCcuPackMemoryToken(0x12345, 0x89abcdefU, true); + spec.remoteAddr = 0x8877665544332211ULL; + spec.remoteToken = TileXRCcuPackMemoryToken(0x23456, 0x76543210U, true); + spec.lengthBytes = 0x80; + spec.channelId = 0x12; + spec.completionCke = 0x401; + spec.completionMask = 1; + + std::vector program; + TileXRCcuMemoryProgramReport report; + if (TileXRCcuBuildMemoryCopyProgram(spec, &program, &report) != TILEXR_SUCCESS) { + std::cerr << "memory copy program build failed: " << report.message << "\n"; + return 1; + } + if (program.size() != 7 || + program[5].words[0] != 0x0201010201011008ULL || + program[5].words[1] != 0x0000001203010202ULL || + program[5].words[2] != 0x0003000000000000ULL || + program[5].words[3] != 0x0000000000010401ULL) { + std::cerr << "unexpected rmt->loc transfer instruction\n"; + return 2; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_memory_program_rejects_missing_required_fields(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_memory_program.h" + + #include + #include + + using namespace TileXR; + + int main() + { + std::vector program(1); + TileXRCcuMemoryProgramReport report; + TileXRCcuMemoryCopySpec empty; + if (TileXRCcuBuildMemoryCopyProgram(empty, &program, &report) != + TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "empty memory copy spec accepted\n"; + return 1; + } + if (!program.empty() || report.message.find("missing") == std::string::npos) { + std::cerr << "weak empty spec diagnostic: " << report.message << "\n"; + return 2; + } + + TileXRCcuMemoryCopySpec spec; + spec.direction = TileXRCcuMemoryCopyDirection::LocalToRemote; + spec.localGsa = 1; + spec.localXn = 2; + spec.remoteGsa = 3; + spec.remoteXn = 4; + spec.lengthXn = 5; + spec.localAddr = 0x1000; + spec.localToken = TileXRCcuPackMemoryToken(1, 2, true); + spec.remoteAddr = 0x2000; + spec.remoteToken = TileXRCcuPackMemoryToken(3, 4, true); + spec.lengthBytes = 128; + spec.channelId = 6; + spec.completionCke = 7; + spec.completionMask = 1; + + if (TileXRCcuBuildMemoryCopyProgram(spec, nullptr, &report) != + TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "null output program accepted\n"; + return 3; + } + + spec.lengthBytes = 0; + if (TileXRCcuBuildMemoryCopyProgram(spec, &program, &report) != + TILEXR_ERROR_PARA_CHECK_FAIL || + report.message.find("length") == std::string::npos) { + std::cerr << "zero length accepted or weak diagnostic: " << report.message << "\n"; + return 4; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_memory_program_is_wired_and_has_no_private_hcomm_surface(self): + cmake = COMM_CMAKE.read_text(encoding="utf-8") + header = MEMORY_HEADER.read_text(encoding="utf-8") + source = MEMORY_SOURCE.read_text(encoding="utf-8") + + self.assertIn("ccu/tilexr_ccu_memory_program.h", cmake) + self.assertIn("ccu/tilexr_ccu_memory_program.cpp", cmake) + self.assertIn("enum class TileXRCcuMemoryCopyDirection", header) + self.assertIn("struct TileXRCcuMemoryCopySpec", header) + self.assertIn("struct TileXRCcuMemoryProgramReport", header) + self.assertIn("TileXRCcuPackMemoryToken", header) + self.assertIn("TileXRCcuBuildMemoryCopyProgram", header) + self.assertIn("TileXRCcuEncodeLoadImdToGsa", source) + self.assertIn("TileXRCcuEncodeLoadImdToXn", source) + self.assertIn("TileXRCcuEncodeTransRmtMemToLocMem", source) + self.assertIn("TileXRCcuEncodeTransLocMemToRmtMem", source) + self.assertIn("TileXRCcuEncodeClearCke", source) + + combined = header + "\n" + source + for needle in PRIVATE_CCU_PRODUCER_NEEDLES: + with self.subTest(needle=needle): + self.assertNotIn(needle, combined) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/ccu/test_tilexr_ccu_microcode.py b/tests/ccu/test_tilexr_ccu_microcode.py new file mode 100644 index 00000000..bf06d5e5 --- /dev/null +++ b/tests/ccu/test_tilexr_ccu_microcode.py @@ -0,0 +1,572 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2026 TileXR Project +# + +import shutil +import subprocess +import tempfile +import textwrap +import unittest +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[2] +MICROCODE_HEADER = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_microcode.h" +MICROCODE_SOURCE = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_microcode.cpp" +COMM_CMAKE = REPO_ROOT / "src" / "comm" / "CMakeLists.txt" +INCLUDE_DIR = REPO_ROOT / "src" / "include" +COMM_DIR = REPO_ROOT / "src" / "comm" + + +PRIVATE_CCU_PRODUCER_NEEDLES = [ + "#include + + using namespace TileXR; + + int main() + { + TileXRCcuInstr load; + if (TileXRCcuEncodeLoadSqeArgsToX(1961, 0, &load) != TILEXR_SUCCESS) { + std::cerr << "load encode failed\n"; + return 1; + } + if (load.words[0] != 0x0000000007a90001ULL || load.words[1] != 0 || + load.words[2] != 0 || load.words[3] != 0) { + std::cerr << "unexpected load word\n"; + return 2; + } + + TileXRCcuInstr sync; + TileXRCcuSyncXnSpec spec; + spec.remoteXn = 2361; + spec.localXn = 1961; + spec.channelId = 2; + spec.notifyCke = 364; + spec.notifyMask = 1; + spec.setCkeId = 0; + spec.setCkeMask = 0; + if (TileXRCcuEncodeSyncXn(spec, &sync) != TILEXR_SUCCESS) { + std::cerr << "sync encode failed\n"; + return 3; + } + if (sync.words[0] != 0x000007a90939100dULL || + sync.words[1] != 0x00000001016c0002ULL || + sync.words[2] != 0x0001000000000000ULL || + sync.words[3] != 0x0000000000000000ULL) { + std::cerr << "unexpected sync words\n"; + return 4; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_microcode_encodes_hcomm_v1_load_immediate_to_xn_words(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_microcode.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuInstr load; + if (TileXRCcuEncodeLoadImdToXn(1961, 0x1122334455667788ULL, 0, &load) != TILEXR_SUCCESS) { + std::cerr << "load immediate encode failed\n"; + return 1; + } + if (load.words[0] != 0x5566778807a90003ULL || + load.words[1] != 0x0000000011223344ULL || + load.words[2] != 0 || + load.words[3] != 0) { + std::cerr << "unexpected load immediate words\n"; + return 2; + } + + if (TileXRCcuEncodeLoadImdToXn(0, 1, 0, &load) != TILEXR_ERROR_PARA_CHECK_FAIL || + TileXRCcuEncodeLoadImdToXn(1961, 1, 0, nullptr) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "invalid load immediate arguments accepted\n"; + return 3; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_microcode_encodes_hcomm_v1_load_immediate_to_gsa_words(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_microcode.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuInstr load; + if (TileXRCcuEncodeLoadImdToGsa(510, 0x1122334455667788ULL, &load) != TILEXR_SUCCESS) { + std::cerr << "load immediate to GSA encode failed\n"; + return 1; + } + if (load.words[0] != 0x5566778801fe0002ULL || + load.words[1] != 0x0000000011223344ULL || + load.words[2] != 0 || + load.words[3] != 0) { + std::cerr << "unexpected load immediate to GSA words\n"; + return 2; + } + + if (TileXRCcuEncodeLoadImdToGsa(0, 1, &load) != TILEXR_ERROR_PARA_CHECK_FAIL || + TileXRCcuEncodeLoadImdToGsa(510, 1, nullptr) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "invalid load immediate to GSA arguments accepted\n"; + return 3; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_microcode_rejects_invalid_arguments(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_microcode.h" + + #include + + using namespace TileXR; + + int main() + { + if (TileXRCcuEncodeLoadSqeArgsToX(1961, 13, nullptr) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "null output accepted\n"; + return 1; + } + + TileXRCcuInstr instr; + if (TileXRCcuEncodeLoadSqeArgsToX(1961, TILEXR_CCU_SQE_ARGS_LEN, &instr) != + TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "out-of-range sqe arg accepted\n"; + return 2; + } + + TileXRCcuSyncXnSpec missingRemote; + missingRemote.localXn = 1961; + missingRemote.channelId = 2; + missingRemote.notifyCke = 364; + missingRemote.notifyMask = 1; + if (TileXRCcuEncodeSyncXn(missingRemote, &instr) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "missing remote xn accepted\n"; + return 3; + } + + TileXRCcuSyncXnSpec missingNotify; + missingNotify.remoteXn = 2361; + missingNotify.localXn = 1961; + missingNotify.channelId = 2; + if (TileXRCcuEncodeSyncXn(missingNotify, &instr) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "missing notify accepted\n"; + return 4; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_syncxn_setcke_fields_match_hcomm_v1_layout(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_microcode.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuInstr sync; + TileXRCcuSyncXnSpec spec; + spec.remoteXn = 2361; + spec.localXn = 1961; + spec.channelId = 2; + spec.notifyCke = 364; + spec.notifyMask = 1; + spec.setCkeId = 400; + spec.setCkeMask = 5; + spec.waitCkeId = 401; + spec.waitCkeMask = 6; + if (TileXRCcuEncodeSyncXn(spec, &sync) != TILEXR_SUCCESS) { + std::cerr << "sync encode failed\n"; + return 1; + } + if (sync.words[0] != 0x000007a90939100dULL || + sync.words[1] != 0x00000001016c0002ULL || + sync.words[2] != 0x0001000000000000ULL || + sync.words[3] != 0x0006019100050190ULL) { + std::cerr << "sync local set/wait CKE fields are not hcomm v1 packed layout\n"; + return 2; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_synccke_fields_match_hcomm_v1_layout(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_microcode.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuInstr sync; + TileXRCcuSyncCkeSpec spec; + spec.remoteCke = 0x330; + spec.localCke = 0x221; + spec.localCkeMask = 1; + spec.channelId = 2; + spec.setCkeId = 0x401; + spec.setCkeMask = 2; + spec.waitCkeId = 0x402; + spec.waitCkeMask = 3; + if (TileXRCcuEncodeSyncCke(spec, &sync) != TILEXR_SUCCESS) { + std::cerr << "synccke encode failed\n"; + return 1; + } + if (sync.words[0] != 0x000102210330100bULL || + sync.words[1] != 0x0000000000000002ULL || + sync.words[2] != 0x0001000000000000ULL || + sync.words[3] != 0x0003040200020401ULL) { + std::cerr << "synccke fields are not hcomm v1 packed layout\n"; + return 2; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_set_and_clear_cke_microcode_encoders_match_hcomm_v1_layout(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_microcode.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuCkeSpec setSpec; + setSpec.ckeId = 332; + setSpec.mask = 3; + setSpec.waitCkeId = 364; + setSpec.waitMask = 1; + setSpec.clearWait = true; + + TileXRCcuInstr setInstr; + if (TileXRCcuEncodeSetCke(setSpec, &setInstr) != TILEXR_SUCCESS) { + std::cerr << "set cke encode failed\n"; + return 1; + } + if (setInstr.words[0] != 0x0003014c00010802ULL || + setInstr.words[1] != 0x000000000001016cULL || + setInstr.words[2] != 0 || + setInstr.words[3] != 0) { + std::cerr << "unexpected set cke words\n"; + return 2; + } + + TileXRCcuCkeSpec clearSpec; + clearSpec.ckeId = 0; + clearSpec.mask = 0; + clearSpec.waitCkeId = 364; + clearSpec.waitMask = 1; + clearSpec.clearWait = true; + + TileXRCcuInstr clearInstr; + if (TileXRCcuEncodeClearCke(clearSpec, &clearInstr) != TILEXR_SUCCESS) { + std::cerr << "clear cke encode failed\n"; + return 3; + } + if (clearInstr.words[0] != 0x0000000000010804ULL || + clearInstr.words[1] != 0x000000000001016cULL || + clearInstr.words[2] != 0 || + clearInstr.words[3] != 0) { + std::cerr << "unexpected clear cke words\n"; + return 4; + } + + TileXRCcuCkeSpec invalid; + if (TileXRCcuEncodeSetCke(invalid, &setInstr) != TILEXR_ERROR_PARA_CHECK_FAIL || + TileXRCcuEncodeClearCke(invalid, &clearInstr) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "empty cke operation accepted\n"; + return 5; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_memory_transfer_microcode_encoders_match_hcomm_v1_layout(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_microcode.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuMemTransferSpec spec; + spec.localGsa = 0x101; + spec.localXn = 0x102; + spec.remoteGsa = 0x201; + spec.remoteXn = 0x202; + spec.lengthXn = 0x301; + spec.channelId = 0x12; + spec.reduceDataType = 0x0a; + spec.reduceOpCode = 0x05; + spec.setCkeId = 0x401; + spec.setCkeMask = 0x2; + spec.waitCkeId = 0x402; + spec.waitCkeMask = 0x3; + spec.clearWait = true; + spec.lengthFromXn = true; + spec.reduceEnabled = true; + + TileXRCcuInstr read; + if (TileXRCcuEncodeTransRmtMemToLocMem(spec, &read) != TILEXR_SUCCESS) { + std::cerr << "trans rmt->loc encode failed\n"; + return 1; + } + if (read.words[0] != 0x0201010201011008ULL || + read.words[1] != 0x5a00001203010202ULL || + read.words[2] != 0x0007000000000000ULL || + read.words[3] != 0x0003040200020401ULL) { + std::cerr << "unexpected trans rmt->loc words\n"; + return 2; + } + + TileXRCcuInstr write; + if (TileXRCcuEncodeTransLocMemToRmtMem(spec, &write) != TILEXR_SUCCESS) { + std::cerr << "trans loc->rmt encode failed\n"; + return 3; + } + if (write.words[0] != 0x0101020202011009ULL || + write.words[1] != 0x5a00001203010102ULL || + write.words[2] != 0x0007000000000000ULL || + write.words[3] != 0x0003040200020401ULL) { + std::cerr << "unexpected trans loc->rmt words\n"; + return 4; + } + + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_memory_transfer_microcode_rejects_missing_required_fields(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_microcode.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuInstr instr; + TileXRCcuMemTransferSpec empty; + if (TileXRCcuEncodeTransRmtMemToLocMem(empty, &instr) != TILEXR_ERROR_PARA_CHECK_FAIL || + TileXRCcuEncodeTransLocMemToRmtMem(empty, &instr) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "empty transfer accepted\n"; + return 1; + } + + TileXRCcuMemTransferSpec spec; + spec.localGsa = 1; + spec.localXn = 2; + spec.remoteGsa = 3; + spec.remoteXn = 4; + spec.lengthXn = 5; + spec.channelId = 6; + spec.setCkeId = 7; + spec.setCkeMask = 8; + + if (TileXRCcuEncodeTransRmtMemToLocMem(spec, nullptr) != TILEXR_ERROR_PARA_CHECK_FAIL || + TileXRCcuEncodeTransLocMemToRmtMem(spec, nullptr) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "null output accepted\n"; + return 2; + } + + spec.reduceDataType = 0x10; + if (TileXRCcuEncodeTransRmtMemToLocMem(spec, &instr) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "out-of-range reduce data type accepted\n"; + return 3; + } + + spec.reduceDataType = 0; + spec.reduceOpCode = 0x10; + if (TileXRCcuEncodeTransLocMemToRmtMem(spec, &instr) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "out-of-range reduce op code accepted\n"; + return 4; + } + + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_microcode_builder_is_wired_and_has_no_private_hcomm_surface(self): + cmake = COMM_CMAKE.read_text(encoding="utf-8") + header = MICROCODE_HEADER.read_text(encoding="utf-8") + source = MICROCODE_SOURCE.read_text(encoding="utf-8") + + self.assertIn("ccu/tilexr_ccu_microcode.h", cmake) + self.assertIn("ccu/tilexr_ccu_microcode.cpp", cmake) + self.assertIn("struct TileXRCcuInstr", header) + self.assertIn("struct TileXRCcuSyncXnSpec", header) + self.assertIn("TileXRCcuEncodeLoadSqeArgsToX", header) + self.assertIn("TileXRCcuEncodeLoadImdToXn", header) + self.assertIn("TileXRCcuEncodeLoadImdToGsa", header) + self.assertIn("TileXRCcuEncodeSyncXn", header) + self.assertIn("TileXRCcuEncodeSyncCke", header) + self.assertIn("TileXRCcuEncodeSetCke", header) + self.assertIn("TileXRCcuEncodeClearCke", header) + self.assertIn("struct TileXRCcuMemTransferSpec", header) + self.assertIn("TileXRCcuEncodeTransRmtMemToLocMem", header) + self.assertIn("TileXRCcuEncodeTransLocMemToRmtMem", header) + self.assertIn("0x0001U", source) + self.assertIn("0x0002U", source) + self.assertIn("0x0003U", source) + self.assertIn("0x0802U", source) + self.assertIn("0x0804U", source) + self.assertIn("0x1008U", source) + self.assertIn("0x1009U", source) + self.assertIn("0x100bU", source) + self.assertIn("0x100dU", source) + + combined = header + "\n" + source + for needle in PRIVATE_CCU_PRODUCER_NEEDLES: + with self.subTest(needle=needle): + self.assertNotIn(needle, combined) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/ccu/test_tilexr_ccu_producer_plan.py b/tests/ccu/test_tilexr_ccu_producer_plan.py new file mode 100644 index 00000000..fc61ea29 --- /dev/null +++ b/tests/ccu/test_tilexr_ccu_producer_plan.py @@ -0,0 +1,461 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2026 TileXR Project +# + +import subprocess +import tempfile +import textwrap +import unittest +import shutil +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[2] +CCU_HEADER = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_producer_plan.h" +CCU_SOURCE = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_producer_plan.cpp" +BARRIER_SOURCE = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_barrier_program.cpp" +MICROCODE_SOURCE = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_microcode.cpp" +RUNTIME_HEADER = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_runtime.h" +COMM_CMAKE = REPO_ROOT / "src" / "comm" / "CMakeLists.txt" +INCLUDE_DIR = REPO_ROOT / "src" / "include" +COMM_DIR = REPO_ROOT / "src" / "comm" + + +PRIVATE_CCU_PRODUCER_NEEDLES = [ + "#include + #include + #include + + using namespace TileXR; + + int main() + { + TileXRCcuProducerPlan plan; + plan.mission = {1, 6, 0x059b0f03U, true}; + plan.kernelLocalXn = {1, 1961, 62}; + plan.kernelLocalGsa = {1, 510, 20}; + plan.kernelLocalCke = {1, 332, 1}; + plan.kernelLocalMission = {1, 6, 1}; + plan.instructionWindow = {1, 475, 170, 489, 156}; + plan.syncResources.push_back({1, 1961, 2361, 364, 2, 3}); + plan.syncResources.push_back({1, 1962, 2362, 364, 3, 3}); + plan.syncResources.push_back({1, 1963, 2364, 364, 4, 3}); + plan.taskWindows.push_back({1, 489, 13, 13, {0x100051152e00ULL, 0x100051152e00ULL, 0x0010017f86b7d29aULL}}); + plan.taskWindows.push_back({1, 502, 143, 13, {}}); + + TileXRCcuProducerPlanReport report; + if (TileXRCcuValidateProducerPlan(plan, &report) != TILEXR_SUCCESS) { + std::cerr << "valid plan rejected: " << report.message << "\n"; + return 1; + } + if (report.syncResourceCount != 3 || report.taskCount != 2 || report.instructionCount != 170) { + std::cerr << "unexpected report counts\n"; + return 2; + } + + std::vector tasks; + if (TileXRCcuBuildTasks(plan, &tasks, &report) != TILEXR_SUCCESS) { + std::cerr << "task build failed: " << report.message << "\n"; + return 3; + } + if (tasks.size() != 2) { + std::cerr << "unexpected task size\n"; + return 4; + } + if (tasks[0].dieId != 1 || tasks[0].missionId != 6 || tasks[0].key != 0x059b0f03U || + tasks[0].instStartId != 489 || tasks[0].instCnt != 13 || tasks[0].argSize != 13 || + tasks[0].args[0] != 0x100051152e00ULL || tasks[0].args[2] != 0x0010017f86b7d29aULL) { + std::cerr << "unexpected sqe-load mission task\n"; + return 5; + } + if (tasks[1].dieId != 1 || tasks[1].missionId != 6 || tasks[1].key != 0x059b0f03U || + tasks[1].instStartId != 502 || tasks[1].instCnt != 143 || tasks[1].argSize != 13 || + tasks[1].args[0] != 0 || tasks[1].args[12] != 0) { + std::cerr << "unexpected sync mission task\n"; + return 6; + } + if (tasks[0].timeout != TILEXR_CCU_DEFAULT_TASK_TIMEOUT_SEC || + tasks[1].timeout != TILEXR_CCU_DEFAULT_TASK_TIMEOUT_SEC) { + std::cerr << "unexpected task timeout " << tasks[0].timeout + << " " << tasks[1].timeout << "\n"; + return 12; + } + TileXRCcuProgram program; + if (TileXRCcuBuildMicrocode(plan, &program, &report) != TILEXR_SUCCESS) { + std::cerr << "microcode build failed: " << report.message << "\n"; + return 7; + } + if (program.sqeLoad.size() != 13 || program.sync.size() != 11) { + std::cerr << "unexpected microcode sizes\n"; + return 8; + } + if (program.sqeLoad[0].words[0] != 0x0000000007a90001ULL || + program.sqeLoad[12].words[0] != 0x0000000c07b50001ULL) { + std::cerr << "unexpected sqe load microcode\n"; + return 9; + } + if (program.sync[0].words[0] != 0x0000000007a90001ULL || + program.sync[1].words[0] != 0x0000000107aa0001ULL || + program.sync[2].words[0] != 0x0000000007b60003ULL || + program.sync[3].words[0] != 0x0000000001fe0002ULL || + program.sync[4].words[0] != 0x0001016c00000802ULL || + program.sync[4].words[1] != 0) { + std::cerr << "unexpected hcomm-style task1 prelude\n"; + return 10; + } + if (program.sync[5].words[0] != 0x000007a90939100dULL || + program.sync[5].words[1] != 0x00000001016c0002ULL || + program.sync[5].words[2] != 0x0001000000000000ULL) { + std::cerr << "unexpected first sync microcode after prelude\n"; + return 10; + } + if (program.sync[8].words[0] != 0x0000000000010802ULL || + program.sync[8].words[1] != 0x000000000001016cULL) { + std::cerr << "unexpected sync wait microcode\n"; + return 11; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_plan_rejects_missing_producer_state(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_producer_plan.h" + + #include + #include + + using namespace TileXR; + + int main() + { + TileXRCcuProducerPlan plan; + plan.kernelLocalXn = {1, 1961, 62}; + plan.kernelLocalCke = {1, 332, 1}; + plan.kernelLocalMission = {1, 6, 1}; + plan.instructionWindow = {1, 475, 170, 489, 156}; + plan.syncResources.push_back({1, 1961, 2361, 364, 2, 3}); + plan.taskWindows.push_back({1, 489, 13, 13, {}}); + + TileXRCcuProducerPlanReport report; + if (TileXRCcuValidateProducerPlan(plan, &report) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "missing mission/key was accepted\n"; + return 1; + } + if (report.message.find("mission") == std::string::npos) { + std::cerr << "missing mission/key diagnostic was weak: " << report.message << "\n"; + return 2; + } + + plan.mission = {1, 6, 0x059b0f03U, true}; + plan.syncResources[0].bindingCount = 0; + if (TileXRCcuValidateProducerPlan(plan, &report) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "missing channel binding was accepted\n"; + return 3; + } + if (report.message.find("binding") == std::string::npos) { + std::cerr << "missing binding diagnostic was weak: " << report.message << "\n"; + return 4; + } + + plan.syncResources[0].bindingCount = 1; + plan.syncResources[0].channelId = 0; + if (TileXRCcuValidateProducerPlan(plan, &report) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "missing channel id was accepted\n"; + return 5; + } + if (report.message.find("channel id") == std::string::npos) { + std::cerr << "missing channel id diagnostic was weak: " << report.message << "\n"; + return 6; + } + + plan.syncResources[0].channelId = 2; + plan.syncResources.push_back({1, 1962, 2362, 365, 2, 1}); + if (TileXRCcuValidateProducerPlan(plan, &report) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "duplicate channel id was accepted\n"; + return 7; + } + if (report.message.find("duplicate channel id") == std::string::npos) { + std::cerr << "duplicate channel id diagnostic was weak: " << report.message << "\n"; + return 8; + } + plan.syncResources.pop_back(); + + plan.syncResources[0].localXn = 2040; + if (TileXRCcuValidateProducerPlan(plan, &report) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "out-of-range local xn was accepted\n"; + return 9; + } + if (report.message.find("local XN") == std::string::npos) { + std::cerr << "local xn diagnostic was weak: " << report.message << "\n"; + return 10; + } + + plan.syncResources[0].localXn = 1961; + plan.taskWindows[0].instStartId = 646; + if (TileXRCcuValidateProducerPlan(plan, &report) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "out-of-window task was accepted\n"; + return 11; + } + if (report.message.find("instruction") == std::string::npos) { + std::cerr << "instruction diagnostic was weak: " << report.message << "\n"; + return 12; + } + + plan.taskWindows[0].instStartId = 489; + plan.kernelLocalCke = {1, 0, 0}; + if (TileXRCcuValidateProducerPlan(plan, &report) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "missing kernel-local cke repository was accepted\n"; + return 13; + } + if (report.message.find("CKE") == std::string::npos) { + std::cerr << "kernel-local cke diagnostic was weak: " << report.message << "\n"; + return 14; + } + + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_producer_plan_is_wired_and_has_no_private_hcomm_surface(self): + cmake = COMM_CMAKE.read_text(encoding="utf-8") + header = CCU_HEADER.read_text(encoding="utf-8") + source = CCU_SOURCE.read_text(encoding="utf-8") + runtime_header = RUNTIME_HEADER.read_text(encoding="utf-8") + + self.assertIn("ccu/tilexr_ccu_producer_plan.h", cmake) + self.assertIn("ccu/tilexr_ccu_producer_plan.cpp", cmake) + self.assertIn('#include "ccu/tilexr_ccu_runtime.h"', header) + self.assertIn("TileXRCcuProducerPlan", header) + self.assertIn("TileXRCcuValidateProducerPlan", header) + self.assertIn("TileXRCcuBuildTasks", header) + self.assertIn("TileXRCcuBuildMicrocode", header) + self.assertIn("TileXRCcuProgram", header) + self.assertIn("localWaitCke", header) + self.assertIn("localWaitMask", header) + self.assertIn("remoteNotifyMask", header) + self.assertIn("tilexr_ccu_barrier_program.h", header) + self.assertIn("TileXRCcuBuildBarrierProgram", source) + self.assertIn("spec.localWaitCke", source) + self.assertIn("spec.remoteNotifyCke", source) + self.assertIn("TileXRCcuTask", runtime_header) + self.assertIn("kernelLocalCke", source) + self.assertIn("std::set channelIds", source) + self.assertIn("missing channel id for sync resource", source) + self.assertIn("duplicate channel id for sync resource", source) + + combined = header + "\n" + source + for needle in PRIVATE_CCU_PRODUCER_NEEDLES: + with self.subTest(needle=needle): + self.assertNotIn(needle, combined) + + def test_microcode_can_use_distinct_remote_notify_and_local_wait_cke(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_producer_plan.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuProducerPlan plan; + plan.mission = {1, 6, 0x059b0f03U, true}; + plan.kernelLocalXn = {1, 1961, 62}; + plan.kernelLocalCke = {1, 332, 4}; + plan.kernelLocalMission = {1, 6, 1}; + plan.instructionWindow = {1, 475, 170, 489, 156}; + + TileXRCcuSyncResource resource; + resource.dieId = 1; + resource.localXn = 1961; + resource.remoteXn = 2361; + resource.notifyCke = 364; + resource.channelId = 2; + resource.bindingCount = 3; + resource.localWaitCke = 332; + resource.localWaitMask = 1; + resource.remoteNotifyMask = 1; + plan.syncResources.push_back(resource); + + plan.taskWindows.push_back({1, 489, 13, 13, {}}); + plan.taskWindows.push_back({1, 502, 143, 13, {}}); + + TileXRCcuProgram program; + TileXRCcuProducerPlanReport report; + if (TileXRCcuBuildMicrocode(plan, &program, &report) != TILEXR_SUCCESS) { + std::cerr << "microcode build failed: " << report.message << "\n"; + return 1; + } + if (program.sync.size() != 7) { + std::cerr << "unexpected sync size\n"; + return 2; + } + if (program.sync[5].words[1] != 0x00000001016c0002ULL) { + std::cerr << "post did not use remote notify CKE\n"; + return 3; + } + if (program.sync[6].words[0] != 0x0000000000010802ULL || + program.sync[6].words[1] != 0x000000000001014cULL) { + std::cerr << "wait/clear did not use local wait CKE\n"; + return 4; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_microcode_can_emit_sync_cke_barrier_mode_with_source_cke(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_producer_plan.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuProducerPlan plan; + plan.barrierMode = TileXRCcuBarrierMode::SyncCke; + plan.mission = {1, 6, 0x059b0f03U, true}; + plan.kernelLocalXn = {1, 1961, 62}; + plan.kernelLocalCke = {1, 0x220, 2}; + plan.kernelLocalMission = {1, 6, 1}; + plan.instructionWindow = {1, 475, 170, 489, 156}; + + TileXRCcuSyncResource resource; + resource.dieId = 1; + resource.localXn = 1961; + resource.remoteXn = 2361; + resource.notifyCke = 0x330; + resource.channelId = 2; + resource.bindingCount = 1; + resource.localWaitCke = 0x220; + resource.localWaitMask = 1; + resource.remoteNotifyMask = 1; + resource.sourceCke = 0x221; + resource.sourceCkeMask = 0xffff; + plan.syncResources.push_back(resource); + + plan.taskWindows.push_back({1, 489, 13, 13, {}}); + plan.taskWindows.push_back({1, 502, 143, 13, {}}); + + TileXRCcuProgram program; + TileXRCcuProducerPlanReport report; + if (TileXRCcuBuildMicrocode(plan, &program, &report) != TILEXR_SUCCESS) { + std::cerr << "sync_cke microcode build failed: " << report.message << "\n"; + return 1; + } + if (program.sync.size() != 3) { + std::cerr << "unexpected sync_cke sync size\n"; + return 2; + } + if (program.sync[0].words[0] != 0xffff022100010802ULL || + program.sync[1].words[0] != 0x000102210330100bULL || + program.sync[1].words[1] != 0x0000000000000002ULL || + program.sync[2].words[0] != 0x0000000000010804ULL || + program.sync[2].words[1] != 0x0000000000010220ULL) { + std::cerr << "sync_cke barrier microcode mismatch\n"; + return 3; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/ccu/test_tilexr_ccu_resource_allocator.py b/tests/ccu/test_tilexr_ccu_resource_allocator.py new file mode 100644 index 00000000..17c105f4 --- /dev/null +++ b/tests/ccu/test_tilexr_ccu_resource_allocator.py @@ -0,0 +1,917 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2026 TileXR Project +# + +import shutil +import subprocess +import tempfile +import textwrap +import unittest +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[2] +ALLOCATOR_HEADER = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_resource_allocator.h" +ALLOCATOR_SOURCE = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_resource_allocator.cpp" +PRODUCER_SOURCE = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_producer_plan.cpp" +BARRIER_SOURCE = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_barrier_program.cpp" +MICROCODE_SOURCE = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_microcode.cpp" +COMM_CMAKE = REPO_ROOT / "src" / "comm" / "CMakeLists.txt" +INCLUDE_DIR = REPO_ROOT / "src" / "include" +COMM_DIR = REPO_ROOT / "src" / "comm" + + +class TileXRCcuResourceAllocatorTest(unittest.TestCase): + def compile_and_run(self, code: str): + compiler = shutil.which("g++") or shutil.which("clang++") or shutil.which("c++") + if compiler is None: + self.skipTest("no local C++ compiler found") + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + test_cpp = temp_path / "allocator_test.cpp" + test_bin = temp_path / "allocator_test" + test_cpp.write_text(code, encoding="utf-8") + subprocess.run( + [ + compiler, + "-std=c++14", + "-I", + str(INCLUDE_DIR), + "-I", + str(COMM_DIR), + str(test_cpp), + str(ALLOCATOR_SOURCE), + str(PRODUCER_SOURCE), + str(BARRIER_SOURCE), + str(MICROCODE_SOURCE), + "-o", + str(test_bin), + ], + cwd=REPO_ROOT, + check=True, + text=True, + capture_output=True, + ) + return subprocess.run([str(test_bin)], cwd=REPO_ROOT, check=False, text=True, capture_output=True) + + def test_allocator_declares_and_uses_separate_mission_instruction_start(self): + header = ALLOCATOR_HEADER.read_text(encoding="utf-8") + source = ALLOCATOR_SOURCE.read_text(encoding="utf-8") + + self.assertIn("uint16_t missionInstructionStartId = 0;", header) + self.assertIn("missionInstructionStartId", source) + self.assertIn("missionInstructionStart", source) + self.assertIn("repositoryPrefixCount", source) + self.assertIn("result.repository.num", source) + self.assertIn("result.repository.startId", source) + self.assertIn("generated.instructionWindow = {", source) + + def test_allocator_builds_complete_tilexr_owned_producer_plan(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_resource_allocator.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuResourceSpec spec; + spec.dieId = 1; + spec.missionKey = 0x059b0f03U; + spec.missionStartId = 6; + spec.missionCount = 2; + spec.instructionStartId = 475; + spec.instructionCount = 170; + spec.xnStartId = 1961; + spec.xnCount = 62; + spec.gsaStartId = 510; + spec.gsaCount = 20; + spec.ckeStartId = 332; + spec.ckeCount = 32; + spec.channelStartId = 2; + spec.channelCount = 4; + + TileXRCcuResourceRequest request; + request.sqeArgCount = TILEXR_CCU_SQE_ARGS_LEN; + request.syncResourceCount = 3; + request.syncInstructionCount = 143; + request.bindingsPerSyncResource = 3; + + TileXRCcuResourceAllocator allocator; + if (allocator.Init(spec) != TILEXR_SUCCESS) { + std::cerr << "allocator init failed\n"; + return 1; + } + + TileXRCcuProducerPlan plan; + TileXRCcuResourceAllocation allocation; + TileXRCcuResourceAllocatorReport report; + if (allocator.Allocate(request, &plan, &allocation, &report) != TILEXR_SUCCESS) { + std::cerr << "allocate failed: " << report.message << "\n"; + return 2; + } + + if (!plan.mission.installed || plan.mission.dieId != 1 || plan.mission.missionId != 6 || + plan.mission.key != 0x059b0f03U) { + std::cerr << "mission/key mismatch\n"; + return 3; + } + if (plan.kernelLocalMission.startId != 6 || plan.kernelLocalMission.num != 1 || + plan.kernelLocalXn.startId != 1961 || plan.kernelLocalXn.num != 14 || + plan.kernelLocalGsa.startId != 510 || plan.kernelLocalGsa.num != 1 || + plan.kernelLocalCke.startId != 332 || plan.kernelLocalCke.num != 3) { + std::cerr << "kernel local ranges mismatch\n"; + return 4; + } + if (plan.instructionWindow.repositoryStartId != 475 || + plan.instructionWindow.repositoryCount != 156 || + plan.instructionWindow.missionStartId != 475 || + plan.instructionWindow.missionCount != 156) { + std::cerr << "instruction window mismatch\n"; + return 5; + } + if (plan.syncResources.size() != 3 || plan.taskWindows.size() != 2) { + std::cerr << "resource/task count mismatch\n"; + return 6; + } + if (plan.syncResources[0].localXn != 1961 || plan.syncResources[0].remoteXn != 1975 || + plan.syncResources[0].notifyCke != 332 || plan.syncResources[0].channelId != 2 || + plan.syncResources[0].bindingCount != 3) { + std::cerr << "first sync resource mismatch\n"; + return 7; + } + if (plan.syncResources[2].localXn != 1963 || plan.syncResources[2].remoteXn != 1977 || + plan.syncResources[2].notifyCke != 334 || plan.syncResources[2].channelId != 4) { + std::cerr << "last sync resource mismatch\n"; + return 8; + } + if (plan.taskWindows[0].instStartId != 475 || plan.taskWindows[0].instCnt != 13 || + plan.taskWindows[0].argSize != 13 || + plan.taskWindows[1].instStartId != 488 || plan.taskWindows[1].instCnt != 143 || + plan.taskWindows[1].argSize != 13) { + std::cerr << "task window mismatch\n"; + return 9; + } + + TileXRCcuProducerPlanReport planReport; + if (TileXRCcuValidateProducerPlan(plan, &planReport) != TILEXR_SUCCESS) { + std::cerr << "generated plan invalid: " << planReport.message << "\n"; + return 10; + } + if (allocation.receiptId == 0 || allocation.packageProvider != "tilexr-hcomm-derived-resource-allocator" || + allocation.localXn.startId != 1961 || allocation.remoteXn.startId != 1975 || + allocation.localGsa.startId != 510 || allocation.localGsa.num != 1 || + allocation.notifyCke.startId != 332 || allocation.channels.startId != 2 || + allocation.channels.num != 3) { + std::cerr << "allocation receipt mismatch\n"; + return 11; + } + if (report.missionAllocated != 1 || report.localXnAllocated != 14 || + report.localGsaAllocated != 1 || + report.remoteXnAllocated != 3 || report.notifyCkeAllocated != 3 || + report.channelBindingsAllocated != 9 || report.repositoryAllocated != 156 || + report.message != "ok") { + std::cerr << "report mismatch\n"; + return 12; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_allocator_can_assign_distinct_local_wait_and_remote_notify_cke_ranges(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_resource_allocator.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuResourceSpec spec; + spec.dieId = 1; + spec.missionKey = 0x059b0f03U; + spec.missionStartId = 6; + spec.missionCount = 2; + spec.instructionStartId = 475; + spec.instructionCount = 170; + spec.xnStartId = 1961; + spec.xnCount = 62; + spec.ckeStartId = 332; + spec.ckeCount = 32; + spec.localWaitCkeStartId = 332; + spec.localWaitCkeCount = 8; + spec.remoteNotifyCkeStartId = 364; + spec.remoteNotifyCkeCount = 8; + spec.channelStartId = 2; + spec.channelCount = 4; + + TileXRCcuResourceRequest request; + request.sqeArgCount = TILEXR_CCU_SQE_ARGS_LEN; + request.syncResourceCount = 3; + request.syncInstructionCount = 143; + request.bindingsPerSyncResource = 3; + + TileXRCcuResourceAllocator allocator; + if (allocator.Init(spec) != TILEXR_SUCCESS) { + std::cerr << "allocator init failed\n"; + return 1; + } + + TileXRCcuProducerPlan plan; + TileXRCcuResourceAllocation allocation; + TileXRCcuResourceAllocatorReport report; + if (allocator.Allocate(request, &plan, &allocation, &report) != TILEXR_SUCCESS) { + std::cerr << "allocate failed: " << report.message << "\n"; + return 2; + } + + if (allocation.localWaitCke.startId != 332 || allocation.localWaitCke.num != 3 || + allocation.remoteNotifyCke.startId != 364 || allocation.remoteNotifyCke.num != 3 || + allocation.notifyCke.startId != 364 || allocation.notifyCke.num != 3) { + std::cerr << "split CKE allocation ranges mismatch\n"; + return 3; + } + if (plan.kernelLocalCke.startId != 332 || plan.kernelLocalCke.num != 3) { + std::cerr << "kernel-local CKE should describe the local wait CKE range\n"; + return 4; + } + if (plan.syncResources.size() != 3 || + plan.syncResources[0].localWaitCke != 332 || + plan.syncResources[0].notifyCke != 364 || + plan.syncResources[1].localWaitCke != 333 || + plan.syncResources[1].notifyCke != 365 || + plan.syncResources[2].localWaitCke != 334 || + plan.syncResources[2].notifyCke != 366) { + std::cerr << "split CKE sync resources mismatch\n"; + return 5; + } + if (report.localWaitCkeAllocated != 3 || + report.remoteNotifyCkeAllocated != 3 || + report.notifyCkeAllocated != 3) { + std::cerr << "split CKE report mismatch\n"; + return 6; + } + + TileXRCcuProducerPlanReport planReport; + if (TileXRCcuValidateProducerPlan(plan, &planReport) != TILEXR_SUCCESS) { + std::cerr << "split CKE generated plan invalid: " << planReport.message << "\n"; + return 7; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_allocator_can_reserve_repository_prefix_before_mission_instruction_window(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_resource_allocator.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuResourceSpec spec; + spec.dieId = 1; + spec.missionKey = 0x059b0f03U; + spec.missionStartId = 6; + spec.missionCount = 2; + spec.instructionStartId = 475; + spec.missionInstructionStartId = 489; + spec.instructionCount = 170; + spec.xnStartId = 1961; + spec.xnCount = 62; + spec.ckeStartId = 332; + spec.ckeCount = 32; + spec.channelStartId = 2; + spec.channelCount = 4; + + TileXRCcuResourceRequest request; + request.sqeArgCount = TILEXR_CCU_SQE_ARGS_LEN; + request.syncResourceCount = 3; + request.syncInstructionCount = 143; + request.bindingsPerSyncResource = 3; + + TileXRCcuResourceAllocator allocator; + if (allocator.Init(spec) != TILEXR_SUCCESS) { + std::cerr << "allocator init failed\n"; + return 1; + } + + TileXRCcuProducerPlan plan; + TileXRCcuResourceAllocation allocation; + TileXRCcuResourceAllocatorReport report; + if (allocator.Allocate(request, &plan, &allocation, &report) != TILEXR_SUCCESS) { + std::cerr << "allocate failed: " << report.message << "\n"; + return 2; + } + + if (plan.instructionWindow.repositoryStartId != 475 || + plan.instructionWindow.repositoryCount != 170 || + plan.instructionWindow.missionStartId != 489 || + plan.instructionWindow.missionCount != 156) { + std::cerr << "repository/mission instruction window mismatch\n"; + return 3; + } + if (plan.taskWindows.size() != 2 || + plan.taskWindows[0].instStartId != 489 || + plan.taskWindows[0].instCnt != 13 || + plan.taskWindows[1].instStartId != 502 || + plan.taskWindows[1].instCnt != 143) { + std::cerr << "mission task windows did not start at mission instruction window\n"; + return 4; + } + if (allocation.repository.startId != 475 || + allocation.repository.num != 170 || + report.repositoryAllocated != 170) { + std::cerr << "repository allocation/report did not include prefix\n"; + return 5; + } + + TileXRCcuProducerPlanReport planReport; + if (TileXRCcuValidateProducerPlan(plan, &planReport) != TILEXR_SUCCESS) { + std::cerr << "prefixed repository plan invalid: " << planReport.message << "\n"; + return 6; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_allocator_builds_pure_barrier_plan_without_sqe_load_task(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_resource_allocator.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuResourceSpec spec; + spec.dieId = 0; + spec.missionKey = 0x12345678U; + spec.missionStartId = 1; + spec.missionCount = 1; + spec.instructionStartId = 1; + spec.instructionCount = 16; + spec.xnStartId = 1; + spec.xnCount = 8; + spec.ckeStartId = 1; + spec.ckeCount = 8; + spec.channelStartId = 1; + spec.channelCount = 2; + + TileXRCcuResourceRequest request; + request.sqeArgCount = 0; + request.syncResourceCount = 1; + request.syncInstructionCount = 2; + request.bindingsPerSyncResource = 1; + + TileXRCcuResourceAllocator allocator; + if (allocator.Init(spec) != TILEXR_SUCCESS) { + std::cerr << "allocator init failed\n"; + return 1; + } + + TileXRCcuProducerPlan plan; + TileXRCcuResourceAllocation allocation; + TileXRCcuResourceAllocatorReport report; + if (allocator.Allocate(request, &plan, &allocation, &report) != TILEXR_SUCCESS) { + std::cerr << "allocate failed: " << report.message << "\n"; + return 2; + } + + if (plan.taskWindows.size() != 1) { + std::cerr << "pure barrier should have one sync task\n"; + return 3; + } + if (plan.taskWindows[0].instStartId != 1 || + plan.taskWindows[0].instCnt != 2 || + plan.taskWindows[0].argSize != TILEXR_CCU_SQE_ARGS_LEN) { + std::cerr << "pure barrier sync task mismatch\n"; + return 4; + } + if (plan.kernelLocalXn.startId != 1 || plan.kernelLocalXn.num != 1 || + allocation.localXn.startId != 1 || allocation.localXn.num != 1 || + allocation.remoteXn.startId != 2 || allocation.remoteXn.num != 1 || + allocation.repository.startId != 1 || allocation.repository.num != 2) { + std::cerr << "pure barrier allocation mismatch\n"; + return 5; + } + if (report.localXnAllocated != 1 || + report.remoteXnAllocated != 1 || + report.repositoryAllocated != 2) { + std::cerr << "pure barrier report mismatch\n"; + return 6; + } + TileXRCcuProducerPlanReport planReport; + if (TileXRCcuValidateProducerPlan(plan, &planReport) != TILEXR_SUCCESS) { + std::cerr << "pure barrier plan invalid: " << planReport.message << "\n"; + return 7; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_allocator_assigns_independent_source_cke_for_sync_cke_barrier_mode(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_resource_allocator.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuResourceSpec spec; + spec.dieId = 1; + spec.missionKey = 0x059b0f03U; + spec.missionStartId = 6; + spec.missionCount = 2; + spec.instructionStartId = 475; + spec.instructionCount = 170; + spec.xnStartId = 1961; + spec.xnCount = 62; + spec.ckeStartId = 0x220; + spec.ckeCount = 16; + spec.localWaitCkeStartId = 0x220; + spec.localWaitCkeCount = 8; + spec.remoteNotifyCkeStartId = 0x330; + spec.remoteNotifyCkeCount = 8; + spec.channelStartId = 2; + spec.channelCount = 4; + + TileXRCcuResourceRequest request; + request.sqeArgCount = TILEXR_CCU_SQE_ARGS_LEN; + request.syncResourceCount = 1; + request.syncInstructionCount = 3; + request.bindingsPerSyncResource = 1; + request.barrierMode = TileXRCcuBarrierMode::SyncCke; + + TileXRCcuResourceAllocator allocator; + if (allocator.Init(spec) != TILEXR_SUCCESS) { + std::cerr << "allocator init failed\n"; + return 1; + } + + TileXRCcuProducerPlan plan; + TileXRCcuResourceAllocation allocation; + TileXRCcuResourceAllocatorReport report; + if (allocator.Allocate(request, &plan, &allocation, &report) != TILEXR_SUCCESS) { + std::cerr << "allocate failed: " << report.message << "\n"; + return 2; + } + + if (plan.barrierMode != TileXRCcuBarrierMode::SyncCke || + allocation.localWaitCke.startId != 0x220 || allocation.localWaitCke.num != 1 || + allocation.sourceCke.startId != 0x221 || allocation.sourceCke.num != 1 || + plan.kernelLocalCke.startId != 0x220 || plan.kernelLocalCke.num != 2) { + std::cerr << "sync_cke CKE allocation ranges mismatch\n"; + return 3; + } + if (plan.syncResources.size() != 1 || + plan.syncResources[0].notifyCke != 0x330 || + plan.syncResources[0].localWaitCke != 0x220 || + plan.syncResources[0].sourceCke != 0x221 || + plan.syncResources[0].sourceCkeMask != 0xffff) { + std::cerr << "sync_cke sync resource CKE fields mismatch\n"; + return 4; + } + TileXRCcuProducerPlanReport planReport; + if (TileXRCcuValidateProducerPlan(plan, &planReport) != TILEXR_SUCCESS) { + std::cerr << "sync_cke generated plan invalid: " << planReport.message << "\n"; + return 5; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_allocator_accepts_synccke_post_only_with_source_cke_and_two_instructions(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_resource_allocator.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuResourceSpec spec; + spec.dieId = 1; + spec.missionKey = 0x059b0f03U; + spec.missionStartId = 6; + spec.missionCount = 2; + spec.instructionStartId = 475; + spec.instructionCount = 170; + spec.xnStartId = 1961; + spec.xnCount = 62; + spec.ckeStartId = 0x220; + spec.ckeCount = 16; + spec.localWaitCkeStartId = 0x220; + spec.localWaitCkeCount = 8; + spec.remoteNotifyCkeStartId = 0x330; + spec.remoteNotifyCkeCount = 8; + spec.channelStartId = 2; + spec.channelCount = 4; + + TileXRCcuResourceRequest request; + request.sqeArgCount = TILEXR_CCU_SQE_ARGS_LEN; + request.syncResourceCount = 1; + request.syncInstructionCount = 2; + request.bindingsPerSyncResource = 1; + request.barrierMode = TileXRCcuBarrierMode::SyncCkePostOnly; + + TileXRCcuResourceAllocator allocator; + if (allocator.Init(spec) != TILEXR_SUCCESS) { + std::cerr << "allocator init failed\n"; + return 1; + } + + TileXRCcuProducerPlan plan; + TileXRCcuResourceAllocation allocation; + TileXRCcuResourceAllocatorReport report; + if (allocator.Allocate(request, &plan, &allocation, &report) != TILEXR_SUCCESS) { + std::cerr << "allocate failed: " << report.message << "\n"; + return 2; + } + + if (plan.barrierMode != TileXRCcuBarrierMode::SyncCkePostOnly || + allocation.sourceCke.startId != 0x221 || allocation.sourceCke.num != 1 || + plan.instructionWindow.repositoryCount != TILEXR_CCU_SQE_ARGS_LEN + 2) { + std::cerr << "sync_cke_post_only allocation mismatch\n"; + return 3; + } + if (plan.syncResources.size() != 1 || + plan.syncResources[0].sourceCke != 0x221 || + plan.syncResources[0].notifyCke != 0x330 || + plan.syncResources[0].localWaitCke != 0x220) { + std::cerr << "sync_cke_post_only resource fields mismatch\n"; + return 4; + } + TileXRCcuProducerPlanReport planReport; + if (TileXRCcuValidateProducerPlan(plan, &planReport) != TILEXR_SUCCESS) { + std::cerr << "sync_cke_post_only generated plan invalid: " << planReport.message << "\n"; + return 5; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_allocator_accepts_local_cke_post_only_with_one_instruction(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_resource_allocator.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuResourceSpec spec; + spec.dieId = 1; + spec.missionKey = 0x059b0f03U; + spec.missionStartId = 6; + spec.missionCount = 2; + spec.instructionStartId = 475; + spec.instructionCount = 170; + spec.xnStartId = 1961; + spec.xnCount = 62; + spec.ckeStartId = 0x220; + spec.ckeCount = 16; + spec.localWaitCkeStartId = 0x220; + spec.localWaitCkeCount = 8; + spec.remoteNotifyCkeStartId = 0x330; + spec.remoteNotifyCkeCount = 8; + spec.channelStartId = 2; + spec.channelCount = 4; + + TileXRCcuResourceRequest request; + request.sqeArgCount = 0; + request.syncResourceCount = 1; + request.syncInstructionCount = 1; + request.bindingsPerSyncResource = 1; + request.barrierMode = TileXRCcuBarrierMode::LocalCkePostOnly; + + TileXRCcuResourceAllocator allocator; + if (allocator.Init(spec) != TILEXR_SUCCESS) { + std::cerr << "allocator init failed\n"; + return 1; + } + + TileXRCcuProducerPlan plan; + TileXRCcuResourceAllocation allocation; + TileXRCcuResourceAllocatorReport report; + if (allocator.Allocate(request, &plan, &allocation, &report) != TILEXR_SUCCESS) { + std::cerr << "allocate failed: " << report.message << "\n"; + return 2; + } + + if (plan.barrierMode != TileXRCcuBarrierMode::LocalCkePostOnly || + plan.taskWindows.size() != 1 || + plan.taskWindows[0].instCnt != 1 || + plan.instructionWindow.repositoryCount != 1) { + std::cerr << "local_cke_post_only allocation mismatch\n"; + return 3; + } + TileXRCcuProducerPlanReport planReport; + if (TileXRCcuValidateProducerPlan(plan, &planReport) != TILEXR_SUCCESS) { + std::cerr << "local_cke_post_only generated plan invalid: " << planReport.message << "\n"; + return 4; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_allocator_rejects_resource_exhaustion_and_double_release(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_resource_allocator.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuResourceSpec spec; + spec.dieId = 0; + spec.missionKey = 0x12345678U; + spec.missionStartId = 1; + spec.missionCount = 1; + spec.instructionStartId = 100; + spec.instructionCount = 32; + spec.xnStartId = 200; + spec.xnCount = 17; + spec.ckeStartId = 300; + spec.ckeCount = 2; + spec.channelStartId = 4; + spec.channelCount = 1; + + TileXRCcuResourceRequest request; + request.sqeArgCount = TILEXR_CCU_SQE_ARGS_LEN; + request.syncResourceCount = 3; + request.syncInstructionCount = 11; + request.bindingsPerSyncResource = 1; + + TileXRCcuResourceAllocator allocator; + if (allocator.Init(spec) != TILEXR_SUCCESS) { + std::cerr << "init failed\n"; + return 1; + } + + TileXRCcuProducerPlan plan; + TileXRCcuResourceAllocation allocation; + TileXRCcuResourceAllocatorReport report; + if (allocator.Allocate(request, &plan, &allocation, &report) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "resource exhaustion was accepted\n"; + return 2; + } + if (report.message.find("insufficient CKE resources") == std::string::npos) { + std::cerr << "weak exhaustion diagnostic: " << report.message << "\n"; + return 3; + } + + spec.ckeCount = 8; + spec.channelCount = 3; + if (allocator.Init(spec) != TILEXR_SUCCESS) { + std::cerr << "reinit failed\n"; + return 4; + } + if (allocator.Allocate(request, &plan, &allocation, &report) != TILEXR_SUCCESS) { + std::cerr << "second allocate failed: " << report.message << "\n"; + return 5; + } + if (allocator.Release(allocation.receiptId) != TILEXR_SUCCESS) { + std::cerr << "release failed\n"; + return 6; + } + if (allocator.Release(allocation.receiptId) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "double release was accepted\n"; + return 7; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_allocator_rejects_sync_instruction_window_too_small_for_barrier_program(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_resource_allocator.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuResourceSpec spec; + spec.dieId = 1; + spec.missionKey = 0x059b0f03U; + spec.missionStartId = 6; + spec.missionCount = 2; + spec.instructionStartId = 475; + spec.instructionCount = 170; + spec.xnStartId = 1961; + spec.xnCount = 62; + spec.ckeStartId = 332; + spec.ckeCount = 32; + spec.channelStartId = 2; + spec.channelCount = 4; + + TileXRCcuResourceRequest request; + request.sqeArgCount = TILEXR_CCU_SQE_ARGS_LEN; + request.syncResourceCount = 2; + request.syncInstructionCount = 3; + request.bindingsPerSyncResource = 1; + + TileXRCcuResourceAllocator allocator; + if (allocator.Init(spec) != TILEXR_SUCCESS) { + std::cerr << "allocator init failed\n"; + return 1; + } + + TileXRCcuProducerPlan plan; + TileXRCcuResourceAllocation allocation; + TileXRCcuResourceAllocatorReport report; + if (allocator.Allocate(request, &plan, &allocation, &report) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "undersized barrier sync instruction window was accepted\n"; + return 2; + } + if (report.message.find("barrier") == std::string::npos || + report.message.find("sync instruction") == std::string::npos) { + std::cerr << "weak barrier instruction diagnostic: " << report.message << "\n"; + return 3; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_allocator_counts_hcomm_style_task1_prelude_for_two_task_sync_xn_programs(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_resource_allocator.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuResourceSpec spec; + spec.dieId = 1; + spec.missionKey = 0x059b0f03U; + spec.missionStartId = 6; + spec.missionCount = 2; + spec.instructionStartId = 475; + spec.instructionCount = 170; + spec.xnStartId = 1961; + spec.xnCount = 62; + spec.ckeStartId = 332; + spec.ckeCount = 32; + spec.channelStartId = 2; + spec.channelCount = 4; + + TileXRCcuResourceRequest request; + request.sqeArgCount = TILEXR_CCU_SQE_ARGS_LEN; + request.syncResourceCount = 2; + request.syncInstructionCount = 2; + request.bindingsPerSyncResource = 1; + request.barrierMode = TileXRCcuBarrierMode::SyncXnPostOnly; + + TileXRCcuResourceAllocator allocator; + if (allocator.Init(spec) != TILEXR_SUCCESS) { + std::cerr << "allocator init failed\n"; + return 1; + } + + TileXRCcuProducerPlan plan; + TileXRCcuResourceAllocation allocation; + TileXRCcuResourceAllocatorReport report; + if (allocator.Allocate(request, &plan, &allocation, &report) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "undersized hcomm-style task1 prelude window was accepted\n"; + return 2; + } + if (report.message.find("prelude") == std::string::npos || + report.message.find("sync instruction") == std::string::npos) { + std::cerr << "weak prelude instruction diagnostic: " << report.message << "\n"; + return 3; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_allocator_is_wired_and_does_not_reference_hcomm_runtime_surface(self): + cmake = COMM_CMAKE.read_text(encoding="utf-8") + header = ALLOCATOR_HEADER.read_text(encoding="utf-8") + source = ALLOCATOR_SOURCE.read_text(encoding="utf-8") + + self.assertIn("ccu/tilexr_ccu_resource_allocator.h", cmake) + self.assertIn("ccu/tilexr_ccu_resource_allocator.cpp", cmake) + self.assertIn("TileXRCcuResourceAllocator", header) + self.assertIn("TileXRCcuResourceSpec", header) + self.assertIn("TileXRCcuResourceRequest", header) + self.assertIn("TileXRCcuResourceAllocation", header) + self.assertIn("TileXRCcuProducerPlan", header) + self.assertIn("tilexr-hcomm-derived-resource-allocator", source) + self.assertIn("requiredBarrierInstructionCount", source) + self.assertIn("syncResourceCount * 2U", source) + self.assertIn("TileXRCcuBarrierMode::LocalCkePostOnly", source) + self.assertIn("request.barrierMode == TileXRCcuBarrierMode::LocalCkePostOnly", source) + self.assertIn("barrier sync instruction window is too small", source) + self.assertIn("const uint32_t channelCount = request.syncResourceCount", source) + self.assertNotIn("TILEXR_CCU_DIRECT_SYNC_RESOURCE_MAP", source) + self.assertNotIn("UseHcommTraceSyncResourceMap", source) + self.assertNotIn("TILEXR_CCU_DIRECT_SQE_LOAD_XN_MAP", source) + self.assertNotIn("TILEXR_CCU_DIRECT_TASK1_PRELUDE_CKE", source) + + combined = header + "\n" + source + for needle in [ + "#include ", source) + self.assertIn("static_assert(RT_CCU_SQE_ARGS_LEN == TILEXR_CCU_SQE_ARGS_LEN", source) + self.assertIn("rtCcuTaskInfo_t runtimeTask", source) + self.assertIn("rtCCULaunch(&runtimeTask, stream)", source) + self.assertIn("stream == nullptr", source) + self.assertIn("RT_CCU_INST_CNT_INVALID", source) + self.assertIn("RT_CCU_INST_START_MAX", source) + self.assertIn("task.argSize != 1 && task.argSize != TILEXR_CCU_SQE_ARGS_LEN", source) + self.assertNotIn("TILEXR_CCU_DIRECT_TASK_DIE_ID", source) + self.assertNotIn("TILEXR_CCU_DIRECT_TASK_TIMEOUT", source) + self.assertNotIn("TILEXR_CCU_DIRECT_TASK_ARG_SIZE", source) + self.assertNotIn("TILEXR_CCU_DIRECT_TASK_ARG", source) + self.assertNotIn("ApplyRuntimeTaskOverrides", source) + self.assertIn("TILEXR_ERROR_MKIRT", source) + + combined = header + "\n" + source + for needle in PRIVATE_CCU_PRODUCER_NEEDLES: + with self.subTest(needle=needle): + self.assertNotIn(needle, combined) + + def test_rt_ccu_launch_reference_is_confined_to_runtime_submit_wrapper(self): + offenders = [] + for root in [REPO_ROOT / "src" / "comm", REPO_ROOT / "src" / "include"]: + for path in root.rglob("*"): + if path.is_file() and path.suffix in {".h", ".hpp", ".cpp", ".cc", ".c"}: + text = path.read_text(encoding="utf-8", errors="replace") + if "rtCCULaunch" in text and path != CCU_SOURCE: + offenders.append(path.relative_to(REPO_ROOT).as_posix()) + self.assertEqual([], offenders) + + def test_runtime_kernel_header_is_confined_to_runtime_submit_wrapper(self): + offenders = [] + for root in [REPO_ROOT / "src" / "comm", REPO_ROOT / "src" / "include"]: + for path in root.rglob("*"): + if path.is_file() and path.suffix in {".h", ".hpp", ".cpp", ".cc", ".c"}: + text = path.read_text(encoding="utf-8", errors="replace") + if "runtime/kernel.h" in text and path != CCU_SOURCE: + offenders.append(path.relative_to(REPO_ROOT).as_posix()) + self.assertEqual([], offenders) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/ccu/test_tilexr_ccu_specs.py b/tests/ccu/test_tilexr_ccu_specs.py new file mode 100644 index 00000000..4760eb98 --- /dev/null +++ b/tests/ccu/test_tilexr_ccu_specs.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2026 TileXR Project +# + +import shutil +import subprocess +import tempfile +import textwrap +import unittest +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[2] +SPECS_HEADER = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_specs.h" +SPECS_SOURCE = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_specs.cpp" +ALLOCATOR_HEADER = REPO_ROOT / "src" / "comm" / "ccu" / "tilexr_ccu_resource_allocator.h" +COMM_CMAKE = REPO_ROOT / "src" / "comm" / "CMakeLists.txt" +INCLUDE_DIR = REPO_ROOT / "src" / "include" +COMM_DIR = REPO_ROOT / "src" / "comm" + + +class TileXRCcuSpecsTest(unittest.TestCase): + def compile_and_run(self, code: str): + compiler = shutil.which("g++") or shutil.which("clang++") or shutil.which("c++") + if compiler is None: + self.skipTest("no local C++ compiler found") + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + test_cpp = temp_path / "specs_test.cpp" + test_bin = temp_path / "specs_test" + test_cpp.write_text(code, encoding="utf-8") + subprocess.run( + [ + compiler, + "-std=c++14", + "-I", + str(INCLUDE_DIR), + "-I", + str(COMM_DIR), + str(test_cpp), + str(SPECS_SOURCE), + "-o", + str(test_bin), + ], + cwd=REPO_ROOT, + check=True, + text=True, + capture_output=True, + ) + return subprocess.run([str(test_bin)], cwd=REPO_ROOT, check=False, text=True, capture_output=True) + + def test_decodes_hcomm_basic_info_caps_into_tilexr_spec(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_specs.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuBasicInfo basic; + basic.dieId = 1; + basic.msId = 0x25; + basic.missionKey = 0x059b0f03U; + basic.resourceAddr = 0x100000000ULL; + basic.caps.cap0 = (7U << 24) | (11U << 16) | 169U; + basic.caps.cap1 = (61U << 16) | 31U; + basic.caps.cap2 = (63U << 16) | 35U; + basic.caps.cap3 = (127U << 16) | 3U; + basic.caps.cap4 = 15U; + + TileXRCcuSpecInfo info; + TileXRCcuSpecsReport report; + if (TileXRCcuDecodeBasicInfo(basic, &info, &report) != TILEXR_SUCCESS) { + std::cerr << "decode failed: " << report.message << "\n"; + return 1; + } + + if (info.dieId != 1 || info.msId != 0x25 || info.missionKey != 0x059b0f03U || + info.resourceAddr != 0x100000000ULL) { + std::cerr << "base fields mismatch\n"; + return 2; + } + if (info.instructionNum != 170 || info.xnNum != 62 || info.gsaNum != 32 || + info.msNum != 64 || info.ckeNum != 36 || info.jettyNum != 128 || + info.channelNum != 4 || info.pfeNum != 16 || + info.missionNum != 12 || info.loopEngineNum != 8) { + std::cerr << "caps decode mismatch\n"; + return 3; + } + if (info.xnBaseAddr != 0x100000000ULL + TILEXR_CCU_V1_XN_RESOURCE_OFFSET) { + std::cerr << "xn base mismatch\n"; + return 4; + } + + TileXRCcuResourceSpec spec; + if (TileXRCcuBuildResourceSpec(info, 6, 475, 1961, 332, 2, &spec, &report, 510) != TILEXR_SUCCESS) { + std::cerr << "build spec failed: " << report.message << "\n"; + return 5; + } + if (spec.dieId != 1 || spec.missionKey != 0x059b0f03U || + spec.missionStartId != 6 || spec.missionCount != 12 || + spec.instructionStartId != 475 || spec.instructionCount != 170 || + spec.gsaStartId != 510 || spec.gsaCount != 32 || + spec.xnStartId != 1961 || spec.xnCount != 62 || + spec.ckeStartId != 332 || spec.ckeCount != 36 || + spec.channelStartId != 2 || spec.channelCount != 4) { + std::cerr << "resource spec mismatch\n"; + return 6; + } + if (report.message != "ok" || report.instructionNum != 170 || report.xnNum != 62) { + std::cerr << "report mismatch\n"; + return 7; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_rejects_invalid_basic_info_and_overflowing_windows(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_specs.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuBasicInfo basic; + basic.dieId = 0; + basic.missionKey = 0; + basic.resourceAddr = 0x100000000ULL; + basic.caps.cap0 = 15U; + basic.caps.cap1 = 15U; + basic.caps.cap2 = 15U; + basic.caps.cap3 = 15U; + + TileXRCcuSpecInfo info; + TileXRCcuSpecsReport report; + if (TileXRCcuDecodeBasicInfo(basic, &info, &report) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "zero mission key was accepted\n"; + return 1; + } + if (report.message.find("missing CCU mission key") == std::string::npos) { + std::cerr << "weak mission diagnostic: " << report.message << "\n"; + return 2; + } + + basic.missionKey = 0x12345678U; + basic.caps.cap0 = (31U << 16) | 15U; + if (TileXRCcuDecodeBasicInfo(basic, &info, &report) != TILEXR_SUCCESS) { + std::cerr << "decode failed after fixing key: " << report.message << "\n"; + return 3; + } + + TileXRCcuResourceSpec spec; + if (TileXRCcuBuildResourceSpec(info, 65520, 10, 20, 30, 40, &spec, &report) != + TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "overflowing mission window was accepted\n"; + return 4; + } + if (report.message.find("mission resource window overflows") == std::string::npos) { + std::cerr << "weak overflow diagnostic: " << report.message << "\n"; + return 5; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_specs_are_wired_and_do_not_reference_hcomm_runtime_surface(self): + cmake = COMM_CMAKE.read_text(encoding="utf-8") + header = SPECS_HEADER.read_text(encoding="utf-8") + source = SPECS_SOURCE.read_text(encoding="utf-8") + allocator_header = ALLOCATOR_HEADER.read_text(encoding="utf-8") + + self.assertIn("ccu/tilexr_ccu_specs.h", cmake) + self.assertIn("ccu/tilexr_ccu_specs.cpp", cmake) + self.assertIn("tilexr_ccu_resource_allocator.h", header) + self.assertIn("TileXRCcuBasicInfo", header) + self.assertIn("TileXRCcuSpecInfo", header) + self.assertIn("TileXRCcuDecodeBasicInfo", header) + self.assertIn("TileXRCcuBuildResourceSpec", header) + self.assertIn("gsaStartId", header) + self.assertIn("gsaStartId", source) + self.assertIn("TileXRCcuResourceSpec", allocator_header) + + combined = header + "\n" + source + for needle in [ + "#include Date: Tue, 28 Jul 2026 12:00:57 +0800 Subject: [PATCH 2/3] ci: validate stacked CCU pull requests --- .github/workflows/pr-ci.yml | 4 +++- tests/ci/test_workflows.rb | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index ae8ab0d7..f6489f24 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -2,7 +2,9 @@ name: PR CI "on": pull_request: - branches: [main] + branches: + - main + - "codex/ccu-stack-*" types: [opened, reopened, synchronize, ready_for_review, converted_to_draft, closed] permissions: diff --git a/tests/ci/test_workflows.rb b/tests/ci/test_workflows.rb index 69690d28..4a0e099e 100644 --- a/tests/ci/test_workflows.rb +++ b/tests/ci/test_workflows.rb @@ -45,8 +45,8 @@ def named_step(workflow, job_name, step_name) assert_equal(["pull_request"], pr.fetch("on").keys, "PR workflow must use only pull_request") pull_request = pr.fetch("on").fetch("pull_request") -assert_equal(["main"], pull_request.fetch("branches"), - "PR workflow must target main") +assert_equal(["main", "codex/ccu-stack-*"], pull_request.fetch("branches"), + "PR workflow must target main and the temporary CCU stack") assert_equal(%w[opened reopened synchronize ready_for_review converted_to_draft closed], pull_request.fetch("types"), "PR event types differ") assert_equal({"contents" => "read"}, pr.fetch("permissions"), From 2ed3374e70b3358412455a5af70a2c8cb6e3d076 Mon Sep 17 00:00:00 2001 From: GoodTimeLZ Date: Tue, 28 Jul 2026 19:18:51 +0800 Subject: [PATCH 3/3] fix(ccu): address core planning review feedback --- src/comm/ccu/tilexr_ccu_barrier_program.cpp | 18 +- src/comm/ccu/tilexr_ccu_memory_program.cpp | 4 + src/comm/ccu/tilexr_ccu_microcode.cpp | 13 +- src/comm/ccu/tilexr_ccu_producer_plan.cpp | 22 +- .../ccu/tilexr_ccu_resource_allocator.cpp | 47 ++++- src/comm/tilexr_comm.cpp | 2 +- tests/ccu/test_tilexr_ccu_barrier_program.py | 21 +- tests/ccu/test_tilexr_ccu_memory_program.py | 9 + tests/ccu/test_tilexr_ccu_microcode.py | 20 ++ tests/ccu/test_tilexr_ccu_producer_plan.py | 8 +- .../ccu/test_tilexr_ccu_resource_allocator.py | 194 +++++++++++++++++- 11 files changed, 333 insertions(+), 25 deletions(-) diff --git a/src/comm/ccu/tilexr_ccu_barrier_program.cpp b/src/comm/ccu/tilexr_ccu_barrier_program.cpp index 319caeea..33b4a145 100644 --- a/src/comm/ccu/tilexr_ccu_barrier_program.cpp +++ b/src/comm/ccu/tilexr_ccu_barrier_program.cpp @@ -86,6 +86,19 @@ bool SyncCkeSetWaitMode(TileXRCcuBarrierMode mode) return mode == TileXRCcuBarrierMode::SyncCkeSetWait; } +bool HasConflictingSourceCkeMasks(const std::vector& specs) +{ + for (size_t i = 0; i < specs.size(); ++i) { + for (size_t j = i + 1; j < specs.size(); ++j) { + if (specs[i].sourceCke == specs[j].sourceCke && + specs[i].sourceCkeMask != specs[j].sourceCkeMask) { + return true; + } + } + } + return false; +} + void AddSourceCkeInitInstructions( const std::vector& specs, std::vector* program) @@ -193,6 +206,9 @@ int TileXRCcuBuildBarrierProgram( return Fail(program, report, "missing local wait CKE resource for CCU barrier program"); } } + if (SyncCkeMode(mode) && HasConflictingSourceCkeMasks(specs)) { + return Fail(program, report, "conflicting source CKE masks for CCU barrier program"); + } program->reserve(specs.size() * (SyncCkeMode(mode) ? 3U : 2U)); if (mode == TileXRCcuBarrierMode::LocalCke) { @@ -221,7 +237,7 @@ int TileXRCcuBuildBarrierProgram( TileXRCcuSyncCkeSpec post; post.remoteCke = spec.remoteNotifyCke; post.localCke = spec.sourceCke; - post.localCkeMask = spec.remoteNotifyMask; + post.localCkeMask = spec.sourceCkeMask; post.channelId = spec.channelId; if (TileXRCcuEncodeSyncCke(post, &instr) != TILEXR_SUCCESS) { return Fail(program, report, "failed to encode CCU barrier SyncCKE post instruction"); diff --git a/src/comm/ccu/tilexr_ccu_memory_program.cpp b/src/comm/ccu/tilexr_ccu_memory_program.cpp index a1559abf..3a14eff9 100644 --- a/src/comm/ccu/tilexr_ccu_memory_program.cpp +++ b/src/comm/ccu/tilexr_ccu_memory_program.cpp @@ -42,6 +42,10 @@ int ValidateSpec( if (program == nullptr) { return Fail(program, report, "missing output CCU memory copy program"); } + if (spec.direction != TileXRCcuMemoryCopyDirection::RemoteToLocal && + spec.direction != TileXRCcuMemoryCopyDirection::LocalToRemote) { + return Fail(program, report, "invalid CCU memory copy direction"); + } if (spec.localGsa == 0 || spec.localXn == 0 || spec.remoteGsa == 0 || spec.remoteXn == 0 || spec.lengthXn == 0) { return Fail(program, report, "missing CCU memory copy GSA/XN resources"); diff --git a/src/comm/ccu/tilexr_ccu_microcode.cpp b/src/comm/ccu/tilexr_ccu_microcode.cpp index 44abfdbe..fcdeaa93 100644 --- a/src/comm/ccu/tilexr_ccu_microcode.cpp +++ b/src/comm/ccu/tilexr_ccu_microcode.cpp @@ -77,6 +77,11 @@ int ValidateTransferSpec(const TileXRCcuMemTransferSpec& spec) return TILEXR_SUCCESS; } +bool CompleteOrAbsentCkePair(uint16_t ckeId, uint16_t mask) +{ + return (ckeId == 0) == (mask == 0); +} + void WriteLe16(uint8_t* bytes, size_t offset, uint16_t value) { bytes[offset] = static_cast(value & 0xffU); @@ -196,7 +201,9 @@ int TileXRCcuEncodeSetCke(const TileXRCcuCkeSpec& spec, TileXRCcuInstr* instr) if (ValidateInstrOutput(instr) != TILEXR_SUCCESS) { return TILEXR_ERROR_PARA_CHECK_FAIL; } - if ((spec.ckeId == 0 || spec.mask == 0) && (spec.waitCkeId == 0 || spec.waitMask == 0)) { + if (!CompleteOrAbsentCkePair(spec.ckeId, spec.mask) || + !CompleteOrAbsentCkePair(spec.waitCkeId, spec.waitMask) || + (spec.ckeId == 0 && spec.waitCkeId == 0)) { return TILEXR_ERROR_PARA_CHECK_FAIL; } @@ -211,7 +218,9 @@ int TileXRCcuEncodeClearCke(const TileXRCcuCkeSpec& spec, TileXRCcuInstr* instr) if (ValidateInstrOutput(instr) != TILEXR_SUCCESS) { return TILEXR_ERROR_PARA_CHECK_FAIL; } - if ((spec.ckeId == 0 || spec.mask == 0) && (spec.waitCkeId == 0 || spec.waitMask == 0)) { + if (!CompleteOrAbsentCkePair(spec.ckeId, spec.mask) || + !CompleteOrAbsentCkePair(spec.waitCkeId, spec.waitMask) || + (spec.ckeId == 0 && spec.waitCkeId == 0)) { return TILEXR_ERROR_PARA_CHECK_FAIL; } diff --git a/src/comm/ccu/tilexr_ccu_producer_plan.cpp b/src/comm/ccu/tilexr_ccu_producer_plan.cpp index d87cd503..4fb648b0 100644 --- a/src/comm/ccu/tilexr_ccu_producer_plan.cpp +++ b/src/comm/ccu/tilexr_ccu_producer_plan.cpp @@ -175,7 +175,9 @@ int ValidateInstructionWindow(const TileXRCcuProducerPlan& plan, TileXRCcuProduc int ValidateKernelLocalRepositories(const TileXRCcuProducerPlan& plan, TileXRCcuProducerPlanReport* report) { - if (plan.kernelLocalXn.dieId != plan.mission.dieId || plan.kernelLocalXn.num == 0) { + const bool needsLocalXn = SyncXnMode(plan.barrierMode) || plan.taskWindows.size() > 1; + if (needsLocalXn && + (plan.kernelLocalXn.dieId != plan.mission.dieId || plan.kernelLocalXn.num == 0)) { return Fail(report, "missing kernel-local XN repository range"); } if (plan.kernelLocalCke.dieId != plan.mission.dieId || plan.kernelLocalCke.num == 0) { @@ -200,11 +202,13 @@ int ValidateSyncResources(const TileXRCcuProducerPlan& plan, TileXRCcuProducerPl if (!channelIds.insert(resource.channelId).second) { return Fail(report, "duplicate channel id for sync resource"); } - if (!ContainsId(plan.kernelLocalXn, resource.dieId, resource.localXn)) { - return Fail(report, "local XN is outside the kernel-local XN repository range"); - } - if (resource.remoteXn == 0) { - return Fail(report, "missing channel-bound remote XN"); + if (SyncXnMode(plan.barrierMode)) { + if (!ContainsId(plan.kernelLocalXn, resource.dieId, resource.localXn)) { + return Fail(report, "local XN is outside the kernel-local XN repository range"); + } + if (resource.remoteXn == 0) { + return Fail(report, "missing channel-bound remote XN"); + } } if (resource.notifyCke == 0) { return Fail(report, "missing remote notify CKE resource"); @@ -245,6 +249,10 @@ int ValidateTasks(const TileXRCcuProducerPlan& plan, TileXRCcuProducerPlanReport if (plan.taskWindows.empty()) { return Fail(report, "missing CCU task windows"); } + if (plan.taskWindows.size() > 1 && + plan.taskWindows.front().instCnt != TILEXR_CCU_SQE_ARGS_LEN) { + return Fail(report, "SQE load task must contain the complete CCU SQE argument window"); + } for (const auto& window : plan.taskWindows) { if (window.dieId != plan.mission.dieId) { return Fail(report, "task die does not match mission die"); @@ -412,7 +420,7 @@ int TileXRCcuBuildMicrocode( program->sqeLoad.clear(); program->sync.clear(); if (plan.taskWindows.size() > 1) { - if (AppendSqeLoadProgram(plan, TILEXR_CCU_SQE_ARGS_LEN, &program->sqeLoad, report) != TILEXR_SUCCESS) { + if (AppendSqeLoadProgram(plan, plan.taskWindows.front().instCnt, &program->sqeLoad, report) != TILEXR_SUCCESS) { return TILEXR_ERROR_PARA_CHECK_FAIL; } } diff --git a/src/comm/ccu/tilexr_ccu_resource_allocator.cpp b/src/comm/ccu/tilexr_ccu_resource_allocator.cpp index e9bb407b..d12843be 100644 --- a/src/comm/ccu/tilexr_ccu_resource_allocator.cpp +++ b/src/comm/ccu/tilexr_ccu_resource_allocator.cpp @@ -130,7 +130,7 @@ int TileXRCcuResourceAllocator::Init(const TileXRCcuResourceSpec& spec) const bool splitRemoteXn = spec.remoteXnCount != 0; if (spec.missionKey == 0 || spec.missionCount == 0 || spec.instructionCount == 0 || - spec.xnCount == 0 || localWaitCkeCount == 0 || remoteNotifyCkeCount == 0 || + localWaitCkeCount == 0 || remoteNotifyCkeCount == 0 || spec.channelCount == 0 || AddWouldOverflow(spec.missionStartId, spec.missionCount) || AddWouldOverflow(spec.instructionStartId, spec.instructionCount) || @@ -176,13 +176,16 @@ int TileXRCcuResourceAllocator::Allocate( *plan = TileXRCcuProducerPlan{}; *allocation = TileXRCcuResourceAllocation{}; - if (request.sqeArgCount > TILEXR_CCU_SQE_ARGS_LEN) { + if (request.sqeArgCount != 0 && request.sqeArgCount != TILEXR_CCU_SQE_ARGS_LEN) { return Fail(report, "invalid SQE argument count"); } if (request.syncResourceCount == 0 || request.syncInstructionCount == 0 || request.bindingsPerSyncResource == 0) { return Fail(report, "invalid CCU sync resource request"); } + if (request.bindingsPerSyncResource > std::numeric_limits::max()) { + return Fail(report, "channel binding count exceeds CCU plan encoding width"); + } if (request.syncResourceCount > std::numeric_limits::max() / 2U) { return Fail(report, "barrier sync instruction window is too small for CCU post and wait microcode"); } @@ -190,6 +193,7 @@ int TileXRCcuResourceAllocator::Allocate( request.barrierMode == TileXRCcuBarrierMode::SyncCkeSetWait || request.barrierMode == TileXRCcuBarrierMode::SyncCkePostOnly; const bool hcommStyleTask1Prelude = request.sqeArgCount != 0 && SyncXnMode(request.barrierMode); + const bool syncXnMode = SyncXnMode(request.barrierMode); const bool postOnly = request.barrierMode == TileXRCcuBarrierMode::SyncXnPostOnly || request.barrierMode == TileXRCcuBarrierMode::SyncCkePostOnly || request.barrierMode == TileXRCcuBarrierMode::LocalCkePostOnly; @@ -209,8 +213,8 @@ int TileXRCcuResourceAllocator::Allocate( } const uint32_t localSqeXnCount = RequiredSqeLoadXnCount(request.sqeArgCount, hcommStyleTask1Prelude); - const uint32_t localXnCount = std::max(localSqeXnCount, request.syncResourceCount); - const uint32_t remoteXnCount = request.syncResourceCount; + const uint32_t localXnCount = std::max(localSqeXnCount, syncXnMode ? request.syncResourceCount : 0U); + const uint32_t remoteXnCount = syncXnMode ? request.syncResourceCount : 0U; const uint32_t localGsaCount = hcommStyleTask1Prelude && spec_.gsaCount != 0 ? 1U : 0U; const uint32_t totalXnCount = localXnCount + remoteXnCount; const uint32_t localWaitCkeCount = request.syncResourceCount; @@ -297,8 +301,10 @@ int TileXRCcuResourceAllocator::Allocate( for (uint32_t i = 0; i < request.syncResourceCount; ++i) { TileXRCcuSyncResource resource; resource.dieId = spec_.dieId; - resource.localXn = static_cast(result.localXn.startId + i); - resource.remoteXn = static_cast(static_cast(result.remoteXn.startId) + i); + if (syncXnMode) { + resource.localXn = static_cast(result.localXn.startId + i); + resource.remoteXn = static_cast(static_cast(result.remoteXn.startId) + i); + } resource.notifyCke = static_cast(static_cast(result.remoteNotifyCke.startId) + i); resource.channelId = static_cast(static_cast(result.channels.startId) + i); resource.bindingCount = CheckedU16(request.bindingsPerSyncResource); @@ -370,6 +376,35 @@ int TileXRCcuResourceAllocator::Release(uint64_t receiptId) if (it == active_.end()) { return TILEXR_ERROR_PARA_CHECK_FAIL; } + const auto latest = active_.rbegin(); + if (latest == active_.rend() || latest->first != receiptId) { + return TILEXR_ERROR_PARA_CHECK_FAIL; + } + const ActiveAllocation& active = it->second; + const uint32_t xnUsed = static_cast(active.localXnUsed) + + (remoteXn_.count == 0 ? active.remoteXnUsed : 0U); + const uint32_t localCkeUsed = + static_cast(active.localWaitCkeUsed) + active.sourceCkeUsed; + if (mission_.used < active.missionUsed || + repository_.used < active.repositoryUsed || + xn_.used < xnUsed || + gsa_.used < active.localGsaUsed || + localWaitCke_.used < localCkeUsed || + remoteNotifyCke_.used < active.remoteNotifyCkeUsed || + channel_.used < active.channelUsed || + (remoteXn_.count != 0 && remoteXn_.used < active.remoteXnUsed)) { + return TILEXR_ERROR_INTERNAL; + } + mission_.used = static_cast(mission_.used - active.missionUsed); + repository_.used = static_cast(repository_.used - active.repositoryUsed); + xn_.used = static_cast(xn_.used - xnUsed); + if (remoteXn_.count != 0) { + remoteXn_.used = static_cast(remoteXn_.used - active.remoteXnUsed); + } + gsa_.used = static_cast(gsa_.used - active.localGsaUsed); + localWaitCke_.used = static_cast(localWaitCke_.used - localCkeUsed); + remoteNotifyCke_.used = static_cast(remoteNotifyCke_.used - active.remoteNotifyCkeUsed); + channel_.used = static_cast(channel_.used - active.channelUsed); active_.erase(it); return TILEXR_SUCCESS; } diff --git a/src/comm/tilexr_comm.cpp b/src/comm/tilexr_comm.cpp index 69736d0d..3fcae640 100644 --- a/src/comm/tilexr_comm.cpp +++ b/src/comm/tilexr_comm.cpp @@ -26,9 +26,9 @@ #include "tilexr_log.h" #include "tools/socket/tilexr_sock_exchange.h" -#include "runtime/kernel.h" #include "runtime/mem.h" #include "runtime/dev.h" +#include "runtime/rts/rts_device.h" #include "runtime/rt_ffts.h" enum TopologyType : int { diff --git a/tests/ccu/test_tilexr_ccu_barrier_program.py b/tests/ccu/test_tilexr_ccu_barrier_program.py index 254a1b82..14f7933f 100644 --- a/tests/ccu/test_tilexr_ccu_barrier_program.py +++ b/tests/ccu/test_tilexr_ccu_barrier_program.py @@ -242,7 +242,7 @@ def test_barrier_program_can_emit_hcomm_like_synccke_post_and_clear_wait(self): std::cerr << "source CKE init mismatch\n"; return 3; } - if (program[1].words[0] != 0x000102210330100bULL || + if (program[1].words[0] != 0xffff02210330100bULL || program[1].words[1] != 0x0000000000000002ULL || program[1].words[2] != 0x0001000000000000ULL || program[1].words[3] != 0) { @@ -256,6 +256,21 @@ def test_barrier_program_can_emit_hcomm_like_synccke_post_and_clear_wait(self): std::cerr << "ClearCKE wait mismatch\n"; return 5; } + + TileXRCcuBarrierSyncSpec conflicting = spec; + conflicting.channelId = 3; + conflicting.remoteNotifyCke = 0x331; + conflicting.localWaitCke = 0x222; + conflicting.sourceCkeMask = 1; + if (TileXRCcuBuildBarrierProgram( + std::vector{spec, conflicting}, + &program, + &report, + TileXRCcuBarrierMode::SyncCke) != TILEXR_ERROR_PARA_CHECK_FAIL || + !program.empty() || report.message.find("conflicting source CKE masks") == std::string::npos) { + std::cerr << "conflicting source CKE masks accepted: " << report.message << "\n"; + return 6; + } return 0; } ''' @@ -309,7 +324,7 @@ def test_barrier_program_can_emit_synccke_post_and_set_wait(self): std::cerr << "source CKE init mismatch\n"; return 3; } - if (program[1].words[0] != 0x000102210330100bULL || + if (program[1].words[0] != 0xffff02210330100bULL || program[1].words[1] != 0x0000000000000002ULL || program[1].words[2] != 0x0001000000000000ULL || program[1].words[3] != 0) { @@ -374,7 +389,7 @@ def test_barrier_program_can_emit_synccke_post_only_diagnostic(self): std::cerr << "source CKE init mismatch\n"; return 3; } - if (program[1].words[0] != 0x000102210330100bULL || + if (program[1].words[0] != 0xffff02210330100bULL || program[1].words[1] != 0x0000000000000002ULL || program[1].words[2] != 0x0001000000000000ULL || program[1].words[3] != 0) { diff --git a/tests/ccu/test_tilexr_ccu_memory_program.py b/tests/ccu/test_tilexr_ccu_memory_program.py index 29868acb..8ec2a7c0 100644 --- a/tests/ccu/test_tilexr_ccu_memory_program.py +++ b/tests/ccu/test_tilexr_ccu_memory_program.py @@ -277,6 +277,15 @@ def test_memory_program_rejects_missing_required_fields(self): std::cerr << "zero length accepted or weak diagnostic: " << report.message << "\n"; return 4; } + + spec.lengthBytes = 128; + spec.direction = static_cast(99); + if (TileXRCcuBuildMemoryCopyProgram(spec, &program, &report) != + TILEXR_ERROR_PARA_CHECK_FAIL || + !program.empty() || report.message.find("direction") == std::string::npos) { + std::cerr << "invalid direction accepted or weak diagnostic: " << report.message << "\n"; + return 5; + } return 0; } ''' diff --git a/tests/ccu/test_tilexr_ccu_microcode.py b/tests/ccu/test_tilexr_ccu_microcode.py index bf06d5e5..4e4a51c2 100644 --- a/tests/ccu/test_tilexr_ccu_microcode.py +++ b/tests/ccu/test_tilexr_ccu_microcode.py @@ -401,6 +401,26 @@ def test_set_and_clear_cke_microcode_encoders_match_hcomm_v1_layout(self): std::cerr << "empty cke operation accepted\n"; return 5; } + + TileXRCcuCkeSpec partialSet; + partialSet.ckeId = 332; + partialSet.waitCkeId = 364; + partialSet.waitMask = 1; + if (TileXRCcuEncodeSetCke(partialSet, &setInstr) != TILEXR_ERROR_PARA_CHECK_FAIL || + TileXRCcuEncodeClearCke(partialSet, &clearInstr) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "partial set CKE pair accepted\n"; + return 6; + } + + TileXRCcuCkeSpec partialWait; + partialWait.ckeId = 332; + partialWait.mask = 1; + partialWait.waitCkeId = 364; + if (TileXRCcuEncodeSetCke(partialWait, &setInstr) != TILEXR_ERROR_PARA_CHECK_FAIL || + TileXRCcuEncodeClearCke(partialWait, &clearInstr) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "partial wait CKE pair accepted\n"; + return 7; + } return 0; } ''' diff --git a/tests/ccu/test_tilexr_ccu_producer_plan.py b/tests/ccu/test_tilexr_ccu_producer_plan.py index fc61ea29..1877da61 100644 --- a/tests/ccu/test_tilexr_ccu_producer_plan.py +++ b/tests/ccu/test_tilexr_ccu_producer_plan.py @@ -182,6 +182,12 @@ def test_plan_validates_evidence_and_generates_launch_tasks(self): std::cerr << "unexpected sync wait microcode\n"; return 11; } + plan.taskWindows[0].instCnt = 1; + if (TileXRCcuValidateProducerPlan(plan, &report) != TILEXR_ERROR_PARA_CHECK_FAIL || + report.message.find("SQE load task") == std::string::npos) { + std::cerr << "partial SQE load task accepted: " << report.message << "\n"; + return 13; + } return 0; } ''' @@ -439,7 +445,7 @@ def test_microcode_can_emit_sync_cke_barrier_mode_with_source_cke(self): return 2; } if (program.sync[0].words[0] != 0xffff022100010802ULL || - program.sync[1].words[0] != 0x000102210330100bULL || + program.sync[1].words[0] != 0xffff02210330100bULL || program.sync[1].words[1] != 0x0000000000000002ULL || program.sync[2].words[0] != 0x0000000000010804ULL || program.sync[2].words[1] != 0x0000000000010220ULL) { diff --git a/tests/ccu/test_tilexr_ccu_resource_allocator.py b/tests/ccu/test_tilexr_ccu_resource_allocator.py index 17c105f4..61eb77ea 100644 --- a/tests/ccu/test_tilexr_ccu_resource_allocator.py +++ b/tests/ccu/test_tilexr_ccu_resource_allocator.py @@ -602,6 +602,96 @@ def test_allocator_accepts_synccke_post_only_with_source_cke_and_two_instruction self.assertEqual("", result.stderr) self.assertEqual(0, result.returncode, result.stdout + result.stderr) + def test_allocator_builds_all_cke_only_modes_without_xn_resources(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_resource_allocator.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuResourceSpec spec; + spec.dieId = 1; + spec.missionKey = 0x059b0f03U; + spec.missionStartId = 6; + spec.missionCount = 1; + spec.instructionStartId = 475; + spec.instructionCount = 16; + spec.xnStartId = 0; + spec.xnCount = 0; + spec.ckeStartId = 0x220; + spec.ckeCount = 16; + spec.localWaitCkeStartId = 0x220; + spec.localWaitCkeCount = 8; + spec.remoteNotifyCkeStartId = 0x330; + spec.remoteNotifyCkeCount = 8; + spec.channelStartId = 2; + spec.channelCount = 4; + + struct ModeCase { + TileXRCcuBarrierMode mode; + uint32_t instructionCount; + }; + const ModeCase cases[] = { + {TileXRCcuBarrierMode::SyncCke, 3}, + {TileXRCcuBarrierMode::SyncCkeSetWait, 3}, + {TileXRCcuBarrierMode::SyncCkePostOnly, 2}, + {TileXRCcuBarrierMode::LocalCke, 2}, + {TileXRCcuBarrierMode::LocalCkePostOnly, 1}, + }; + + for (const auto& modeCase : cases) { + TileXRCcuResourceRequest request; + request.sqeArgCount = 0; + request.syncResourceCount = 1; + request.syncInstructionCount = modeCase.instructionCount; + request.bindingsPerSyncResource = 1; + request.barrierMode = modeCase.mode; + + TileXRCcuResourceAllocator allocator; + if (allocator.Init(spec) != TILEXR_SUCCESS) { + std::cerr << "allocator init failed\n"; + return 1; + } + + TileXRCcuProducerPlan plan; + TileXRCcuResourceAllocation allocation; + TileXRCcuResourceAllocatorReport report; + if (allocator.Allocate(request, &plan, &allocation, &report) != TILEXR_SUCCESS) { + std::cerr << "CKE-only allocate failed: " << report.message << "\n"; + return 2; + } + if (allocation.localXn.num != 0 || allocation.remoteXn.num != 0 || + plan.kernelLocalXn.num != 0) { + std::cerr << "CKE-only mode allocated XN resources\n"; + return 3; + } + + TileXRCcuProducerPlanReport planReport; + TileXRCcuProgram program; + if (TileXRCcuValidateProducerPlan(plan, &planReport) != TILEXR_SUCCESS || + TileXRCcuBuildMicrocode(plan, &program, &planReport) != TILEXR_SUCCESS) { + std::cerr << "CKE-only plan failed: " << planReport.message << "\n"; + return 4; + } + if (!program.sqeLoad.empty() || program.sync.size() != modeCase.instructionCount) { + std::cerr << "CKE-only microcode size mismatch\n"; + return 5; + } + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + def test_allocator_accepts_local_cke_post_only_with_one_instruction(self): code = textwrap.dedent( r''' @@ -620,8 +710,8 @@ def test_allocator_accepts_local_cke_post_only_with_one_instruction(self): spec.missionCount = 2; spec.instructionStartId = 475; spec.instructionCount = 170; - spec.xnStartId = 1961; - spec.xnCount = 62; + spec.xnStartId = 0; + spec.xnCount = 0; spec.ckeStartId = 0x220; spec.ckeCount = 16; spec.localWaitCkeStartId = 0x220; @@ -655,6 +745,7 @@ def test_allocator_accepts_local_cke_post_only_with_one_instruction(self): if (plan.barrierMode != TileXRCcuBarrierMode::LocalCkePostOnly || plan.taskWindows.size() != 1 || plan.taskWindows[0].instCnt != 1 || + allocation.localXn.num != 0 || allocation.remoteXn.num != 0 || plan.instructionWindow.repositoryCount != 1) { std::cerr << "local_cke_post_only allocation mismatch\n"; return 3; @@ -714,6 +805,20 @@ def test_allocator_rejects_resource_exhaustion_and_double_release(self): TileXRCcuProducerPlan plan; TileXRCcuResourceAllocation allocation; TileXRCcuResourceAllocatorReport report; + request.sqeArgCount = 1; + if (allocator.Allocate(request, &plan, &allocation, &report) != TILEXR_ERROR_PARA_CHECK_FAIL || + report.message.find("SQE argument count") == std::string::npos) { + std::cerr << "partial SQE load request accepted: " << report.message << "\n"; + return 8; + } + request.sqeArgCount = TILEXR_CCU_SQE_ARGS_LEN; + request.bindingsPerSyncResource = 65536; + if (allocator.Allocate(request, &plan, &allocation, &report) != TILEXR_ERROR_PARA_CHECK_FAIL || + report.message.find("binding count") == std::string::npos) { + std::cerr << "oversized binding count accepted: " << report.message << "\n"; + return 9; + } + request.bindingsPerSyncResource = 1; if (allocator.Allocate(request, &plan, &allocation, &report) != TILEXR_ERROR_PARA_CHECK_FAIL) { std::cerr << "resource exhaustion was accepted\n"; return 2; @@ -733,14 +838,95 @@ def test_allocator_rejects_resource_exhaustion_and_double_release(self): std::cerr << "second allocate failed: " << report.message << "\n"; return 5; } + const TileXRCcuResourceAllocation released = allocation; if (allocator.Release(allocation.receiptId) != TILEXR_SUCCESS) { std::cerr << "release failed\n"; return 6; } - if (allocator.Release(allocation.receiptId) != TILEXR_ERROR_PARA_CHECK_FAIL) { - std::cerr << "double release was accepted\n"; + if (allocator.Allocate(request, &plan, &allocation, &report) != TILEXR_SUCCESS || + allocation.mission.startId != released.mission.startId || + allocation.repository.startId != released.repository.startId || + allocation.localXn.startId != released.localXn.startId || + allocation.remoteXn.startId != released.remoteXn.startId || + allocation.localWaitCke.startId != released.localWaitCke.startId || + allocation.remoteNotifyCke.startId != released.remoteNotifyCke.startId || + allocation.channels.startId != released.channels.startId) { + std::cerr << "released resources were not reused: " << report.message << "\n"; return 7; } + if (allocator.Release(released.receiptId) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "double release was accepted\n"; + return 10; + } + if (allocator.Release(allocation.receiptId) != TILEXR_SUCCESS) { + std::cerr << "replacement release failed\n"; + return 11; + } + return 0; + } + ''' + ) + + result = self.compile_and_run(code) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_allocator_rejects_non_lifo_release(self): + code = textwrap.dedent( + r''' + #include "ccu/tilexr_ccu_resource_allocator.h" + + #include + + using namespace TileXR; + + int main() + { + TileXRCcuResourceSpec spec; + spec.dieId = 0; + spec.missionKey = 0x12345678U; + spec.missionStartId = 10; + spec.missionCount = 2; + spec.instructionStartId = 100; + spec.instructionCount = 8; + spec.xnStartId = 200; + spec.xnCount = 4; + spec.ckeStartId = 300; + spec.ckeCount = 4; + spec.channelStartId = 20; + spec.channelCount = 2; + + TileXRCcuResourceRequest request; + request.sqeArgCount = 0; + request.syncResourceCount = 1; + request.syncInstructionCount = 2; + request.bindingsPerSyncResource = 1; + + TileXRCcuResourceAllocator allocator; + if (allocator.Init(spec) != TILEXR_SUCCESS) { + std::cerr << "init failed\n"; + return 1; + } + + TileXRCcuProducerPlan plan; + TileXRCcuResourceAllocation first; + TileXRCcuResourceAllocation second; + TileXRCcuResourceAllocatorReport report; + if (allocator.Allocate(request, &plan, &first, &report) != TILEXR_SUCCESS || + allocator.Allocate(request, &plan, &second, &report) != TILEXR_SUCCESS) { + std::cerr << "allocate failed: " << report.message << "\n"; + return 2; + } + if (allocator.Release(first.receiptId) != TILEXR_ERROR_PARA_CHECK_FAIL) { + std::cerr << "non-LIFO release was accepted\n"; + return 3; + } + if (allocator.Release(second.receiptId) != TILEXR_SUCCESS || + allocator.Release(first.receiptId) != TILEXR_SUCCESS) { + std::cerr << "tail releases failed\n"; + return 4; + } return 0; } '''