40df3067a4
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>
281 lines
10 KiB
Markdown
281 lines
10 KiB
Markdown
# 18 — E84: Parallel I/O handoff
|
|
|
|
← [17 E116 + E120 + E39 — Performance, CEM, objects](17_e116_e120_e39_objects.md) | [Back to index](00_index.md) | Next: [19 E42 + E148 + S9 — Misc](19_e42_e148_s9_misc.md) →
|
|
|
|
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:
|
|
|
|
```cpp
|
|
// 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`](../include/secsgem/gem/e84_state.hpp).
|
|
Stored in `E84SignalSet` as a 10-bit bitmap.
|
|
|
|
---
|
|
|
|
## The handshake state machine
|
|
|
|
```cpp
|
|
// 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`](../tests/test_e84.cpp) (6 cases — every
|
|
happy-path transition);
|
|
[`tests/test_e84_ports.cpp`](../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.
|
|
|
|
```cpp
|
|
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:
|
|
|
|
```cpp
|
|
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`](../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:
|
|
|
|
1. **Test the protocol logic without a real load port.** Spinning
|
|
up actual hardware for unit tests is impossible.
|
|
2. **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.
|
|
3. **Emit CEIDs alongside transitions.** When the port goes
|
|
`Transferring`, the equipment also wants to fire `CarrierIn`
|
|
over SECS-II — the same way E40/E87/E90 transitions do.
|
|
4. **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`](../include/secsgem/gem/e84_state.hpp) 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`](../include/secsgem/gem/e84_asio_timers.hpp)
|
|
wraps the FSM with real `asio::steady_timer`s. 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`](../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:
|
|
|
|
1. E84 reaches `Complete` → equipment fires CEID `CarrierArrived`
|
|
(configured in `data/equipment.yaml`).
|
|
2. CEID `CarrierArrived` fires → S6F11 (host informed).
|
|
3. Host sees S6F11 → looks up carrier ID → optionally sends
|
|
S3F19 SlotMapVerify (E87).
|
|
4. Host sends S3F17 `ProceedWithCarrier` → CarrierAccess goes
|
|
InAccess (E87).
|
|
5. Processing happens (E40 + E90 + E157).
|
|
6. All wafers done → equipment fires CEID `CarrierComplete`.
|
|
7. Host sends S3F17 `CarrierOut`.
|
|
8. 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.
|
|
|
|
Next: [→ 19 E42 + E148 + S9 — Misc](19_e42_e148_s9_misc.md)
|