← 전체 글
SECS/GEM/약 22분 읽기/ 조회

Host가 한 시간 끊겼을 때 SECS/GEM Spooling이 실제로 지켜주는 것

SEMI E30 spooling은 host가 S2F43으로 허용한 stream만 대상이다. SPOOL LOAD/UNLOAD 상태, S2F44 RSPACK과 STRACK, S6F24 RSDA, 그리고 S2F43 bytes를 실제 socket에 흘려본 capture.

SECS/GEMMESSCADA문제 해결체크리스트

토요일 밤 MES 재기동이 예정보다 길어졌다. Host가 없는 시간 약 40분. 장비는 계속 돌아서 lot 세 개가 끝났고, 현장에서는 아무도 이상을 못 느꼈다. 월요일에 MES를 보니 lot complete 기록은 다 있었다. 다만 세 건의 timestamp가 전부 09:14, 통신이 붙은 그 시각이었다. Protocol상 실패한 건 하나도 없다. 장비는 spool했고, 재연결했고, queue를 비웠고, host는 전부 ack했다. 그런데 이력은 못 쓴다.

Spooling을 켜기만 하고 사양을 정하지 않으면 대체로 이렇게 끝난다.

E30 spooling 상태: SPOOL INACTIVE와 SPOOL ACTIVE, 그리고 SPOOL ACTIVE 안에서 동시에 도는 SPOOL LOAD(SPOOL NOT FULL / SPOOL FULL)와 SPOOL UNLOAD SPOOL ACTIVE SPOOL LOAD SPOOL INACTIVE SPOOL NOT FULL SPOOL UNLOAD SPOOL FULL 통신 끊김 같은 spool OverWriteSpool S6F23 · RSDC = 0

Spooling 대상은 host가 정한다. 이 단계를 대부분 건너뛴다

GEM(SEMI E30)에서 장비가 알아서 spool 대상을 고르지 않는다. Host가 S2F43 Reset Spooling Streams and Functions로 spool 가능한 stream/function을 선언한다. 목록을 비운 채로 S2F43을 보내면 spooling을 전부 끈 것이다.

답은 S2F44로 온다. 첫 item이 RSPACK이다. 0 = 수락, 1 = 거부, 나머지 값은 reserved다. 여기서 멈추면 안 된다. S2F44는 거부된 항목 목록을 같이 싣고, 항목마다 STRIDSTRACK, 그리고 문제가 된 FCNID 목록이 붙는다. STRACK 값은 1 = 이 stream은 spool 대상이 될 수 없음, 2 = 모르는 stream, 3 = 모르는 function, 4 = secondary function이다.

STRACK 4가 제일 흔하고 제일 시시하다. Host driver가 목록에 S6F11 대신 S6F12를 넣었다는 뜻이고, secondary는 원래 spool 대상이 아니다. Code에서 5분이면 고친다. 그런데 RSPACK byte 하나만 읽고 log에 "spool setup failed"라고 남기는 driver를 쓰면, 그 5분을 며칠 뒤에 찾는다.

대상은 장비가 발신하는 primary message뿐이다. S6F11 event report, S5F1 alarm, S6F1 trace 정도. Host가 보내는 message는 대상이 아니고 secondary reply도 아니다. 그래서 "무엇이 spool되나"는 실제로는 "어떤 SxFy를 허용했고, 그 위에 어떤 CEID와 report가 물려 있나"라는 질문이다.

S6F11과 S5F1은 켠다. S6F1 trace는 한 번 더 생각하고 켠다. 1초 주기 trace면 4시간 outage에 14,400건이 쌓이고, 오래된 것부터 버리는 설정이라면 그 밑에 lot event가 깔려서 사라진다.

CEID 고르는 기준은 하나다. 없으면 genealogy가 깨지는 것만 넣는다. Carrier in/out, lot start/complete, process start/complete/abort, recipe selected와 verification result, 그리고 alarm set과 alarm clear는 반드시 쌍으로. Chamber 단위 diagnostic event는 보통 뺀다.

재연결 순간에 보이는 그림은 state model이 정한다

E30은 spooling을 state machine으로 정의한다. On/off 두 칸이 아니다.

  • SPOOL INACTIVE — 통신 정상. Message는 spool을 거치지 않고 바로 나간다.
  • SPOOL ACTIVE / SPOOL LOAD — spool에 message를 넣는 쪽. 이 안이 다시 SPOOL NOT FULL(spool 끝에 append)과 SPOOL FULL(spool이 용량에 도달)로 갈린다.
  • SPOOL ACTIVE / SPOOL UNLOAD — spool에 쌓인 것을 host로 내보내거나 버리는 쪽. 둘 중 무엇을 할지는 장비가 아니라 host가 S6F23의 RSDC byte로 정한다.

LOAD와 UNLOAD는 순서대로 오는 단계가 아니다. 같은 spool을 공유하면서 독립적으로 도는 두 축이다. 그래서 drain 도중에 생긴 event도 자리를 잃지 않는다. UNLOAD가 앞을 비우는 동안 LOAD가 뒤에 계속 붙인다.

Vendor HMI는 drain 구간을 "Spool Output", "Transmitting" 같은 자기 말로 표시한다. 화면 글자로 대화하면 서로 다른 것을 가리키게 되니, 장비 vendor와는 E30 이름으로 이야기하는 편이 낫다.

한계에 도달했을 때의 처리는 E30 equipment constant OverWriteSpool이 정한다. true면 가장 오래된 것을 버리고 자리를 만들고, false면 새로 생긴 message를 버리고 기존 것을 지킨다. 그런데 용량 자체, 즉 몇 건이 들어가는지는 E30에 host가 쓸 수 있는 constant로 정의돼 있지 않다. Vendor 설정이고 ECID가 아니라 service menu 항목인 장비도 많다. 통신 사양 협의 때 숫자를 문서로 받아 두는 편이 낫다. 나중에 link로 읽어낼 수 있다는 보장이 없다.

읽을 수 있는 쪽은 E30의 spool status variable이다. SpoolCountActual(지금 spool에 들어 있는 건수), SpoolCountTotal(spooling이 시작된 뒤 누적 건수), SpoolStartTime, SpoolFullTime. 첫 번째는 host가 주기적으로 봐야 한다. SpoolCountActual을 trend로 걸어두면 현장에서 전화가 오기 전에 장애를 안다.

하나 더, MaxSpoolTransmit은 S6F23 한 번에 장비가 내보낼 spool message 개수의 상한이다. 이걸 걸어두면 3,000건을 한 번에 쏟지 않고 host가 요청을 나눠 가며 drain 속도를 조절한다. 거의 아무도 설정하지 않아서, 아래에 적은 burst 실패가 계속 나온다.

OverWriteSpool은 어느 쪽이 기본으로 맞는다고 말할 수 없다. 2시간짜리 batch를 도는 장비인데 사내 change policy상 host 정지 창이 4시간이라면, spool 용량을 batch 하나 기준으로 잡고 OverWriteSpool을 true로 두는 순간 뒤쪽만 남고 lot start가 날아간다. Start 없는 complete는 MES 입장에서 둘 다 없는 것보다 나쁘다. 나는 OverWriteSpool을 false로 두고 overflow alarm을 띄우는 쪽을 택한다. 구멍이 하나 생기더라도 경계가 분명한 구멍이 낫다.

이 영역에서 제일 고약한 실패는 host 쪽에 있다. 재연결 후 장비는 SPOOL UNLOAD로 넘어갈 S6F23 Request Spooled Data를 기다린다. RSDC = 0이 전송, RSDC = 1폐기다. 일부 MES 복구 routine은 interface를 빨리 정상화하려고 정리 차원에서 purge를 보낸다. Byte 하나 틀린 S6F23이면 40분치 생산 이력이 사라지고, log에는 S6F24 RSDA = 0이 깔끔하게 남는다. 장비 설정을 만지기 전에 host가 무엇을 보내는지부터 확인한다.

RSDA도 값이 셋이다. 0 = OK, 1 = 거부(busy, 나중에 다시), 2 = 거부(spool된 data 없음). 이 둘을 구분 못 하는 host를 자주 본다. RSDA 1은 실패가 아니라 재시도 요청인데 한 번 보내고 포기하는 구현이 있고, RSDA 2는 오히려 답을 주는 값이다. 재연결 후 아무 것도 안 올라올 때 "장비가 spool을 안 했다"와 "host가 요청을 안 했다"를 갈라 준다. 빈 spool에 S6F23을 보내면 조용히 성공하지 않고 RSDA 2가 온다.

Drain 중에 새로 생기는 message는 live로 나가지 않고 spool 뒤에 붙는다. 그래야 순서가 끝까지 유지된다. Vendor의 배려가 아니라 표준 동작이다. 장비가 live와 spool을 섞어 보낸다면 규격에 안 맞는 것이고, host의 중복 판단 logic이 그 뒷감당을 해야 한다.

Report에 clock variable이 없으면 event 시간도 없다

토요일 밤 사례를 만든 원인이 이것이다.

S6F11에는 CEID와 S2F35로 연결된 report body가 들어간다. 그 report를 정의하고 link하고 enable하는 순서는 따로 정리해 두었다. Event 발생 시간은 그 자체로 들어가지 않는다. S2F33으로 정의한 report에 장비 clock SV를 넣지 않았다면, host가 볼 수 있는 시간은 도착 시간뿐이다. Spool drain 뒤의 도착 시간은 아무 의미가 없다.

그러니 spool 대상으로 삼을 report에는 clock SV를 전부 넣는다. Vendor마다 이름은 Clock, TimeStamp, EventTime 등으로 다르다. Format은 TimeFormat equipment constant가 고르고, ASCII 문자열 세 가지가 있다.

TimeFormat형식길이
0YYMMDDhhmmssA[12]
1YYYYMMDDhhmmssccA[16]
2YYYY-MM-DDThh:mm:ss.s + Z 또는 ±hh:mm가변

0은 쓰지 않는다. 세기도 없고 1/100초도 없다. 장비가 2를 지원하면 2로 간다. Offset을 싣는 유일한 형식이고, DST 전환이나 표준시가 다른 fab을 걸친 spool drain에서 시각을 두고 논쟁이 안 나는 값은 이것뿐이다. 지원 안 하면 1이다. Host 연결마다 S2F31 Date and Time Set Request로 동기화하고, 보정한 차이를 기록으로 남긴다. 하루 4초씩 밀리는 장비는 RTC battery가 끝나간다는 신호다.

발신원을 구분할 값도 같이 넣는다. Chamber 두 개가 local sequence number를 공유하는 구성이 전형적인 함정이다. 같은 초 안에 같은 CEID로 sequence 1047이 두 번 올라오면, host 중복 제거 logic이 멀쩡한 lot event 하나를 조용히 버린다.

시험은 얌전하게 하지 말고 선을 뽑는다

HSMS Separate.req로 정상 종료만 시켜보는 시험은 거의 아무것도 증명하지 못한다. 장비가 즉시 끊김을 알고 깔끔하게 SPOOL LOAD로 넘어가기 때문이다. 실제 장애는 지저분하다. 장비는 NOT SELECTED 상태이거나 reply를 기다리면서 E37 timer가 만료되기를 기다린다. 일반적인 default는 T3 reply 45초, T6 control transaction 5초, T7 NOT SELECTED 10초, T8 network intercharacter 5초다. 이 구간에서 발생한 event가 "아직 spool 안 됨"과 "보냈는데 reply 없음" 사이에 끼기 가장 쉽다.

시운전 때 실제로 도는 순서는 이렇다.

  1. 현재 spool 용량, OverWriteSpool, MaxSpoolTransmit, TimeFormat, S2F43 spoolable 목록을 기록한다. 화면 캡처까지 남긴다. 2년 뒤에 아무도 못 찾는 게 바로 이 값들이다.
  2. S2F31로 clock을 맞추고 보정량을 적어둔다.
  3. Lot 사이가 아니라 process 중간에 network를 뽑는다. 장비가 SPOOL ACTIVE로 가는지 확인한다.
  4. 대표 traffic을 만든다. Lot complete 하나, alarm set과 그 clear, recipe download와 verification.
  5. 장비 HMI에서 SpoolCountActual이 올라가는 걸 본다. 거기서 안 보이면 그것부터가 지적 사항이다.
  6. 재연결하고 복구 구간 전체를 wire에서 capture한다. Host 요약 log 말고, RSDC 값이 실제로 찍힌 S6F23이 필요하다.
  7. MES에서 복구된 기록의 원래 event time, 순서, lot/carrier/recipe context를 본다. "행이 들어왔는가"가 아니라 "품질 담당자가 이 행을 믿을 수 있는가"로 판정한다.
  8. Spool이 가득 찰 만큼 길게 끊고 한 번 더 한다. Overflow 동작과 alarm이 1번에서 설정한 대로인지 확인한다.

일정에 밀려 잘려나가는 게 항상 8번이다. 그리고 한 번도 실행된 적 없는 분기를 시험하는 유일한 항목도 8번이다.

S2F43을 실제 socket에 올려보면

이 글이 걸려 있는 두 message는 손으로 만들 수 있을 만큼 작다. E5에서 body 구조를 읽는 것보다 한 번 socket에 흘려보는 쪽이 남는 게 많다.

아래는 SECS/GEM 시뮬레이터의 HSMS listener(127.0.0.1:5501)를 host client로 밖에서 두드려 받은 실제 transcript다. TX가 host가 보낸 것, RX가 돌아온 것. SessionID는 전 구간 11이다.

TX  00 00 00 0A  00 0B 00 00 00 01  00 00 02 01     Select.req
RX  00 00 00 0A  00 0B 00 00 00 02  00 00 02 01     Select.rsp

응답의 header byte 3이 00, SelectStatus 0 수락이다. 아래는 전부 이 session 위의 data message다.

TX  00 00 00 0C  00 0B 82 2B 00 00 00 00 02 02
    01 00
Bytes항목
00 00 00 0CLength12 — header 10 byte + body 2 byte
00 0BSessionID11
82Header byte 2W-bit 0x80 set, Stream 2
2BHeader byte 3Function 43
00PType0, SECS-II
00SType0, data message
00 00 02 02SystemBytes514
01 00BodyL,0 — 빈 list

Body 2 byte로 그 장비의 모든 stream에 대해 spooling이 꺼진다. Host connect sequence에 있어야 하는데 대개 없는 message가 이것이고, "interface 초기화" 정리 script가 body는 안 읽힌 채 내보내는 것도 이것이다.

실제로 뭔가를 선언하는 쪽은 이렇게 생겼다.

TX  00 00 00 19  00 0B 82 2B 00 00 00 00 02 03
    01 01  01 02  A5 01 06  01 02  A5 01 0B  A5 01 0D

01 01은 항목 하나짜리 list, 01 02가 그 항목의 두 field를 연다. List의 길이 byte는 byte 수가 아니라 원소 수를 센다. A5는 format code 51 octal(U1)에 length byte 하나이므로 A5 01 06은 STRID 6이다. 이어서 다시 01 02, A5 01 0B, A5 01 0D — FCNID 11과 13. S6F11 event report와 S6F13 annotated event report만 spool 대상이라는 뜻이다.

Drain 요청은 이렇다.

TX  00 00 00 0D  00 0B 86 17 00 00 00 00 02 04
    A5 01 00

86이 W-bit + Stream 6, 17이 Function 23이고 body 전체가 A5 01 00, 즉 RSDC = 0 전송이다. 마지막 byte를 01로 바꾸면 똑같이 생긴 3 byte가 purge다. 확인 절차도 없고, 앞 값과 대조할 두 번째 field도 없다. 40분치 이력이 아무도 안 보는 byte 하나에 걸려 있다.

Data message 셋은 모두 같은 모양으로 답이 왔다.

RX  00 00 00 0A  00 0B 02 00 00 00 00 00 02 02     S2F0
RX  00 00 00 0A  00 0B 02 00 00 00 00 00 02 03     S2F0
RX  00 00 00 0A  00 0B 06 00 00 00 00 00 02 04     S6F0

Header byte 3이 00, Function 0이다. E5의 abort transaction이고, 보낸 쪽 SystemBytes를 그대로 달고 body 없이 돌아온다. 이 listener는 HSMS control message와 일부 data stream만 구현하고 spooling은 없으니, 장비 동작으로 읽으면 안 된다. Host가 처리해야 할 응답의 한 형태로 읽는 게 맞다. SxF0은 "이 transaction은 끝났고 더 올 것이 없다"는 뜻인데, 기대한 function만 매칭하는 host는 이걸 무시하고 이미 도착한 답을 기다리며 T3(기본 45초)를 끝까지 태운다.

바로 뒤에 보낸 Linktest는 같은 millisecond에 돌아왔다.

TX  00 00 00 0A  00 0B 00 00 00 05  00 00 02 05     Linktest.req
RX  00 00 00 0A  00 0B 00 00 00 06  00 00 02 05     Linktest.rsp

이 글의 요지가 네 줄에 들어 있다. HSMS는 내내 멀쩡했고, application layer는 아무 말도 하지 않았다. Spooling은 선이 뽑히는 순간이 아니라 이 판정에서 시작된다.

미리 알아두면 좋은 실패 형태

  • Report에 CEID는 넣고 해석에 필요한 variable은 빼서, MES가 lot ID 없는 "process complete"를 받는다.
  • 같은 fab, 같은 GEM version인데 controller reboot 후 spool이 살아남는 장비와 사라지는 장비가 섞여 있다.
  • Alarm set은 spool 대상이고 clear는 아니라서(또는 반대로), MES에 끝나지 않는 alarm이 남는다.
  • 3,000건이 몇 초 만에 몰려 들어와 host 자신의 T3를 넘기고, host가 link를 끊고, 장비는 절반만 비운 queue를 들고 다시 SPOOL LOAD로 돌아간다. 이걸 막는 constant가 MaxSpoolTransmit인데, 설정된 현장을 거의 못 봤다.
  • 장비 설정 화면에서는 spooling이 enable인데 S2F43이 한 번도 온 적이 없어서 spoolable 목록이 비어 있다. 기능이 장식이다.

마지막 항목은 흔해서 제일 먼저 확인할 만하다. Host connect sequence capture를 받아서 S1F13/S1F14와 첫 S6F11 사이에 S2F43이 있는지 본다. 없으면 이 글의 나머지는 아직 아무 의미가 없다.

연습할 곳

40분치 lot 이력이 걸린 상태에서 spool이 넘치는 장면은 실제 장비에서만 나온다. 나머지는 직접 socket에 올려볼 수 있다. SECS/GEM 시뮬레이터는 위와 같은 S2F43, S6F23 bytes를 그대로 받고, Linktest를 무시하게 만들어 두면 host가 T3가 아니라 T6로 session이 끊겼다고 판정하는 것도 볼 수 있다. 한 번 해보면 "통신이 끊겼다"가 한 시점이 아니라는 게 보인다.