e84: per-port FSM via E84PortStore
E84 (Parallel I/O) is fundamentally per-load-port: each port has its own ten-wire handshake with the AMHS. Earlier revisions modeled it as a single equipment-wide FSM; this commit refactors to a per-port store, so multi-LP tools can run independent handshakes in parallel. Public API change in EquipmentDataModel: E84StateMachine e84; -> removed E84PortStore e84_ports; // create(port_id), get(port_id), ... Convenience pass-throughs: E84PortStore::on_signal_change auto-creates the port on first use (ergonomic for demos); applications should call create() explicitly with their full port set. The two existing callsites (test_gem300_scenario, test_e87_wire_scenarios) are updated. The multi-LP test now demonstrates the actual win: interleaved LP1 load + LP2 unload handshakes that reach their respective Ready states without sequencing, and an ES on LP1 that does NOT affect LP2 — exactly the failure mode the previous design couldn't catch. Five new dedicated tests in test_e84_ports.cpp for the store itself. COMPLIANCE.md §4i updated: row now reflects per-port design. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
// E84PortStore: per-port E84 FSMs.
|
||||
//
|
||||
// Each load port owns its own E84 handshake state. These tests
|
||||
// confirm the store keys correctly, that per-port FSMs are
|
||||
// independent, and that the change handler reports the right
|
||||
// port_id alongside the from/to state.
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "secsgem/gem/store/e84_ports.hpp"
|
||||
|
||||
using namespace secsgem::gem;
|
||||
|
||||
TEST_CASE("E84PortStore: create + get + has + size") {
|
||||
E84PortStore s;
|
||||
CHECK(s.size() == 0);
|
||||
REQUIRE(s.create(1));
|
||||
REQUIRE(s.create(2));
|
||||
CHECK(s.size() == 2);
|
||||
CHECK(s.has(1));
|
||||
CHECK(s.has(2));
|
||||
CHECK_FALSE(s.has(3));
|
||||
CHECK(s.get(1) != nullptr);
|
||||
CHECK(s.get(99) == nullptr);
|
||||
CHECK_FALSE(s.create(1)); // duplicate
|
||||
}
|
||||
|
||||
TEST_CASE("E84PortStore: implicit-create on first signal-change") {
|
||||
// Convenience pass-through: on_signal_change auto-creates the port
|
||||
// if it doesn't exist yet.
|
||||
E84PortStore s;
|
||||
CHECK(s.on_signal_change(7, E84Signal::CS_0, true));
|
||||
CHECK(s.has(7));
|
||||
CHECK(s.get(7)->signal(E84Signal::CS_0));
|
||||
}
|
||||
|
||||
TEST_CASE("E84PortStore: per-port FSMs run independently") {
|
||||
E84PortStore s;
|
||||
REQUIRE(s.create(1));
|
||||
REQUIRE(s.create(2));
|
||||
|
||||
// LP1 walks all the way to Complete; LP2 stays idle.
|
||||
s.on_signal_change(1, E84Signal::CS_0, true);
|
||||
s.on_signal_change(1, E84Signal::VALID, true);
|
||||
s.on_signal_change(1, E84Signal::L_REQ, true);
|
||||
s.on_signal_change(1, E84Signal::BUSY, true);
|
||||
s.on_signal_change(1, E84Signal::BUSY, false);
|
||||
s.on_signal_change(1, E84Signal::COMPT, true);
|
||||
CHECK(s.get(1)->state() == E84State::Complete);
|
||||
CHECK(s.get(2)->state() == E84State::Idle);
|
||||
|
||||
// Reset LP1; LP2 is unaffected.
|
||||
s.reset(1);
|
||||
CHECK(s.get(1)->state() == E84State::Idle);
|
||||
CHECK(s.get(2)->state() == E84State::Idle);
|
||||
}
|
||||
|
||||
TEST_CASE("E84PortStore: change handler reports port_id") {
|
||||
E84PortStore s;
|
||||
std::vector<std::pair<uint8_t, E84State>> seen;
|
||||
s.set_state_change_handler(
|
||||
[&](uint8_t pid, E84State, E84State to, E84Signal) {
|
||||
seen.emplace_back(pid, to);
|
||||
});
|
||||
REQUIRE(s.create(1));
|
||||
REQUIRE(s.create(2));
|
||||
s.on_signal_change(1, E84Signal::CS_0, true);
|
||||
s.on_signal_change(2, E84Signal::CS_0, true);
|
||||
s.on_signal_change(2, E84Signal::VALID, true);
|
||||
s.on_signal_change(2, E84Signal::U_REQ, true);
|
||||
|
||||
// Should have seen events tagged with the right port_id.
|
||||
bool saw_lp1_carrier_present = false;
|
||||
bool saw_lp2_unload_ready = false;
|
||||
for (auto& [pid, to] : seen) {
|
||||
if (pid == 1 && to == E84State::CarrierPresent) saw_lp1_carrier_present = true;
|
||||
if (pid == 2 && to == E84State::UnloadReady) saw_lp2_unload_ready = true;
|
||||
}
|
||||
CHECK(saw_lp1_carrier_present);
|
||||
CHECK(saw_lp2_unload_ready);
|
||||
}
|
||||
|
||||
TEST_CASE("E84PortStore: ids() returns the registered port set") {
|
||||
E84PortStore s;
|
||||
s.create(3);
|
||||
s.create(1);
|
||||
s.create(2);
|
||||
auto ids = s.ids();
|
||||
// std::map iteration is ordered by key, so the returned vector is
|
||||
// sorted.
|
||||
REQUIRE(ids.size() == 3);
|
||||
CHECK(ids[0] == 1);
|
||||
CHECK(ids[1] == 2);
|
||||
CHECK(ids[2] == 3);
|
||||
}
|
||||
Reference in New Issue
Block a user