Six more chapters finishing Part 2. Together with chapters 10–13 they document every SEMI standard this codebase implements. 14 — E40 + E94: process jobs (8-state lifecycle, S16F11/F5/F7/F9 on the wire) and control jobs (CJ wraps PJs with batch policy, S14F9/S16F27 messages). Worked cascade showing how CJSTART propagates through the PJ FSM and triggers S6F11 CEIDs at each transition. 15 — E87 carriers: three orthogonal sub-machines (CarrierID, SlotMap, CarrierAccess) per carrier and three more (Transfer, Reservation, Association) per load port. S3F17 CarrierAction strings + CAACK codes, S3F19 SlotMap verify, the 5-state slot encoding, multi-port concurrency. 16 — E90 + E157: substrate tracking via three orthogonal axes (STS / SPS / SubstrateIDStatus) and module process tracking (NotExecuting / GeneralExecuting / StepExecuting / StepCompleted). End-to-end PVD example showing E40 + E157 + E90 transitions cascading into CEIDs. 17 — E116 + E120 + E39: equipment performance time-buckets across six states, common equipment model object hierarchy, S14F1/F3 GetAttr/SetAttr as the uniform wire access for any object type across multiple standards. 18 — E84 parallel I/O: ten signal lines, the 9-state handshake FSM, the three TA1/TA2/TA3 timing-critical timers, why a physical handshake gets modeled in software (testability, timer enforcement, CEID emission, multi-port concurrency), the pure-FSM + asio-adapter split. 19 — E42 + E148 + S5F9–F18: formatted recipes (S7F23/F25 typed PPBODY), time synchronization with 16-char + 14-char accepted on set, exception recovery as a persistent multi-step host-supervised FSM (Posted → Recovering → Cleared with abort/retry). Revisits the auto-S9 family and contrasts S9 (transport) vs S5F9 (application). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
10 KiB
18 — E84: Parallel I/O handoff
← 17 E116 + E120 + E39 — Performance, CEM, objects | Back to index | Next: 19 E42 + E148 + S9 — Misc →
E84 is unusual in the GEM 300 suite: it's the only standard that's not SECS at all. Not a wire format, not a message catalog — ten physical wires between the AMHS robot and the load port, asserted at CMOS voltage levels with strict timing.
Why? Because dropping a $20 000 FOUP is catastrophic, and you can't afford to coordinate the kinematics over TCP — too much latency, too many failure modes. The handshake has to be deterministic in hardware.
This chapter:
- The ten signal lines and what each one means.
- The handshake state machine.
- The three timing-critical timers (TA1, TA2, TA3).
- How the codebase models a physical-layer handshake as software (and why it does).
The ten signals
Each signal is one single-bit boolean asserted on a physical wire. Four go from the equipment to the AMHS; six go from the AMHS to the equipment:
// include/secsgem/gem/e84_state.hpp:28
enum class E84Signal : uint8_t {
CS_0 = 0, // AMHS -> equip: carrier stage select 0
CS_1 = 1, // AMHS -> equip: carrier stage select 1
VALID = 2, // AMHS -> equip: handshake start
TR_REQ = 3, // AMHS -> equip: transfer request
BUSY = 4, // AMHS -> equip: transfer in progress
COMPT = 5, // AMHS -> equip: transfer complete
L_REQ = 6, // equip -> AMHS: load request (port ready to receive)
U_REQ = 7, // equip -> AMHS: unload request (port ready to release)
READY = 8, // equip -> AMHS: ready
ES = 9, // either: emergency stop
};
- CS_0 + CS_1: two bits encoding which port the AMHS is addressing (CS = Carrier Select). Tools with up to 4 ports can be indexed by two bits.
- VALID: the AMHS asserts this when CS bits are stable — "you can read me now."
- TR_REQ: AMHS is requesting a transfer.
- BUSY: AMHS is actively moving the carrier. Goes high when the robot starts lowering / lifting.
- COMPT: AMHS has finished the kinematic operation.
- L_REQ: equipment is ready to receive a carrier.
- U_REQ: equipment is ready to release a carrier.
- READY: equipment kinematic interlocks are satisfied.
- ES: Emergency Stop. Either side can assert. If asserted, every state machine on both sides goes to a safe state.
Defined in
include/secsgem/gem/e84_state.hpp.
Stored in E84SignalSet as a 10-bit bitmap.
The handshake state machine
// include/secsgem/gem/e84_state.hpp:63
enum class E84State : uint8_t {
Idle = 0, // no signals
CarrierPresent = 1, // CS asserted; no VALID yet
ValidAsserted = 2, // CS + VALID; equipment hasn't ack'd
LoadReady = 3, // VALID + L_REQ; port ready to receive
UnloadReady = 4, // VALID + U_REQ; port ready to release
Transferring = 5, // BUSY asserted; transfer happening
Complete = 6, // COMPT asserted; AMHS done
EmergencyStop = 7, // ES asserted
HandoffFault = 8, // a timer expired
};
The happy path for an inbound load:
Idle ─CS asserted─► CarrierPresent ─VALID asserted─► ValidAsserted
│
(equipment
decides: yes,
I can take it)
│
L_REQ asserted
▼
LoadReady
│
TR_REQ asserted
BUSY asserted
▼
Transferring
│
(robot lowers
carrier onto
port; takes a
few seconds)
│
BUSY de-asserted
COMPT asserted
▼
Complete
│
(signals all
drop back; CS
de-asserted)
▼
Idle
An outbound unload follows the same pattern but uses U_REQ
instead of L_REQ, and ends with the carrier moving off the
port.
The FSM is event-driven: every transition is triggered by one
signal change, not by a clock tick. E84StateMachine::on_signal_change()
re-evaluates the bitmap and emits a state transition if one is due.
Tests:
tests/test_e84.cpp (6 cases — every
happy-path transition);
tests/test_e84_ports.cpp (5 cases
— per-port store).
The three TA timers
These are why E84 matters more than "the AMHS lifts the carrier." Without timer enforcement, a stuck signal could leave the mechanical handoff frozen mid-motion — the robot holding the carrier, neither side noticing the other has gone quiet.
struct E84Timeouts {
std::chrono::milliseconds ta1{0};
std::chrono::milliseconds ta2{0};
std::chrono::milliseconds ta3{0};
};
(Spec defaults are 2 s / 2 s / 60 s; tool builders tune per port.)
TA1
Armed: on entering ValidAsserted (AMHS asserted VALID).
Cancelled: on entering LoadReady or UnloadReady (equipment
asserted L_REQ or U_REQ).
Bounds: how long may the equipment take to respond to VALID? If TA1 expires the AMHS doesn't know whether the equipment is busy, broken, or asleep — fault.
TA2
Armed: on entering LoadReady or UnloadReady.
Cancelled: on entering Transferring (AMHS asserted BUSY).
Bounds: how long may the AMHS take to start moving once the port is ready? Prevents the equipment holding its port idle forever waiting for an AMHS that's stuck.
TA3
Armed: on entering Transferring.
Cancelled: on entering Complete.
Bounds: how long may the actual transfer take? If the robot freezes mid-motion, TA3 catches it.
What happens on timeout
The FSM transitions to HandoffFault with the relevant
E84Fault reason:
enum class E84Fault : uint8_t {
None = 0,
TA1Expired = 1,
TA2Expired = 2,
TA3Expired = 3,
};
The equipment fires an alarm (configurable ALID per port), the EAP brings up the operator panel, and someone has to physically inspect.
Tested by
tests/test_e84_timers.cpp (12
cases — every timer armed/cancelled/expired path).
Why model a physical handshake in software
The wires are real. The signals are CMOS-level on opto-isolated 24 V lines. But the software needs to:
- Test the protocol logic without a real load port. Spinning up actual hardware for unit tests is impossible.
- Drive the timer enforcement. Even if the wires are physical, the timers TA1/TA2/TA3 are wall-clock and need a software clock to track.
- Emit CEIDs alongside transitions. When the port goes
Transferring, the equipment also wants to fireCarrierInover SECS-II — the same way E40/E87/E90 transitions do. - Model multi-port concurrency. A 4-port tool has four independent E84 FSMs running in parallel; they have to be modeled distinctly.
The codebase ships two implementations:
Pure FSM (testable)
E84StateMachine is the
IO-free FSM. Inputs: signal change events. Outputs: state
transitions + timer arm/cancel requests. No wall clock.
This is what tests drive — they feed signal events in, expect transitions out, and synthetically expire timers.
asio adapter (production)
E84AsioTimers
wraps the FSM with real asio::steady_timers. When the FSM
requests arm(TA1, 2s), the adapter schedules a wall-clock timer;
when 2 s pass and nothing's cancelled it, the adapter feeds the
expiry event back into the FSM.
This is what runs in production — connected to a GPIO driver that pulses the actual wires.
Tested by
tests/test_e84_asio_timers.cpp
(4 cases — every timer fires on real wall clock).
How E84 connects to the rest of GEM
E84 itself only manages the physical handoff. Once a carrier is docked, SECS messages take over:
- E84 reaches
Complete→ equipment fires CEIDCarrierArrived(configured indata/equipment.yaml). - CEID
CarrierArrivedfires → S6F11 (host informed). - Host sees S6F11 → looks up carrier ID → optionally sends S3F19 SlotMapVerify (E87).
- Host sends S3F17
ProceedWithCarrier→ CarrierAccess goes InAccess (E87). - Processing happens (E40 + E90 + E157).
- All wafers done → equipment fires CEID
CarrierComplete. - Host sends S3F17
CarrierOut. - AMHS comes back; E84 runs in reverse to unload.
E84 is the bookend at both ends of the carrier flow. Without it, the carrier never docks and never undocks; without the SECS messages after step 1, nothing knows the carrier arrived.
Where to go next
You now know every state-machine-bearing standard in the GEM 300 suite. One more chapter wraps up the remaining narrow ones — formatted process programs, distributed time sync, and the exception recovery streams.