Skip to content

Commit db9f8df

Browse files
Add generic event rendezvous function block (GEN_E_REND)
This function block provides event synchronization for a configurable number of inputs. The count of event inputs (EI) is determined dynamically from the FB instance name (e.g., GEN_E_REND_3). An output event (EO) is fired once all EIs have been received, and a reset input (R) clears the internal state. eclipse-4diac/4diac-ide#2087 Add tests for GEN_E_REND function blocks
1 parent b096b37 commit db9f8df

8 files changed

Lines changed: 480 additions & 0 deletions

File tree

stdfblib/events/include/forte/iec61499/events/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ target_sources(forte-events PUBLIC
4949
GEN_E_DEMUX_fbt.h
5050
GEN_E_MUX_fbt.h
5151
GEN_E_MERGE_fbt.h
52+
GEN_E_REND_fbt.h
5253
GEN_E_SPLIT_fbt.h
5354
timedfb.h
5455
)
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 HR Agartechnik GmbH
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Franz Höpfinger
12+
* - implement Generic GEN_E_REND_fbt
13+
*******************************************************************************/
14+
15+
// Some portions generated by Gemini CLI
16+
17+
#pragma once
18+
19+
#include <vector>
20+
#include "forte/genfb.h"
21+
#include "forte/stringid.h"
22+
23+
namespace forte::iec61499::events {
24+
/**
25+
* @brief A generic function block for event rendezvous (synchronization).
26+
*
27+
* The number of event inputs is determined by the instance name, e.g., an instance
28+
* named "GEN_E_REND_3" will have 3 synchronization inputs (EI1, EI2, EI3) and one Reset input (R).
29+
* The output EO is fired only after all EI inputs have been received.
30+
*/
31+
class GEN_E_REND final : public CGenFunctionBlock<CFunctionBlock> {
32+
DECLARE_GENERIC_FIRMWARE_FB(GEN_E_REND)
33+
34+
private:
35+
/**
36+
* @brief Executes when an event is received.
37+
*
38+
* @param paEIID The ID of the input event that triggered the execution.
39+
* @param paECET The event chain execution thread.
40+
*/
41+
void executeEvent(TEventID paEIID, CEventChainExecutionThread *const paECET) override;
42+
43+
/** These functions are not needed as this FB has no data inputs/outputs. */
44+
void readInputData(TEventID paEIID) override;
45+
void writeOutputData(TEventID paEIID) override;
46+
47+
/**
48+
* @brief Creates the interface specification based on the configuration string.
49+
*
50+
* It parses the number from the end of the config string (e.g., "3" from "GEN_E_REND_3")
51+
* to determine the number of event inputs.
52+
* @param paConfigString The configuration string (typically the FB instance name).
53+
* @param paInterfaceSpec The interface specification structure to be filled.
54+
* @return true on success, false otherwise.
55+
*/
56+
bool createInterfaceSpec(const char *paConfigString, SFBInterfaceSpec &paInterfaceSpec) override;
57+
58+
/** A vector to hold the dynamically generated StringIds for the event input names. */
59+
std::vector<StringId> mEventInputNames;
60+
61+
/**
62+
* @brief Tracks which events have been received.
63+
* mFlags[i] corresponds to EI(i+1).
64+
*/
65+
std::vector<bool> mFlags;
66+
67+
/**
68+
* @brief Number of synchronization inputs (excluding R).
69+
* Set during configuration.
70+
*/
71+
size_t mNumEIs;
72+
73+
public:
74+
GEN_E_REND(StringId paInstanceNameId, CFBContainer &paContainer);
75+
76+
CEventConnection conn_EO;
77+
78+
CIEC_ANY *getDI(size_t) override;
79+
CIEC_ANY *getDO(size_t) override;
80+
CEventConnection *getEOConUnchecked(TPortId) override;
81+
CDataConnection **getDIConUnchecked(TPortId) override;
82+
CDataConnection *getDOConUnchecked(TPortId) override;
83+
};
84+
} // namespace forte::iec61499::events

stdfblib/events/src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ target_sources(forte-events PRIVATE
4747
GEN_E_DEMUX_fbt.cpp
4848
GEN_E_MUX_fbt.cpp
4949
GEN_E_MERGE_fbt.cpp
50+
GEN_E_REND_fbt.cpp
5051
GEN_E_SPLIT_fbt.cpp
5152
timedfb.cpp
5253
)
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 HR Agartechnik GmbH
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Franz Höpfinger
12+
* - implement Generic GEN_E_REND_fbt
13+
*******************************************************************************/
14+
15+
// Some portions generated by Gemini CLI
16+
17+
#include "forte/iec61499/events/GEN_E_REND_fbt.h"
18+
#include "forte/util/string_utils.h"
19+
20+
using namespace forte::literals;
21+
22+
namespace forte::iec61499::events {
23+
// Registers the generic FB with the name "GEN_E_REND" in the FORTE type library.
24+
DEFINE_GENERIC_FIRMWARE_FB(GEN_E_REND, "iec61499::events::GEN_E_REND"_STRID)
25+
26+
namespace {
27+
// Defines the constant name for the single event output.
28+
const auto cEventOutputNames = std::array{"EO"_STRID};
29+
} // namespace
30+
31+
GEN_E_REND::GEN_E_REND(const StringId paInstanceNameId, CFBContainer &paContainer) :
32+
CGenFunctionBlock<CFunctionBlock>(paContainer, paInstanceNameId),
33+
mNumEIs(0),
34+
conn_EO(*this, 0) {
35+
}
36+
37+
void GEN_E_REND::executeEvent(const TEventID paEIID, CEventChainExecutionThread *const paECET) {
38+
if (paEIID == mNumEIs) {
39+
// This is the R (Reset) event
40+
std::fill(mFlags.begin(), mFlags.end(), false);
41+
} else if (paEIID < mNumEIs) {
42+
// This is one of the EI inputs
43+
mFlags[paEIID] = true;
44+
45+
// Check if all inputs have been received
46+
bool allReceived = true;
47+
for (bool received : mFlags) {
48+
if (!received) {
49+
allReceived = false;
50+
break;
51+
}
52+
}
53+
54+
if (allReceived) {
55+
// Send EO
56+
sendOutputEvent(0, paECET);
57+
// Reset flags after rendezvous
58+
std::fill(mFlags.begin(), mFlags.end(), false);
59+
}
60+
}
61+
}
62+
63+
void GEN_E_REND::readInputData(TEventID) {
64+
// Nothing to do, as there are no data inputs.
65+
}
66+
67+
void GEN_E_REND::writeOutputData(TEventID) {
68+
// Nothing to do, as there are no data outputs.
69+
}
70+
71+
bool GEN_E_REND::createInterfaceSpec(const char *paConfigString, SFBInterfaceSpec &paInterfaceSpec) {
72+
// Find the last underscore in the name, e.g., "E_REND_3".
73+
const char *acPos = strrchr(paConfigString, '_');
74+
75+
if (nullptr != acPos) {
76+
++acPos; // Move pointer to the character after the underscore.
77+
78+
// Convert the numeric part to an integer. This is the number of EI inputs.
79+
mNumEIs = static_cast<size_t>(util::strtoul(acPos, nullptr, 10));
80+
81+
// Check if the number of inputs is within the valid range.
82+
// E_REND needs at least 2 inputs. scmMaxInterfaceEvents is the hard limit.
83+
// We add 1 for R, so mNumEIs + 1 must be <= scmMaxInterfaceEvents.
84+
if (mNumEIs >= 2 && (mNumEIs + 1) < scmMaxInterfaceEvents) {
85+
86+
// Resize flags vector to match number of inputs
87+
mFlags.resize(mNumEIs, false);
88+
89+
mEventInputNames.clear();
90+
// Generate names "EI1", "EI2", etc.
91+
generateGenericInterfacePointNameArray("EI", mEventInputNames, mNumEIs);
92+
93+
// Add the Reset input "R"
94+
mEventInputNames.push_back("R"_STRID);
95+
96+
// Assign the generated input names and the constant output name to the interface.
97+
paInterfaceSpec.mEINames = mEventInputNames;
98+
paInterfaceSpec.mEONames = cEventOutputNames;
99+
100+
return true; // Success
101+
} else {
102+
// Log errors if the number is out of bounds.
103+
if ((mNumEIs + 1) >= scmMaxInterfaceEvents) {
104+
DEVLOG_ERROR("Cannot configure FB-Instance GEN_E_REND_%d. Number of event inputs exceeds maximum of %d.\n",
105+
mNumEIs, CFunctionBlock::scmMaxInterfaceEvents - 1);
106+
} else {
107+
DEVLOG_ERROR(
108+
"Cannot configure FB-Instance GEN_E_REND_%d. Number of event inputs smaller than minimum of 2.\n",
109+
mNumEIs);
110+
}
111+
}
112+
}
113+
return false; // Failure
114+
}
115+
116+
// The following functions return nullptr because this FB does not handle data.
117+
CIEC_ANY *GEN_E_REND::getDI(size_t) {
118+
return nullptr;
119+
}
120+
121+
CIEC_ANY *GEN_E_REND::getDO(size_t) {
122+
return nullptr;
123+
}
124+
125+
CEventConnection *GEN_E_REND::getEOConUnchecked(const TPortId paIndex) {
126+
switch (paIndex) {
127+
case 0: return &conn_EO;
128+
}
129+
return nullptr;
130+
}
131+
132+
CDataConnection **GEN_E_REND::getDIConUnchecked(TPortId) {
133+
return nullptr;
134+
}
135+
136+
CDataConnection *GEN_E_REND::getDOConUnchecked(TPortId) {
137+
return nullptr;
138+
}
139+
} // namespace forte::iec61499::events

tests/stdfblib/events/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,8 @@ forte_test_add_sourcefile_cpp(E_CTD_tester.cpp)
2929
forte_test_add_sourcefile_cpp(E_CTU_tester.cpp)
3030
forte_test_add_sourcefile_cpp(E_CTUD_tester.cpp)
3131
forte_test_add_sourcefile_cpp(E_REND_tester.cpp)
32+
forte_test_add_sourcefile_cpp(GEN_E_REND_2_tester.cpp)
33+
forte_test_add_sourcefile_cpp(GEN_E_REND_3_tester.cpp)
34+
forte_test_add_sourcefile_cpp(GEN_E_REND_4_tester.cpp)
3235

3336

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 HR Agartechnik GmbH
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Franz Höpfinger
12+
* - initial API and implementation and/or initial documentation
13+
*******************************************************************************/
14+
15+
#include "../../core/fbtests/fbtestfixture.h"
16+
17+
using namespace forte::literals;
18+
19+
namespace forte::iec61499::events::test {
20+
struct GEN_E_REND_2_TestFixture : public forte::test::CFBTestFixtureBase {
21+
GEN_E_REND_2_TestFixture() : CFBTestFixtureBase("iec61499::events::E_REND_2"_STRID) {
22+
setup();
23+
}
24+
25+
static constexpr TEventID EI1 = 0;
26+
static constexpr TEventID EI2 = 1;
27+
static constexpr TEventID R = 2;
28+
static constexpr TEventID EO = 0;
29+
};
30+
31+
BOOST_FIXTURE_TEST_SUITE(GenERend2Tests, GEN_E_REND_2_TestFixture)
32+
33+
BOOST_AUTO_TEST_CASE(Rendezvous2) {
34+
triggerEvent(EI1);
35+
BOOST_CHECK(eventChainEmpty());
36+
triggerEvent(EI2);
37+
BOOST_CHECK(checkForSingleOutputEventOccurence(EO));
38+
}
39+
40+
BOOST_AUTO_TEST_CASE(Rendezvous2_OutOfOrder) {
41+
triggerEvent(EI2);
42+
BOOST_CHECK(eventChainEmpty());
43+
triggerEvent(EI1);
44+
BOOST_CHECK(checkForSingleOutputEventOccurence(EO));
45+
}
46+
47+
BOOST_AUTO_TEST_CASE(Reset) {
48+
triggerEvent(EI1);
49+
BOOST_CHECK(eventChainEmpty());
50+
51+
triggerEvent(R);
52+
BOOST_CHECK(eventChainEmpty());
53+
54+
// After reset EI1 alone must not fire EO
55+
triggerEvent(EI1);
56+
BOOST_CHECK(eventChainEmpty());
57+
58+
// Now both together
59+
triggerEvent(EI2);
60+
BOOST_CHECK(checkForSingleOutputEventOccurence(EO));
61+
}
62+
63+
BOOST_AUTO_TEST_CASE(MultipleRendezvous) {
64+
for (int i = 0; i < 5; i++) {
65+
triggerEvent(EI1);
66+
BOOST_CHECK(eventChainEmpty());
67+
triggerEvent(EI2);
68+
BOOST_CHECK(checkForSingleOutputEventOccurence(EO));
69+
}
70+
}
71+
72+
BOOST_AUTO_TEST_SUITE_END()
73+
} // namespace forte::iec61499::events::test

0 commit comments

Comments
 (0)