docs: chapters 14–19 — GEM 300 standards (Part 2 complete)
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>
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user