diff --git a/COMPLIANCE.md b/COMPLIANCE.md index 96f048b..aa09d00 100644 --- a/COMPLIANCE.md +++ b/COMPLIANCE.md @@ -176,6 +176,11 @@ YAML/handler extensions, not surgery): | S5F15 / S5F16 | E→H | ✅ | catalog | ✅ round-trip (exception recover complete) | | S5F17 / S5F18 | H→E | ✅ | catalog | ✅ round-trip (exception recover abort) | | S2F49 / S2F50 | H→E | ✅ | catalog + server | ✅ round-trip + dispatch (enhanced remote command, OBJSPEC + per-CP CPACK/CEPACK) | +| S12F1 / S12F2 | H→E | ✅ | catalog | ✅ round-trip (Map Setup Data Send, E5 §13) | +| S12F3 / S12F4 | H→E | ✅ | catalog | ✅ round-trip (Map Setup Data Request) | +| S12F5 / S12F6 | E→H | ✅ | catalog | ✅ round-trip (Map Transmit Inquire / Grant) | +| S12F7 / S12F8 | E→H | ✅ | catalog | ✅ round-trip (Map Data Send, row format MAPFT=0) | +| S12F19 | E→H | ✅ | catalog | ✅ round-trip (Map Error Send, one-way) | | S6F1 / S6F2 | E→H | ✅ | catalog | ✅ round-trip | | S6F11 / S6F12 | E→H | ✅ | catalog | ✅ round-trip + demo | | S6F23 / S6F24 | H→E | ✅ | catalog | ✅ round-trip + demo | diff --git a/data/messages.yaml b/data/messages.yaml index bb922b5..ac67722 100644 --- a/data/messages.yaml +++ b/data/messages.yaml @@ -1089,6 +1089,163 @@ messages: builder: s10f6_terminal_display_multi_ack body: {kind: scalar, item_type: BINARY_BYTE, enum: TerminalAck, param: ack} + # ===================================================================== + # S12 — Wafer Maps (E5 §13). + # + # IDTYP discriminates the kind of identifier carried by MID: + # 0 wafer ID, 1 substrate ID, 2 lot+slot, 3 design ID. + # MAPFT selects the data-payload format: + # 0 row format (F7/F8), 1 array format (F9/F10), 2 coordinate (F11/F12). + # Reference points are pairs (I4 coordinates). + # + # This first pass covers the setup + transmit-inquire + row-format data + # + error fan; the alternative-encoding pairs (F9/F10 array, F11/F12 + # coord, F13-F18 corresponding requests) are scope-deferred — they're + # mechanical YAML edits once a tool needs them. + # ===================================================================== + + # S12F1 / F2 — Map Setup Data Send. + - id: S12F1 + stream: 12 + function: 1 + w: true + builder: s12f1_map_setup_send + parser: parse_s12f1 + body: + kind: list + struct_name: MapSetupData + fields: + - {name: mid, shape: {kind: scalar, item_type: ASCII}} + - {name: idtyp, shape: {kind: scalar, item_type: BINARY_BYTE}} + - {name: fnloc, shape: {kind: scalar, item_type: U2}} + - {name: ffrot, shape: {kind: scalar, item_type: U2}} + - {name: orloc, shape: {kind: scalar, item_type: BINARY_BYTE}} + - {name: rpsel, shape: {kind: scalar, item_type: U1}} + - name: refp + shape: + kind: list_of + element: + kind: list + struct_name: ReferencePoint + external_struct: true + fields: + - {name: x, shape: {kind: scalar, item_type: I4}} + - {name: y, shape: {kind: scalar, item_type: I4}} + - name: dutms + shape: {kind: list_of, element: {kind: scalar, item_type: ASCII}} + + - id: S12F2 + stream: 12 + function: 2 + builder: s12f2_map_setup_ack + body: {kind: scalar, item_type: BINARY_BYTE, enum: MapSetupAck, param: sdack} + + # S12F3 / F4 — Map Setup Data Request. + - id: S12F3 + stream: 12 + function: 3 + w: true + builder: s12f3_map_setup_request + parser: parse_s12f3 + body: + kind: list + struct_name: MapSetupRequest + fields: + - {name: mid, shape: {kind: scalar, item_type: ASCII}} + - {name: idtyp, shape: {kind: scalar, item_type: BINARY_BYTE}} + - {name: mapft, shape: {kind: scalar, item_type: BINARY_BYTE}} + + - id: S12F4 + stream: 12 + function: 4 + builder: s12f4_map_setup_reply + parser: parse_s12f4 + body: + kind: list + struct_name: MapSetupReply + fields: + - {name: mid, shape: {kind: scalar, item_type: ASCII}} + - {name: idtyp, shape: {kind: scalar, item_type: BINARY_BYTE}} + - {name: fnloc, shape: {kind: scalar, item_type: U2}} + - {name: orloc, shape: {kind: scalar, item_type: BINARY_BYTE}} + - {name: rpsel, shape: {kind: scalar, item_type: U1}} + - name: refp + shape: + kind: list_of + element: + kind: list + struct_name: ReferencePoint + external_struct: true + fields: + - {name: x, shape: {kind: scalar, item_type: I4}} + - {name: y, shape: {kind: scalar, item_type: I4}} + - name: dutms + shape: {kind: list_of, element: {kind: scalar, item_type: ASCII}} + - {name: xdies, shape: {kind: scalar, item_type: U2}} + - {name: ydies, shape: {kind: scalar, item_type: U2}} + + # S12F5 / F6 — Map Transmit Inquire / Grant. Equipment asks the host + # for permission to send a map (MAPFT specifies the format, MLCL is + # the predicted data length in die units). + - id: S12F5 + stream: 12 + function: 5 + w: true + builder: s12f5_map_transmit_inquire + parser: parse_s12f5 + body: + kind: list + struct_name: MapTransmitInquire + fields: + - {name: mid, shape: {kind: scalar, item_type: ASCII}} + - {name: idtyp, shape: {kind: scalar, item_type: BINARY_BYTE}} + - {name: mapft, shape: {kind: scalar, item_type: BINARY_BYTE}} + - {name: mlcl, shape: {kind: scalar, item_type: U4}} + + - id: S12F6 + stream: 12 + function: 6 + builder: s12f6_map_transmit_grant + body: {kind: scalar, item_type: BINARY_BYTE, enum: MapTransmitGrant, param: grant} + + # S12F7 / F8 — Map Data Send (row format, MAPFT=0). Each row of the + # map is one BINARY blob of packed bin codes; the outer list orders + # rows top-to-bottom relative to ORLOC. + - id: S12F7 + stream: 12 + function: 7 + w: true + builder: s12f7_map_data_send_row + parser: parse_s12f7 + body: + kind: list + struct_name: MapDataRow + fields: + - {name: mid, shape: {kind: scalar, item_type: ASCII}} + - {name: idtyp, shape: {kind: scalar, item_type: BINARY_BYTE}} + - name: rows + shape: {kind: list_of, element: {kind: scalar, item_type: BINARY}} + + - id: S12F8 + stream: 12 + function: 8 + builder: s12f8_map_data_send_row_ack + body: {kind: scalar, item_type: BINARY_BYTE, enum: MapDataAck, param: maper} + + # S12F19 — Map Error Send. Equipment reports a non-recoverable map + # error (e.g. format mismatch, oversize payload). One-way (no reply). + - id: S12F19 + stream: 12 + function: 19 + builder: s12f19_map_error_send + parser: parse_s12f19 + body: + kind: list + struct_name: MapError + fields: + - {name: mapercd, shape: {kind: scalar, item_type: BINARY_BYTE}} + - {name: errtext, shape: {kind: scalar, item_type: ASCII}} + # ===================================================================== # S14 — Object services (E94 ControlJob create/delete). # diff --git a/include/secsgem/gem/messages_helpers.hpp b/include/secsgem/gem/messages_helpers.hpp index 86473c4..4803bcb 100644 --- a/include/secsgem/gem/messages_helpers.hpp +++ b/include/secsgem/gem/messages_helpers.hpp @@ -140,4 +140,35 @@ enum class TerminalAck : uint8_t { // S10F2, S10F4 TerminalNotAvailable = 2, }; +// E5 §13 S12 (Wafer Maps). Encoded as separate enums even though the +// underlying byte semantics differ slightly across F2/F6/F8 — keeping +// them distinct preserves call-site readability. +// Reference point on a wafer/substrate map (E5 §13.7.4 REFP). Declared +// out of line so the messages.yaml catalog can mark it `external_struct` +// and reuse the same C++ type across F1 / F4. +struct ReferencePoint { + int32_t x; + int32_t y; +}; + +enum class MapSetupAck : uint8_t { // S12F2 SDACK + Accept = 0, + Error = 1, +}; + +enum class MapTransmitGrant : uint8_t { // S12F6 GRANT + Granted = 0, + BusyTryAgain = 1, + NoSpaceAvailable = 2, + DuplicateMID = 3, + MaterialNotRecognized = 4, +}; + +enum class MapDataAck : uint8_t { // S12F8 MAPER + Accept = 0, + FormatError = 1, + UnknownID = 2, + AbortNoMap = 3, +}; + } // namespace secsgem::gem diff --git a/tests/test_messages.cpp b/tests/test_messages.cpp index 8639275..699d6d5 100644 --- a/tests/test_messages.cpp +++ b/tests/test_messages.cpp @@ -657,3 +657,83 @@ TEST_CASE("S16F27 / S16F28 CJobCommand round-trip") { CHECK(parsed->ctljobcmd == "CJSTART"); CHECK(*ack_byte(s16f28_cj_command_ack(HostCmdAck::Accept)) == 0); } + +// ---- S12 Wafer Maps (E5 §13) ------------------------------------------- + +TEST_CASE("S12F1 / S12F2 Map Setup Data Send round-trip") { + std::vector refp = {{0, 0}, {100, 200}}; + std::vector dutms = {"01", "02"}; + auto m = s12f1_map_setup_send("WAFER-1", /*idtyp=*/0, /*fnloc=*/0, + /*ffrot=*/0, /*orloc=*/2, /*rpsel=*/2, + refp, dutms); + CHECK(m.stream == 12); + CHECK(m.function == 1); + CHECK(m.reply_expected); + + auto parsed = parse_s12f1(m); + REQUIRE(parsed.has_value()); + CHECK(parsed->mid == "WAFER-1"); + CHECK(parsed->idtyp == 0); + CHECK(parsed->orloc == 2); + REQUIRE(parsed->refp.size() == 2); + CHECK(parsed->refp[1].x == 100); + CHECK(parsed->refp[1].y == 200); + REQUIRE(parsed->dutms.size() == 2); + CHECK(parsed->dutms[0] == "01"); + + CHECK(*ack_byte(s12f2_map_setup_ack(MapSetupAck::Accept)) == 0); + CHECK(*ack_byte(s12f2_map_setup_ack(MapSetupAck::Error)) == 1); +} + +TEST_CASE("S12F3 / S12F4 Map Setup Data Request round-trip") { + auto req = s12f3_map_setup_request("WAFER-1", /*idtyp=*/0, /*mapft=*/0); + auto parsed = parse_s12f3(req); + REQUIRE(parsed.has_value()); + CHECK(parsed->mid == "WAFER-1"); + CHECK(parsed->mapft == 0); + + std::vector refp = {{0, 0}}; + auto reply = s12f4_map_setup_reply("WAFER-1", /*idtyp=*/0, /*fnloc=*/270, + /*orloc=*/0, /*rpsel=*/1, refp, + {"R1C1"}, /*xdies=*/4, /*ydies=*/4); + auto preply = parse_s12f4(reply); + REQUIRE(preply.has_value()); + CHECK(preply->fnloc == 270); + CHECK(preply->xdies == 4); + CHECK(preply->ydies == 4); +} + +TEST_CASE("S12F5 / S12F6 Map Transmit Inquire / Grant round-trip") { + auto m = s12f5_map_transmit_inquire("WAFER-1", /*idtyp=*/0, + /*mapft=*/0, /*mlcl=*/256); + auto parsed = parse_s12f5(m); + REQUIRE(parsed.has_value()); + CHECK(parsed->mlcl == 256); + + CHECK(*ack_byte(s12f6_map_transmit_grant(MapTransmitGrant::Granted)) == 0); + CHECK(*ack_byte(s12f6_map_transmit_grant(MapTransmitGrant::BusyTryAgain)) == 1); +} + +TEST_CASE("S12F7 / S12F8 Map Data Send (row format) round-trip") { + std::vector rows = { + std::string({0x01, 0x02, 0x03}), + std::string({0x04, 0x05, 0x06}), + }; + auto m = s12f7_map_data_send_row("WAFER-1", /*idtyp=*/0, rows); + auto parsed = parse_s12f7(m); + REQUIRE(parsed.has_value()); + REQUIRE(parsed->rows.size() == 2); + CHECK(parsed->rows[0].size() == 3); + CHECK(static_cast(parsed->rows[1][2]) == 0x06); + + CHECK(*ack_byte(s12f8_map_data_send_row_ack(MapDataAck::Accept)) == 0); +} + +TEST_CASE("S12F19 Map Error Send round-trip") { + auto m = s12f19_map_error_send(/*mapercd=*/2, "unrecognized MID"); + auto parsed = parse_s12f19(m); + REQUIRE(parsed.has_value()); + CHECK(parsed->mapercd == 2); + CHECK(parsed->errtext == "unrecognized MID"); + CHECK_FALSE(m.reply_expected); +}