// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception // Copyright (C) 2025 Autonomy / OpenPLC Project // This file is part of the STruC++ Runtime Library and is covered by the // STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. /** * STruC++ Runtime - Located Variables Support * * This header defines the types and structures needed for located variables * (AT %IX0.0, %QX0.0, %MW100, etc.) that bind to runtime I/O image tables. * * Located variables provide the interface between IEC programs and physical * I/O in PLC systems. The compiler generates a descriptor array that the * runtime uses to bind variables to I/O addresses. */ #pragma once #include #include #include "iec_fault.hpp" #if STRUCPP_HAS_EXCEPTIONS #include #endif namespace strucpp { // ============================================================================= // Located Variable Area Types // ============================================================================= /** * Memory area for located variables. * Corresponds to the first letter after % in IEC addresses: * - I = Input (read from physical inputs) * - Q = Output (write to physical outputs) * - M = Memory (internal markers/flags) */ enum class LocatedArea : uint8_t { Input = 0, // %I - Input area Output = 1, // %Q - Output area Memory = 2 // %M - Memory/Marker area }; // ============================================================================= // Located Variable Size Types // ============================================================================= /** * Data size for located variables. * Corresponds to the size specifier in IEC addresses: * - X = Bit (1 bit) * - B = Byte (8 bits) * - W = Word (16 bits) * - D = Double Word (32 bits) * - L = Long Word (64 bits) */ enum class LocatedSize : uint8_t { Bit = 0, // %?X - Single bit Byte = 1, // %?B - 8 bits Word = 2, // %?W - 16 bits DWord = 3, // %?D - 32 bits LWord = 4 // %?L - 64 bits }; // ============================================================================= // Located Variable Descriptor // ============================================================================= /** * Descriptor for a located variable. * * This structure is generated by the compiler for each located variable * in the program. The runtime uses this descriptor array to: * 1. Bind variables to I/O image tables at startup * 2. Copy values between I/O images and variables during scan cycle * 3. Support variable forcing for debugging * * Memory Layout (16 bytes, aligned): * - area: 1 byte * - size: 1 byte * - byte_index: 2 bytes (major address component) * - bit_index: 1 byte (minor address component, 0-7 for bits) * - reserved: 3 bytes (padding for alignment) * - pointer: 8 bytes (pointer to variable storage) * * Address Format: * %. * * Examples: * %IX0.5 -> area=Input, size=Bit, byte_index=0, bit_index=5 * %QW10 -> area=Output, size=Word, byte_index=10, bit_index=0 * %MD100 -> area=Memory, size=DWord, byte_index=100, bit_index=0 */ struct LocatedVar { LocatedArea area; ///< Memory area (I, Q, or M) LocatedSize size; ///< Data size (X, B, W, D, or L) uint16_t byte_index; ///< Byte offset in the I/O image uint8_t bit_index; ///< Bit offset within byte (0-7, only for X size) uint8_t _reserved[3]; ///< Padding for alignment void* pointer; ///< Pointer to the variable's raw storage /** * Check if this descriptor represents a bit-addressed variable. */ constexpr bool is_bit() const noexcept { return size == LocatedSize::Bit; } /** * Get the size in bytes for this variable. * Returns 0 for bit-addressed variables (handled specially). */ constexpr size_t byte_size() const noexcept { switch (size) { case LocatedSize::Bit: return 0; // Special handling case LocatedSize::Byte: return 1; case LocatedSize::Word: return 2; case LocatedSize::DWord: return 4; case LocatedSize::LWord: return 8; default: return 0; } } }; // Verify expected layout (size varies by platform: 16 bytes on 64-bit, 8 on 32-bit, 6 on AVR) #if INTPTR_MAX == INT64_MAX static_assert(sizeof(LocatedVar) == 16, "LocatedVar should be 16 bytes on 64-bit"); static_assert(alignof(LocatedVar) == 8, "LocatedVar should be 8-byte aligned on 64-bit"); #endif // ============================================================================= // Helper Functions for Address Parsing // ============================================================================= /** * Parse an area character to LocatedArea enum. * @param c The area character ('I', 'Q', or 'M') * @return The corresponding LocatedArea value * @throws std::invalid_argument if character is invalid */ inline LocatedArea parse_area(char c) { switch (c) { case 'I': case 'i': return LocatedArea::Input; case 'Q': case 'q': return LocatedArea::Output; case 'M': case 'm': return LocatedArea::Memory; #if STRUCPP_HAS_EXCEPTIONS default: throw std::invalid_argument("Invalid area character"); #else default: iec_runtime_fault(IecFault::BadLocation, "Invalid area character"); #endif } } /** * Parse a size character to LocatedSize enum. * @param c The size character ('X', 'B', 'W', 'D', or 'L') * @return The corresponding LocatedSize value * @throws std::invalid_argument if character is invalid */ inline LocatedSize parse_size(char c) { switch (c) { case 'X': case 'x': return LocatedSize::Bit; case 'B': case 'b': return LocatedSize::Byte; case 'W': case 'w': return LocatedSize::Word; case 'D': case 'd': return LocatedSize::DWord; case 'L': case 'l': return LocatedSize::LWord; #if STRUCPP_HAS_EXCEPTIONS default: throw std::invalid_argument("Invalid size character"); #else default: iec_runtime_fault(IecFault::BadLocation, "Invalid size character"); #endif } } /** * Get the area character for a LocatedArea enum. * @param area The LocatedArea value * @return The corresponding character ('I', 'Q', or 'M') */ constexpr char area_to_char(LocatedArea area) noexcept { switch (area) { case LocatedArea::Input: return 'I'; case LocatedArea::Output: return 'Q'; case LocatedArea::Memory: return 'M'; default: return '?'; } } /** * Get the size character for a LocatedSize enum. * @param size The LocatedSize value * @return The corresponding character ('X', 'B', 'W', 'D', or 'L') */ constexpr char size_to_char(LocatedSize size) noexcept { switch (size) { case LocatedSize::Bit: return 'X'; case LocatedSize::Byte: return 'B'; case LocatedSize::Word: return 'W'; case LocatedSize::DWord: return 'D'; case LocatedSize::LWord: return 'L'; default: return '?'; } } // ============================================================================= // Located Variable Table Marker // ============================================================================= /** * End marker for the located variable descriptor array. * The compiler generates this as the last entry in the array. * Runtime scans the array until it finds this marker (pointer == nullptr). */ constexpr LocatedVar LOCATED_VAR_END = { LocatedArea::Input, // Doesn't matter LocatedSize::Bit, // Doesn't matter 0, // byte_index 0, // bit_index {0, 0, 0}, // reserved nullptr // End marker }; } // namespace strucpp