1
0

new learnigns

This commit is contained in:
Aiden
2026-05-26 00:59:38 +10:00
parent 443789d6ae
commit d1d924c408
6 changed files with 1685 additions and 849 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -113,26 +113,45 @@ extern volatile u8 MEM8[0x10000];
* - byte4: value_lo (medium) - candidate low byte of a word value
* - byte5: checksum (high) - 0x5A-seeded XOR of bytes 0..4
* dispatch: command_low3 = RX_FRAME(0) & 0x07; observed H'00, H'01, H'02, H'04, H'05, H'06, H'07
* dispatcher split: FAA2 == 0 accepts initial/idle commands H'00, H'01, H'02, H'07; FAA2 != 0 accepts continuation commands H'04, H'05, H'06, H'07
* dispatcher caveat: Initial dispatch follows checksum validation and RX error handling. Command 1 is only on the initial/idle path and is also gated by F861.bit7 == 0.
* dispatch evidence: H'BC08, H'BC0C, H'BC20, H'BC22, H'BC24, H'BC26, H'BC29, H'BC2B, H'BC2E, H'BC30, H'BC45, H'BC47, H'BC4A, H'BC4C, H'BC4F, H'BC51, H'BC54, H'BC56
* index decoder: RX[1:2] -> logical index via loc_622B (medium)
* command candidates:
* - H'00 set_value_acked: candidate write of RX[3:4] into primary/current tables, followed by a response; handler H'BC69; responses response_at_BCCD
* - H'01 read_value: candidate read from the primary table, followed by a response carrying the value; handler H'BCD7; responses response_at_BCFA
* availability: valid checksum/no RX physical error && FAA2 == 0
* - H'01 read_value: initial/idle-path primary table read only, followed by an odd response staging sequence; handler H'BCD7; responses response_at_BCFA
* availability: valid checksum/no RX physical error && FAA2 == 0 && F861.bit7 == 0
* note: Only accepted on the initial/idle dispatcher path: valid checksum/no RX error, FAA2 == 0, and F861.bit7 == 0.
* note: BCD7 stages F850=0x04, writes F851 from F861 and then overwrites F851 from F862.
* note: BCD7 reads the primary table word at E000 + 2*selector; F854 receives the low byte and F853 receives the high byte.
* note: F852 is not freshly written in the BCD7 handler, so do not describe the response as a fixed 04 00 QQ hi lo frame.
* - H'02 clear_or_abort: candidate clear/abort path with no immediate response builder; handler H'BD04; responses none
* availability: valid checksum/no RX physical error && FAA2 == 0
* - H'04 set_value_no_immediate_reply: candidate write/update path that stores a value without an immediate serial response; handler H'BD0E; responses none
* - H'05 ack_or_clear_pending: candidate pending/event acknowledgement path; handler H'BD80; responses none
* availability: valid checksum/no RX physical error && FAA2 != 0
* - H'05 ack_or_clear_pending: continuation-only conditional acknowledgement/session clear path; handler H'BD80; responses none
* availability: valid checksum/no RX physical error && FAA2 != 0
* note: Only accepted on the continuation dispatcher path when FAA2 != 0.
* note: For selector 0x0040, frame 05 00 40 00 00 1F performs no response staging.
* note: The handler clears FAA3/FAA2; F9B5 advances only when FAA2.bit3 was set from a queued report.
* note: If FAA2 == 0, command 5 falls through the initial dispatcher instead of doing acknowledgement work.
* - H'06 set_secondary_value: candidate secondary-table value write path; handler H'BDDB; responses none
* - H'07 retransmit_or_error_reply: candidate retransmit/NAK-style path; error handling also builds command 0x07 responses; handler H'BE05; responses response_at_BE22
* availability: valid checksum/no RX physical error && FAA2 != 0
* - H'07 retransmit_or_error_reply: candidate retransmit path; retry/error handling also builds a command 0x07 RX-payload echo; handler H'BE05; responses response_at_BE22
* availability: valid checksum/no RX physical error && FAA2 == 0 OR valid checksum/no RX physical error && FAA2 != 0
* note: loc_BE4D is a retry/error echo path: F850=0x07 and F861-F864 are copied into F851-F854 before loc_BA26.
* note: Observed frame 07 80 40 20 90 2D means RX bytes F861-F864 were 80 40 20 90; it is not a table value.
* command effects:
* - H'00 set_value_acked: Candidate acknowledged set: writes value bytes to primary/current tables, flags the index, and stages an echo-style response.
* effect: table_write_candidate; target primary_value_table_candidate; source RX[3:4] value bytes, with an observed 0x80 fallback when decoded index is zero; table H'E000
* effect: table_write_candidate; target current_value_table_candidate; source same candidate value written to the primary table; table H'E800
* effect: flag_update_candidate; target per_index_flag_table_candidate; set bit 7; table H'EC00
* evidence: H'BC08, H'BC0C, H'BC20, H'BC22, H'BCB0, H'BCB9, H'BCC1, H'BCC9, H'BCB5, H'BCBD, H'BCC5, H'BCCD
* - H'01 read_value: Candidate read: reads the primary table and stages a value response.
* - H'01 read_value: Initial/idle candidate read: reads the primary table and stages an odd value response with F852 possibly stale.
* effect: table_read_candidate; target primary_value_table_candidate; table H'E000
* effect: response_staging_candidate; stage F850-F854 and call loc_BA26
* evidence: H'BC08, H'BC0C, H'BC24, H'BC26, H'BCB0, H'BCB9, H'BCC1, H'BCC9, H'BCD7, H'BCE0, H'BCE8, H'BCF0, H'BCF6, H'BCB5, H'BCBD, H'BCC5, H'BCDC, H'BCE4, H'BCFA
* evidence: H'BC08, H'BC0C, H'BC24, H'BC26, H'BCD7, H'BCE0, H'BCE8, H'BCF0, H'BCF6, H'BCDC, H'BCE4, H'BCFA
* - H'02 clear_or_abort: Candidate clear/abort: clears serial session state without an observed immediate response.
* effect: state_clear_candidate; target serial_session_flags_candidate; clear bit 7; state H'FAA2
* evidence: H'BC08, H'BC0C, H'BC29, H'BC2B
@@ -140,15 +159,16 @@ extern volatile u8 MEM8[0x10000];
* effect: table_write_candidate; target primary_value_table_candidate; source RX[3:4] value bytes, with an observed 0x80 fallback when decoded index is zero; table H'E000
* effect: flag_update_candidate; target per_index_flag_table_candidate; set bit 7; table H'EC00
* evidence: H'BC08, H'BC0C, H'BC45, H'BC47
* - H'05 ack_or_clear_pending: Candidate acknowledgement/clear: updates pending/event state without an observed immediate response.
* effect: pending_acknowledgement_candidate; target selected event/pending state; clear selected pending flags and then clear serial session state
* - H'05 ack_or_clear_pending: Continuation-only ACK/session clear: clears FAA3/FAA2 and only advances F9B5 when queued-report FAA2.bit3 was set; selector 0x0040 has no response.
* effect: conditional_ack_session_clear_candidate; target selected event/pending state; when FAA2 != 0, clear FAA3/FAA2; advance F9B5 only if FAA2.bit3 was set from queued-report state; selector 0x0040 stages no response; selector H'0040 has no response
* evidence: H'BC08, H'BC0C, H'BC4A, H'BC4C
* - H'06 set_secondary_value: Candidate secondary set: writes value bytes to the secondary table and flags the index.
* effect: table_write_candidate; target secondary_value_table_candidate; source RX[3:4] value bytes; table H'E400
* effect: flag_update_candidate; target per_index_flag_table_candidate; set bit 6; table H'EC00
* evidence: H'BC08, H'BC0C, H'BC4F, H'BC51
* - H'07 retransmit_or_error_reply: Candidate retransmit/error reply: reuses prior TX bytes or builds an explicit 0x07 retry/error response.
* - H'07 retransmit_or_error_reply: Candidate retransmit/error reply: reuses prior TX bytes or copies RX payload bytes behind an explicit 0x07 retry/error echo.
* effect: retransmit_candidate; target TX staging bytes H'F850-H'F854 before loc_BA26; source previous TX frame bytes H'F858-H'F85C
* effect: retry_error_echo_candidate; target F850=0x07, F851-F854=F861-F864 before loc_BA26; source RX payload bytes F861-F864; 07 80 40 20 90 2D echoes RX payload 80 40 20 90; it is not a table value
* effect: response_staging_candidate; stage F850-F854 and call loc_BA26
* evidence: H'BC08, H'BC0C, H'BC2E, H'BC30, H'BC54, H'BC56, H'BE09, H'BE11, H'BE19, H'BE22
* response schemas:
@@ -156,8 +176,11 @@ extern volatile u8 MEM8[0x10000];
* evidence: H'BB1C, H'BB2B, H'BB20, H'BB3F, H'BB39
* - response_at_BCCD: byte0=0x04; byte1=rx[1]; byte2=rx[2]; byte3=rx[3]; byte4=rx[4]
* evidence: H'BCB0, H'BCB9, H'BCC1, H'BCC9
* - response_at_BCFA: byte0=0x04; byte1=rx[2]; byte2=rx[2]; byte3=primary_value_table_candidate; byte4=primary_value_table_candidate
* evidence: H'BCD7, H'BCE8, H'BCC1, H'BCF6, H'BCF0
* - response_at_BCFA: byte0=0x04; byte1=rx[2]; byte2=stale/unchanged; byte3=primary_value_table_candidate; byte4=primary_value_table_candidate
* note: Command 1 BCD7 staging is odd: F850=0x04; F851 is written from F861 then overwritten by F862.
* note: The primary table word is read from E000 + 2*selector; F854/F853 receive low/high value bytes.
* note: F852 may be stale or unchanged in this handler; avoid a fixed 04 00 QQ hi lo response shape.
* evidence: H'BCD7, H'BCE8, H'BCF6, H'BCF0
* - response_at_BE22: byte0=tx[0]; byte1=tx[1]; byte2=tx[2]; byte3=tx[3]; byte4=tx[4]
* evidence: H'BE09, H'BE11, H'BE19
* - ... 1 more candidate response schemas
@@ -186,7 +209,9 @@ extern volatile u8 MEM8[0x10000];
* - ... 7 more state-variable candidates
* retry/error model candidate:
* - checksum path: 0x5A-seeded XOR over RX[0..4] differs from RX[5] -> loc_BE29
* - retry path: counter H'FAA6, threshold 2; Candidate retry path clears/consults serial flags, increments FAA6, compares it with 2, and when still below the apparent limit stages a command 0x07 response.
* - retry path: counter H'FAA6, threshold 2; Candidate retry path clears/consults serial flags, increments FAA6, compares it with 2, and when still below the apparent limit enters loc_BE4D to stage a command 0x07 retry/error echo of RX payload bytes F861-F864.
* echo path: loc_BE4D: F850=0x07; F851-F854=F861-F864
* echo caveat: 07 80 40 20 90 2D echoes RX payload 80 40 20 90 and is not a table value.
* - command 0x07 path: Candidate retransmit/explicit command 0x07 path either copies previous TX frame bytes back to F850-F854 or stages an observed 0x07 response before loc_BA26.
* - evidence: H'BBD8, H'BBDC, H'BBE0, H'BBE4, H'BBE8, H'BBEC, H'BBF0, H'BE4D, H'BE56, H'BE5E, H'BE66, H'BE52, H'BE5A, H'BE62, H'BE6A, H'BE29, H'BE2D, H'BE33, H'BE37, H'BE43, H'BE47, H'BE05, H'BE0D, H'BE15, H'BE09, H'BE11, H'BE19, H'BE22
* gate/queue state machine candidate:
@@ -201,7 +226,7 @@ extern volatile u8 MEM8[0x10000];
* - session_timeout_clears_gate_and_queue: When F9C5 is clear, loc_3FEF clears F9B5/F9B0 and clears FAA5.bit7; when nonzero, it sets FAA5.bit7.
* - idle_heartbeat_gate_initial_delay_loaded: Startup/init loads F9C4 with H'14 before the first idle/default report can be queued.
* - idle_heartbeat_gate_post_send_delay_loaded: loc_BA26 reloads F9C4 with H'07 after each send, matching the observed heartbeat spacing.
* - host_ack_can_advance_queue: Commands 0x05/0x06 are modeled as acknowledgement paths that can clear pending state or advance F9B5.; commands H'05, H'06
* - host_ack_can_advance_queue: Command 0x05 is a continuation-only ACK/session clear path: it clears FAA3/FAA2 and advances F9B5 only when queued-report FAA2.bit3 was set. Selector 0x0040 has no response; if FAA2 == 0 the command falls through instead of doing ACK work.; commands H'05
* - caveat: Many panel controls may require host/session traffic before reporting. Observed autonomous call/camera-power indexes are runtime/capture overlays, not ROM constants.
* - evidence: H'3FD3, H'3FD7, H'3FD9, H'3FDD, H'3FDF, H'3FE3, H'3FE5, H'3FE9, H'3FEB, H'3FEF, H'3FF3, H'3FF5, H'3FF9, H'3FFD, H'4001, H'4003, H'4005, H'4007, H'4046, H'404A, H'404C, H'4050, H'4052, H'4056, H'4058, H'4059, H'405D, H'405F, H'4063, H'4065, H'4067, H'406C, H'4070, H'BAF2, H'BAF6, H'BAF8, H'BAFC, H'BAFE, H'BB00, H'BB04, H'BB06, H'BB08, H'BB0C, H'BB0E, H'BB11, H'BB13, H'BB15, H'BB17, H'BB19, H'BB1C, H'BB20, H'BB24, H'BB26, H'BB29, H'BB2B, H'BB2F, H'BB33, H'BB35, H'BB39, H'BB3D, H'BB3F, H'BB43, H'BE9E, H'BEA2, H'BEA5, H'BEA9, H'BEAD, H'BEAF, H'BEB3, H'BEB5, H'BEB9, H'BEBB, H'BEBF, H'BEC1, H'BEC5, H'BECB, H'BECF, H'BED1, H'BED5
* TX/autonomous report model candidate:
@@ -338,33 +363,70 @@ void sci1_process_candidate_protocol_command(void)
u16 logical_index = sci1_rx_candidate_logical_index();
u16 value = sci1_rx_candidate_value();
bool session_active = MEM8[0xFAA2u] != 0u;
if (!session_active) {
/* Initial/idle dispatcher: valid checksum/no RX error, FAA2 == 0. */
switch (command) {
case 0x00u:
/* set_value_acked: candidate write of RX[3:4] into primary/current tables, followed by a response
* availability: valid checksum/no RX physical error && FAA2 == 0
* candidate effect: table_write_candidate; target primary_value_table_candidate; source RX[3:4] value bytes, with an observed 0x80 fallback when decoded index is zero; table H'E000
* candidate effect: table_write_candidate; target current_value_table_candidate; source same candidate value written to the primary table; table H'E800
* candidate effect: flag_update_candidate; target per_index_flag_table_candidate; set bit 7; table H'EC00
* evidence: H'BC08, H'BC0C, H'BC20, H'BC22, H'BCB0, H'BCB9, H'BCC1, H'BCC9, H'BCB5, H'BCBD, H'BCC5, H'BCCD
*/
candidate_set_value_acked(logical_index, value);
break;
case 0x01u:
/* read_value: initial/idle-path primary table read only, followed by an odd response staging sequence
* availability: valid checksum/no RX physical error && FAA2 == 0 && F861.bit7 == 0
* note: Only accepted on the initial/idle dispatcher path: valid checksum/no RX error, FAA2 == 0, and F861.bit7 == 0.
* note: BCD7 stages F850=0x04, writes F851 from F861 and then overwrites F851 from F862.
* note: BCD7 reads the primary table word at E000 + 2*selector; F854 receives the low byte and F853 receives the high byte.
* note: F852 is not freshly written in the BCD7 handler, so do not describe the response as a fixed 04 00 QQ hi lo frame.
* candidate effect: table_read_candidate; target primary_value_table_candidate; table H'E000
* candidate effect: response_staging_candidate; stage F850-F854 and call loc_BA26
* evidence: H'BC08, H'BC0C, H'BC24, H'BC26, H'BCD7, H'BCE0, H'BCE8, H'BCF0, H'BCF6, H'BCDC, H'BCE4, H'BCFA
*/
if ((RX_FRAME(1) & 0x80u) != 0u) {
candidate_unknown_command(command, logical_index, value);
break;
}
candidate_read_value(logical_index, value);
break;
case 0x02u:
/* clear_or_abort: candidate clear/abort path with no immediate response builder
* availability: valid checksum/no RX physical error && FAA2 == 0
* candidate effect: state_clear_candidate; target serial_session_flags_candidate; clear bit 7; state H'FAA2
* evidence: H'BC08, H'BC0C, H'BC29, H'BC2B
*/
candidate_clear_or_abort(logical_index, value);
break;
case 0x07u:
/* retransmit_or_error_reply: candidate retransmit path; retry/error handling also builds a command 0x07 RX-payload echo
* availability: valid checksum/no RX physical error && FAA2 == 0 OR valid checksum/no RX physical error && FAA2 != 0
* note: loc_BE4D is a retry/error echo path: F850=0x07 and F861-F864 are copied into F851-F854 before loc_BA26.
* note: Observed frame 07 80 40 20 90 2D means RX bytes F861-F864 were 80 40 20 90; it is not a table value.
* candidate effect: retransmit_candidate; target TX staging bytes H'F850-H'F854 before loc_BA26; source previous TX frame bytes H'F858-H'F85C
* candidate effect: retry_error_echo_candidate; target F850=0x07, F851-F854=F861-F864 before loc_BA26; source RX payload bytes F861-F864; 07 80 40 20 90 2D echoes RX payload 80 40 20 90; it is not a table value
* candidate effect: response_staging_candidate; stage F850-F854 and call loc_BA26
* evidence: H'BC08, H'BC0C, H'BC2E, H'BC30, H'BC54, H'BC56, H'BE09, H'BE11, H'BE19, H'BE22
*/
candidate_retransmit_or_error_reply(logical_index, value);
break;
default:
candidate_unknown_command(command, logical_index, value);
break;
}
return;
}
/* Continuation dispatcher: FAA2 != 0. */
switch (command) {
case 0x00u:
/* set_value_acked: candidate write of RX[3:4] into primary/current tables, followed by a response
* candidate effect: table_write_candidate; target primary_value_table_candidate; source RX[3:4] value bytes, with an observed 0x80 fallback when decoded index is zero; table H'E000
* candidate effect: table_write_candidate; target current_value_table_candidate; source same candidate value written to the primary table; table H'E800
* candidate effect: flag_update_candidate; target per_index_flag_table_candidate; set bit 7; table H'EC00
* evidence: H'BC08, H'BC0C, H'BC20, H'BC22, H'BCB0, H'BCB9, H'BCC1, H'BCC9, H'BCB5, H'BCBD, H'BCC5, H'BCCD
*/
candidate_set_value_acked(logical_index, value);
break;
case 0x01u:
/* read_value: candidate read from the primary table, followed by a response carrying the value
* candidate effect: table_read_candidate; target primary_value_table_candidate; table H'E000
* candidate effect: response_staging_candidate; stage F850-F854 and call loc_BA26
* evidence: H'BC08, H'BC0C, H'BC24, H'BC26, H'BCB0, H'BCB9, H'BCC1, H'BCC9, H'BCD7, H'BCE0, H'BCE8, H'BCF0, H'BCF6, H'BCB5, H'BCBD, H'BCC5, H'BCDC, H'BCE4, H'BCFA
*/
candidate_read_value(logical_index, value);
break;
case 0x02u:
/* clear_or_abort: candidate clear/abort path with no immediate response builder
* candidate effect: state_clear_candidate; target serial_session_flags_candidate; clear bit 7; state H'FAA2
* evidence: H'BC08, H'BC0C, H'BC29, H'BC2B
*/
candidate_clear_or_abort(logical_index, value);
break;
case 0x04u:
/* set_value_no_immediate_reply: candidate write/update path that stores a value without an immediate serial response
* availability: valid checksum/no RX physical error && FAA2 != 0
* candidate effect: table_write_candidate; target primary_value_table_candidate; source RX[3:4] value bytes, with an observed 0x80 fallback when decoded index is zero; table H'E000
* candidate effect: flag_update_candidate; target per_index_flag_table_candidate; set bit 7; table H'EC00
* evidence: H'BC08, H'BC0C, H'BC45, H'BC47
@@ -372,14 +434,20 @@ void sci1_process_candidate_protocol_command(void)
candidate_set_value_no_immediate_reply(logical_index, value);
break;
case 0x05u:
/* ack_or_clear_pending: candidate pending/event acknowledgement path
* candidate effect: pending_acknowledgement_candidate; target selected event/pending state; clear selected pending flags and then clear serial session state
/* ack_or_clear_pending: continuation-only conditional acknowledgement/session clear path
* availability: valid checksum/no RX physical error && FAA2 != 0
* note: Only accepted on the continuation dispatcher path when FAA2 != 0.
* note: For selector 0x0040, frame 05 00 40 00 00 1F performs no response staging.
* note: The handler clears FAA3/FAA2; F9B5 advances only when FAA2.bit3 was set from a queued report.
* note: If FAA2 == 0, command 5 falls through the initial dispatcher instead of doing acknowledgement work.
* candidate effect: conditional_ack_session_clear_candidate; target selected event/pending state; when FAA2 != 0, clear FAA3/FAA2; advance F9B5 only if FAA2.bit3 was set from queued-report state; selector 0x0040 stages no response; selector H'0040 has no response
* evidence: H'BC08, H'BC0C, H'BC4A, H'BC4C
*/
candidate_ack_or_clear_pending(logical_index, value);
break;
case 0x06u:
/* set_secondary_value: candidate secondary-table value write path
* availability: valid checksum/no RX physical error && FAA2 != 0
* candidate effect: table_write_candidate; target secondary_value_table_candidate; source RX[3:4] value bytes; table H'E400
* candidate effect: flag_update_candidate; target per_index_flag_table_candidate; set bit 6; table H'EC00
* evidence: H'BC08, H'BC0C, H'BC4F, H'BC51
@@ -387,8 +455,12 @@ void sci1_process_candidate_protocol_command(void)
candidate_set_secondary_value(logical_index, value);
break;
case 0x07u:
/* retransmit_or_error_reply: candidate retransmit/NAK-style path; error handling also builds command 0x07 responses
/* retransmit_or_error_reply: candidate retransmit path; retry/error handling also builds a command 0x07 RX-payload echo
* availability: valid checksum/no RX physical error && FAA2 == 0 OR valid checksum/no RX physical error && FAA2 != 0
* note: loc_BE4D is a retry/error echo path: F850=0x07 and F861-F864 are copied into F851-F854 before loc_BA26.
* note: Observed frame 07 80 40 20 90 2D means RX bytes F861-F864 were 80 40 20 90; it is not a table value.
* candidate effect: retransmit_candidate; target TX staging bytes H'F850-H'F854 before loc_BA26; source previous TX frame bytes H'F858-H'F85C
* candidate effect: retry_error_echo_candidate; target F850=0x07, F851-F854=F861-F864 before loc_BA26; source RX payload bytes F861-F864; 07 80 40 20 90 2D echoes RX payload 80 40 20 90; it is not a table value
* candidate effect: response_staging_candidate; stage F850-F854 and call loc_BA26
* evidence: H'BC08, H'BC0C, H'BC2E, H'BC30, H'BC54, H'BC56, H'BE09, H'BE11, H'BE19, H'BE22
*/

View File

@@ -385,6 +385,17 @@ def _semantics_lines(
" * dispatch: command_low3 = RX_FRAME(0) & 0x07"
+ (f"; observed {values}" if values else ""),
)
state_split = dispatch.get("state_split") or dispatch.get("dispatcher_split")
if isinstance(state_split, dict):
initial = ", ".join(str(item) for item in state_split.get("initial_idle_commands_hex", []) if item)
continuation = ", ".join(str(item) for item in state_split.get("continuation_commands_hex", []) if item)
lines.append(
" * dispatcher split: FAA2 == 0 accepts initial/idle commands "
f"{initial or 'none'}; FAA2 != 0 accepts continuation commands {continuation or 'none'}",
)
caveat = str(state_split.get("caveat") or "").strip()
if caveat:
lines.append(f" * dispatcher caveat: {_comment_text(caveat)}")
if opts.include_evidence:
lines.append(f" * dispatch evidence: {_hex_join(dispatch.get('evidence_addresses_hex'))}")
@@ -405,6 +416,11 @@ def _semantics_lines(
handler = command.get("handler_start_hex") or "multiple"
responses = ", ".join(str(item) for item in command.get("response_candidates", [])) or "none"
lines.append(f" * - {value} {name}: {summary}; handler {handler}; responses {responses}")
availability = _comment_text(str(command.get("availability_summary") or ""))
if availability:
lines.append(f" * availability: {availability}")
for note in command.get("semantic_notes", []):
lines.append(f" * note: {_comment_text(str(note))}")
lines.extend(_command_effect_comment_lines(protocol.get("command_effects"), opts, prefix=" * "))
lines.extend(_response_schema_comment_lines(_schema_list(protocol), opts, prefix=" * "))
lines.extend(_table_map_comment_lines(_table_map_list(protocol), opts, prefix=" * "))
@@ -458,9 +474,51 @@ def _semantics_lines(
" u16 logical_index = sci1_rx_candidate_logical_index();",
" u16 value = sci1_rx_candidate_value();",
"",
" switch (command) {",
],
)
lines.extend(_command_dispatch_switch_lines(commands, opts))
return lines
def _command_dispatch_switch_lines(commands: list[JsonObject], opts: SerialPseudocodeOptions) -> list[str]:
initial = [
command for command in commands
if _command_has_availability(command, "initial_idle_dispatch")
]
continuation = [
command for command in commands
if _command_has_availability(command, "continuation_dispatch")
]
if not initial and not continuation:
return _flat_command_switch_lines(commands, opts, indent=" ") + ["}", ""]
lines = [
" bool session_active = MEM8[0xFAA2u] != 0u;",
"",
" if (!session_active) {",
" /* Initial/idle dispatcher: valid checksum/no RX error, FAA2 == 0. */",
]
lines.extend(_flat_command_switch_lines(initial, opts, indent=" "))
lines.extend(
[
" return;",
" }",
"",
" /* Continuation dispatcher: FAA2 != 0. */",
],
)
lines.extend(_flat_command_switch_lines(continuation, opts, indent=" "))
lines.extend(["}", ""])
return lines
def _flat_command_switch_lines(
commands: list[JsonObject],
opts: SerialPseudocodeOptions,
*,
indent: str,
) -> list[str]:
lines = [f"{indent}switch (command) {{"]
for command in commands:
value = command.get("command_value")
if not isinstance(value, int):
@@ -468,28 +526,50 @@ def _semantics_lines(
name = _safe_identifier(str(command.get("name_candidate") or f"command_{value:02X}"))
summary = _comment_text(str(command.get("summary") or "candidate command semantics unknown"))
evidence = _hex_join(command.get("evidence_addresses_hex"))
lines.append(f" case 0x{value:02X}u:")
lines.append(f" /* {name}: {summary}")
lines.append(f"{indent}case 0x{value:02X}u:")
lines.append(f"{indent} /* {name}: {summary}")
availability = _comment_text(str(command.get("availability_summary") or ""))
if availability:
lines.append(f"{indent} * availability: {availability}")
for note in command.get("semantic_notes", []):
lines.append(f"{indent} * note: {_comment_text(str(note))}")
for effect_line in _command_effect_switch_lines(command):
lines.append(f" * {effect_line}")
lines.append(f"{indent} * {effect_line}")
if opts.include_evidence and evidence:
lines.append(f" * evidence: {evidence}")
lines.append(" */")
lines.append(f" candidate_{name}(logical_index, value);")
lines.append(" break;")
lines.append(f"{indent} * evidence: {evidence}")
lines.append(f"{indent} */")
if value == 0x01:
lines.append(f"{indent} if ((RX_FRAME(1) & 0x80u) != 0u) {{")
lines.append(f"{indent} candidate_unknown_command(command, logical_index, value);")
lines.append(f"{indent} break;")
lines.append(f"{indent} }}")
lines.append(f"{indent} candidate_{name}(logical_index, value);")
lines.append(f"{indent} break;")
lines.extend(
[
" default:",
" candidate_unknown_command(command, logical_index, value);",
" break;",
" }",
"}",
"",
f"{indent}default:",
f"{indent} candidate_unknown_command(command, logical_index, value);",
f"{indent} break;",
f"{indent}}}",
],
)
return lines
def _command_has_availability(command: JsonObject, availability: str) -> bool:
value = command.get("availability")
if value == availability:
return True
if isinstance(value, list) and availability in value:
return True
conditions = " ".join(str(item) for item in command.get("availability_conditions", []))
if availability == "initial_idle_dispatch":
return "FAA2 == 0" in conditions
if availability == "continuation_dispatch":
return "FAA2 != 0" in conditions
return False
def _command_effect_comment_lines(
value: object,
opts: SerialPseudocodeOptions,
@@ -528,6 +608,8 @@ def _response_schema_comment_lines(
response_id = schema.get("response_id") or schema.get("call_address_hex") or "candidate_response"
byte_text = _response_schema_summary(schema)
lines.append(f"{prefix}- {response_id}: {byte_text}")
for note in schema.get("semantic_notes", []):
lines.append(f"{prefix} note: {_comment_text(str(note))}")
evidence = _hex_join(schema.get("evidence_addresses_hex"))
if opts.include_evidence and evidence:
lines.append(f"{prefix} evidence: {evidence}")
@@ -612,6 +694,13 @@ def _retry_error_comment_lines(
threshold = retry.get("threshold_candidate", "?")
summary = _comment_text(str(retry.get("summary") or "candidate retry path"))
lines.append(f"{prefix}- retry path: counter {counter}, threshold {threshold}; {summary}")
echo = retry.get("echo_response_candidate")
if isinstance(echo, dict):
staging = _comment_text(str(echo.get("staging_candidate") or "retry/error echo staging"))
caveat = _comment_text(str(echo.get("observed_frame_caveat") or ""))
lines.append(f"{prefix} echo path: {echo.get('entry_label', 'loc_BE4D')}: {staging}")
if caveat:
lines.append(f"{prefix} echo caveat: {caveat}")
if isinstance(command_07, dict):
summary = _comment_text(str(command_07.get("summary") or "candidate command 0x07 path"))
lines.append(f"{prefix}- command 0x07 path: {summary}")
@@ -987,6 +1076,8 @@ def _effect_summary(effect: JsonObject) -> str:
operation = effect.get("operation_candidate")
table = effect.get("table_base_hex")
state = effect.get("state_address_hex")
selector = effect.get("selector_without_response_hex")
observed = effect.get("observed_frame_caveat")
if target:
parts.append(f"target {target}")
if source:
@@ -997,6 +1088,10 @@ def _effect_summary(effect: JsonObject) -> str:
parts.append(f"table {table}")
if state:
parts.append(f"state {state}")
if selector:
parts.append(f"selector {selector} has no response")
if observed:
parts.append(str(observed))
return _comment_text("; ".join(parts))

View File

@@ -392,6 +392,9 @@ def _find_command_dispatch(ordered: list[JsonObject]) -> JsonObject | None:
continue
comparisons = _dispatch_comparisons(ordered, index + 1, selector_reg)
state_split = _dispatcher_state_split(ordered, index + 1, comparisons)
if state_split:
_annotate_dispatch_comparison_availability(comparisons, state_split)
command_values = sorted({int(item["command_value"]) for item in comparisons})
candidate = {
"kind": "command_dispatch_candidate",
@@ -413,6 +416,8 @@ def _find_command_dispatch(ordered: list[JsonObject]) -> JsonObject | None:
"command_values": command_values,
"command_values_hex": [_h16(value, width=2) for value in command_values],
"comparisons": comparisons,
"state_split": state_split,
"dispatcher_split": state_split,
"cases": [
{
"value": int(item["command_value"]),
@@ -421,6 +426,8 @@ def _find_command_dispatch(ordered: list[JsonObject]) -> JsonObject | None:
"target_hex": item["handler_start_hex"],
"compare_address": item["compare_address"],
"branch_address": item["branch_address"],
"availability": item.get("availability"),
"availability_conditions": item.get("availability_conditions", []),
}
for item in comparisons
],
@@ -446,6 +453,101 @@ def _find_command_dispatch(ordered: list[JsonObject]) -> JsonObject | None:
return best
def _dispatcher_state_split(
ordered: list[JsonObject],
start_index: int,
comparisons: list[JsonObject],
) -> JsonObject | None:
if not comparisons:
return None
for index in range(start_index, min(len(ordered) - 1, start_index + 24)):
ins = ordered[index]
if not _has_ref_in_range(ins, 0xFAA2, 0xFAA2):
continue
if _mnemonic_root(str(ins.get("mnemonic", ""))) != "TST":
continue
branch = ordered[index + 1]
if _mnemonic_root(str(branch.get("mnemonic", ""))) != "BNE":
continue
targets = _targets(branch)
if not targets:
continue
continuation_start = int(targets[0])
initial_values = sorted(
{
int(item["command_value"])
for item in comparisons
if int(item.get("compare_address", 0)) < continuation_start
}
)
continuation_values = sorted(
{
int(item["command_value"])
for item in comparisons
if int(item.get("compare_address", 0)) >= continuation_start
}
)
if not initial_values and not continuation_values:
continue
return {
"kind": "serial_command_dispatch_state_split",
"state_address": 0xFAA2,
"state_address_hex": _h16(0xFAA2),
"test_address": int(ins["address"]),
"test_address_hex": _h16(int(ins["address"])),
"branch_address": int(branch["address"]),
"branch_address_hex": _h16(int(branch["address"])),
"continuation_target": continuation_start,
"continuation_target_hex": _h16(continuation_start),
"initial_idle_commands": initial_values,
"initial_idle_commands_hex": [_h16(value, width=2) for value in initial_values],
"continuation_commands": continuation_values,
"continuation_commands_hex": [_h16(value, width=2) for value in continuation_values],
"summary": (
"FAA2 == 0 takes the initial/idle dispatcher path; FAA2 != 0 takes "
"the continuation dispatcher path."
),
"caveat": (
"Initial dispatch follows checksum validation and RX error handling. Command 1 "
"is only on the initial/idle path and is also gated by F861.bit7 == 0."
),
"evidence_addresses": [int(ins["address"]), int(branch["address"])],
"evidence_addresses_hex": _hlist([int(ins["address"]), int(branch["address"])]),
}
return None
def _annotate_dispatch_comparison_availability(
comparisons: list[JsonObject],
state_split: Mapping[str, Any],
) -> None:
continuation_start = state_split.get("continuation_target")
if not isinstance(continuation_start, int):
return
for item in comparisons:
compare_address = item.get("compare_address")
if not isinstance(compare_address, int):
continue
command_value = int(item.get("command_value", -1))
if compare_address < continuation_start:
availability = "initial_idle_dispatch"
conditions = [
"valid checksum/no RX physical error",
"FAA2 == 0",
]
if command_value == 0x01:
conditions.append("F861.bit7 == 0")
else:
availability = "continuation_dispatch"
conditions = [
"valid checksum/no RX physical error",
"FAA2 != 0",
]
item["availability"] = availability
item["availability_conditions"] = conditions
item["availability_summary"] = " && ".join(conditions)
def _dispatch_comparisons(
ordered: list[JsonObject],
start_index: int,
@@ -537,6 +639,8 @@ def _command_candidates(
"dispatch_compare_address_hex": comparison["compare_address_hex"],
"dispatch_branch_address": comparison["branch_address"],
"dispatch_branch_address_hex": comparison["branch_address_hex"],
"availability": comparison.get("availability"),
"availability_conditions": comparison.get("availability_conditions", []),
}
if alternative not in command["handler_alternatives"]:
command["handler_alternatives"].append(alternative)
@@ -555,6 +659,30 @@ def _command_candidates(
command["handler_start_hex"] = _h16(starts_for_command[0]) if len(starts_for_command) == 1 else None
command["handler_end"] = ends_for_command[0] if len(ends_for_command) == 1 else None
command["handler_end_hex"] = _h16(ends_for_command[0]) if len(ends_for_command) == 1 else None
availability_conditions = _dedupe_strings(
str(condition)
for alt in alternatives
for condition in alt.get("availability_conditions", [])
if condition
)
availability = _dedupe_strings(
str(alt["availability"])
for alt in alternatives
if alt.get("availability")
)
availability_summaries = _dedupe_strings(
" && ".join(str(condition) for condition in alt.get("availability_conditions", []) if condition)
for alt in alternatives
if alt.get("availability_conditions")
)
command["availability"] = availability[0] if len(availability) == 1 else availability
command["availability_conditions"] = availability_conditions
command["availability_summary"] = (
availability_summaries[0]
if len(availability_summaries) == 1
else " OR ".join(availability_summaries)
)
command["semantic_notes"] = _command_semantic_notes(int(command["command_value"]))
ranges_for_command = [
(alt["handler_start"], alt["handler_end"])
@@ -657,26 +785,47 @@ def _command_name_candidate(value: int) -> str:
def _command_summary(value: int) -> str:
return {
0x00: "candidate write of RX[3:4] into primary/current tables, followed by a response",
0x01: "candidate read from the primary table, followed by a response carrying the value",
0x01: "initial/idle-path primary table read only, followed by an odd response staging sequence",
0x02: "candidate clear/abort path with no immediate response builder",
0x04: "candidate write/update path that stores a value without an immediate serial response",
0x05: "candidate pending/event acknowledgement path",
0x05: "continuation-only conditional acknowledgement/session clear path",
0x06: "candidate secondary-table value write path",
0x07: "candidate retransmit/NAK-style path; error handling also builds command 0x07 responses",
0x07: "candidate retransmit path; retry/error handling also builds a command 0x07 RX-payload echo",
}.get(value, "candidate command semantics are unknown")
def _command_semantic_notes(value: int) -> list[str]:
return {
0x01: [
"Only accepted on the initial/idle dispatcher path: valid checksum/no RX error, FAA2 == 0, and F861.bit7 == 0.",
"BCD7 stages F850=0x04, writes F851 from F861 and then overwrites F851 from F862.",
"BCD7 reads the primary table word at E000 + 2*selector; F854 receives the low byte and F853 receives the high byte.",
"F852 is not freshly written in the BCD7 handler, so do not describe the response as a fixed 04 00 QQ hi lo frame.",
],
0x05: [
"Only accepted on the continuation dispatcher path when FAA2 != 0.",
"For selector 0x0040, frame 05 00 40 00 00 1F performs no response staging.",
"The handler clears FAA3/FAA2; F9B5 advances only when FAA2.bit3 was set from a queued report.",
"If FAA2 == 0, command 5 falls through the initial dispatcher instead of doing acknowledgement work.",
],
0x07: [
"loc_BE4D is a retry/error echo path: F850=0x07 and F861-F864 are copied into F851-F854 before loc_BA26.",
"Observed frame 07 80 40 20 90 2D means RX bytes F861-F864 were 80 40 20 90; it is not a table value.",
],
}.get(value, [])
def _command_effect_summary(value: int, effects: list[JsonObject]) -> str:
if not effects:
return "No structured command effects were inferred."
return {
0x00: "Candidate acknowledged set: writes value bytes to primary/current tables, flags the index, and stages an echo-style response.",
0x01: "Candidate read: reads the primary table and stages a value response.",
0x01: "Initial/idle candidate read: reads the primary table and stages an odd value response with F852 possibly stale.",
0x02: "Candidate clear/abort: clears serial session state without an observed immediate response.",
0x04: "Candidate deferred set: writes value bytes and flags the index without an observed immediate response.",
0x05: "Candidate acknowledgement/clear: updates pending/event state without an observed immediate response.",
0x05: "Continuation-only ACK/session clear: clears FAA3/FAA2 and only advances F9B5 when queued-report FAA2.bit3 was set; selector 0x0040 has no response.",
0x06: "Candidate secondary set: writes value bytes to the secondary table and flags the index.",
0x07: "Candidate retransmit/error reply: reuses prior TX bytes or builds an explicit 0x07 retry/error response.",
0x07: "Candidate retransmit/error reply: reuses prior TX bytes or copies RX payload bytes behind an explicit 0x07 retry/error echo.",
}.get(value, "Candidate effects are inferred from handler-local writes, reads, calls, and response staging.")
@@ -740,9 +889,10 @@ def _command_effects(
{
"kind": "table_read_candidate",
"target_candidate": "primary_value_table_candidate",
"destination_candidate": "response value bytes",
"destination_candidate": "response value bytes F854/F853, with F852 not freshly written by BCD7",
"table_base": 0xE000,
"table_base_hex": _h16(0xE000),
"address_expression_candidate": "E000 + 2*selector",
"evidence_addresses": _table_access_addresses(table_accesses, 0x2000, "read"),
}
)
@@ -781,9 +931,15 @@ def _command_effects(
elif value == 0x05:
add(
{
"kind": "pending_acknowledgement_candidate",
"kind": "conditional_ack_session_clear_candidate",
"target_candidate": "selected event/pending state",
"operation_candidate": "clear selected pending flags and then clear serial session state",
"operation_candidate": (
"when FAA2 != 0, clear FAA3/FAA2; advance F9B5 only if FAA2.bit3 was "
"set from queued-report state; selector 0x0040 stages no response"
),
"selector_without_response_hex": _h16(0x0040),
"requires": ["FAA2 != 0"],
"fallthrough_when": "FAA2 == 0",
"evidence_addresses": _dedupe_ints(
_state_access_addresses(state_accesses, 0xFAA2)
+ _state_access_addresses(state_accesses, 0xFAA3)
@@ -822,6 +978,16 @@ def _command_effects(
"evidence_addresses": _addresses_in_ranges(ordered, ranges, 0xBE05, 0xBE22),
}
)
add(
{
"kind": "retry_error_echo_candidate",
"source_candidate": "RX payload bytes F861-F864",
"destination_candidate": "F850=0x07, F851-F854=F861-F864 before loc_BA26",
"observed_frame_caveat": "07 80 40 20 90 2D echoes RX payload 80 40 20 90; it is not a table value",
"response_candidates": [item for item in response_ids if item == "response_at_BE6A"],
"evidence_addresses": _addresses_in_ranges(ordered, ranges, 0xBE4D, 0xBE6A),
}
)
if response_ids:
add(
@@ -852,6 +1018,10 @@ def _command_effect_aliases(commands: list[JsonObject]) -> list[JsonObject]:
"command_value_hex": command.get("command_value_hex"),
"name_candidate": command.get("name_candidate"),
"summary": command.get("effect_summary", command.get("summary")),
"availability": command.get("availability"),
"availability_conditions": command.get("availability_conditions", []),
"availability_summary": command.get("availability_summary"),
"semantic_notes": command.get("semantic_notes", []),
"effects": command.get("effects", []),
"response_candidates": command.get("response_candidates", []),
"evidence_addresses": command.get("evidence_addresses", []),
@@ -938,6 +1108,7 @@ def _response_candidates(ordered: list[JsonObject]) -> list[JsonObject]:
}
response["schema"] = _response_schema(response)
response["byte_schema"] = response["schema"]["bytes"]
response["semantic_notes"] = response["schema"].get("semantic_notes", [])
responses.append(response)
return responses
@@ -1004,6 +1175,7 @@ def _response_schema(response: Mapping[str, Any]) -> JsonObject:
for addr in item.get("evidence_addresses", [])
if isinstance(addr, int)
)
semantic_notes = _response_schema_semantic_notes(response, bytes_out)
return {
"kind": "response_schema_candidate",
"response_id": response.get("id"),
@@ -1014,6 +1186,7 @@ def _response_schema(response: Mapping[str, Any]) -> JsonObject:
"buffer_end": TX_STAGING_END,
"buffer_end_hex": _h16(TX_STAGING_END),
"bytes": bytes_out,
"semantic_notes": semantic_notes,
"evidence_addresses": evidence_addresses,
"evidence_addresses_hex": _hlist(evidence_addresses),
"confidence": "candidate-medium" if evidence_addresses else "candidate-low",
@@ -1024,6 +1197,30 @@ def _response_schema(response: Mapping[str, Any]) -> JsonObject:
}
def _response_schema_semantic_notes(
response: Mapping[str, Any],
bytes_out: list[JsonObject],
) -> list[str]:
call_address = response.get("call_address")
if call_address == 0xBCFA:
for item in bytes_out:
if item.get("offset") == 2 and item.get("source_kind") == "unknown":
item["source_kind"] = "stale_or_unchanged"
item["source_expression"] = "stale/unchanged"
item["caveat"] = "BCD7 does not freshly write F852 before loc_BA26."
return [
"Command 1 BCD7 staging is odd: F850=0x04; F851 is written from F861 then overwritten by F862.",
"The primary table word is read from E000 + 2*selector; F854/F853 receive low/high value bytes.",
"F852 may be stale or unchanged in this handler; avoid a fixed 04 00 QQ hi lo response shape.",
]
if call_address == 0xBE6A:
return [
"loc_BE4D retry/error echo stages F850=0x07 and copies F861-F864 into F851-F854 before loc_BA26.",
"Observed 07 80 40 20 90 2D echoes RX payload bytes 80 40 20 90; it is not a table-derived value.",
]
return []
def _response_schemas(responses: list[JsonObject]) -> list[JsonObject]:
return [
response["schema"]
@@ -1203,6 +1400,7 @@ def _response_builder_aliases(responses: list[JsonObject]) -> list[JsonObject]:
"call_address": response.get("call_address"),
"call_address_hex": response.get("call_address_hex"),
"writes": writes,
"semantic_notes": response.get("semantic_notes", []),
"evidence_addresses": response.get("evidence_addresses", []),
"evidence_addresses_hex": response.get("evidence_addresses_hex", []),
"confidence": response.get("confidence", "medium"),
@@ -1571,6 +1769,7 @@ def _retry_error_model(ordered: list[JsonObject], responses: list[JsonObject]) -
retry_evidence = _dedupe_ints(
[int(ins["address"]) for ins in retry_path if _has_ref_in_range(ins, 0xFAA2, 0xFAA6)]
+ [int(ins["address"]) for ins in retry_path if _has_ref_in_range(ins, TX_STAGING_START, TX_STAGING_END)]
+ [int(ins["address"]) for ins in retry_path if _has_ref_in_range(ins, RX_FRAME_START + 1, RX_FRAME_START + 4)]
+ [int(ins["address"]) for ins in retry_path if _is_send_builder_call(ins)]
+ _response_evidence_addresses(responses, checksum_error_response)
)
@@ -1606,8 +1805,16 @@ def _retry_error_model(ordered: list[JsonObject], responses: list[JsonObject]) -
"response_candidates": checksum_error_response,
"summary": (
"Candidate retry path clears/consults serial flags, increments FAA6, compares it "
"with 2, and when still below the apparent limit stages a command 0x07 response."
"with 2, and when still below the apparent limit enters loc_BE4D to stage a "
"command 0x07 retry/error echo of RX payload bytes F861-F864."
),
"echo_response_candidate": {
"entry_label": "loc_BE4D",
"entry_address": 0xBE4D,
"entry_address_hex": _h16(0xBE4D),
"staging_candidate": "F850=0x07; F851-F854=F861-F864",
"observed_frame_caveat": "07 80 40 20 90 2D echoes RX payload 80 40 20 90 and is not a table value.",
},
"evidence_addresses": retry_evidence,
"evidence_addresses_hex": _hlist(retry_evidence),
"confidence": "candidate-medium" if retry_counter_evidence else "candidate-low",
@@ -1646,7 +1853,7 @@ def _gate_queue_model(ordered: list[JsonObject], commands: list[JsonObject]) ->
command_ack_values = [
int(command["command_value"])
for command in commands
if command.get("command_value") in {0x05, 0x06}
if command.get("command_value") == 0x05
]
if not evidence and not command_ack_values:
return None
@@ -1774,13 +1981,18 @@ def _gate_queue_model(ordered: list[JsonObject], commands: list[JsonObject]) ->
},
{
"name": "host_ack_can_advance_queue",
"summary": "Commands 0x05/0x06 are modeled as acknowledgement paths that can clear pending state or advance F9B5.",
"summary": (
"Command 0x05 is a continuation-only ACK/session clear path: it clears "
"FAA3/FAA2 and advances F9B5 only when queued-report FAA2.bit3 was set. "
"Selector 0x0040 has no response; if FAA2 == 0 the command falls through "
"instead of doing ACK work."
),
"command_values_hex": [_h16(value, width=2) for value in command_ack_values],
"state_addresses_hex": [_h16(0xF9B5)],
"state_addresses_hex": [_h16(0xFAA2), _h16(0xFAA3), _h16(0xF9B5)],
"evidence_addresses": _dedupe_ints(
addr
for command in commands
if command.get("command_value") in {0x05, 0x06}
if command.get("command_value") == 0x05
for addr in command.get("evidence_addresses", [])
if isinstance(addr, int)
),
@@ -2283,9 +2495,26 @@ def _response_window(ordered: list[JsonObject], call_index: int) -> list[JsonObj
for index in range(call_index - 1, max(-1, call_index - 48), -1):
candidate = ordered[index]
mnemonic = str(candidate.get("mnemonic", "")).upper()
if mnemonic in {"RTS", "RTE"}:
root = _mnemonic_root(mnemonic)
if root in {"RTS", "RTE"}:
break
if candidate.get("kind") == "branch" and mnemonic != "BSR":
if root in {
"BRA",
"BEQ",
"BNE",
"BCS",
"BCC",
"BHI",
"BLS",
"BGE",
"BLT",
"BGT",
"BLE",
"BMI",
"BPL",
"BVS",
"BVC",
}:
break
start = index
return ordered[start:call_index]

View File

@@ -196,6 +196,60 @@ class SerialPseudocodeTest(unittest.TestCase):
self.assertIn("case 0x01u:", text)
self.assertIn("candidate_read_value(logical_index, value);", text)
def test_protocol_pseudocode_surfaces_dispatcher_split(self):
payload = candidate_payload()
payload["instructions"] = [
{"address": 0xBC08, "mnemonic": "MOV:G.B", "operands": "@H'F860, R0", "references": [{"address": 0xF860}], "targets": []},
{"address": 0xBC0C, "mnemonic": "AND.B", "operands": "#H'07, R0", "references": [], "targets": []},
{"address": 0xBC0F, "mnemonic": "TST.B", "operands": "@H'FAA2", "references": [{"address": 0xFAA2}], "targets": []},
{"address": 0xBC13, "mnemonic": "BNE", "operands": "loc_BC3A", "references": [], "targets": [0xBC3A]},
{"address": 0xBC19, "mnemonic": "BTST.B", "operands": "#7, @H'F861", "references": [{"address": 0xF861}], "targets": []},
{"address": 0xBC20, "mnemonic": "CMP:E", "operands": "#H'00, R0", "references": [], "targets": []},
{"address": 0xBC22, "mnemonic": "BEQ", "operands": "loc_BC69", "references": [], "targets": [0xBC69]},
{"address": 0xBC24, "mnemonic": "CMP:E", "operands": "#H'01, R0", "references": [], "targets": []},
{"address": 0xBC26, "mnemonic": "BEQ", "operands": "loc_BCD7", "references": [], "targets": [0xBCD7]},
{"address": 0xBC29, "mnemonic": "CMP:E", "operands": "#H'02, R0", "references": [], "targets": []},
{"address": 0xBC2B, "mnemonic": "BEQ", "operands": "loc_BD04", "references": [], "targets": [0xBD04]},
{"address": 0xBC2E, "mnemonic": "CMP:E", "operands": "#H'07, R0", "references": [], "targets": []},
{"address": 0xBC30, "mnemonic": "BEQ", "operands": "loc_BE05", "references": [], "targets": [0xBE05]},
{"address": 0xBC45, "mnemonic": "CMP:E", "operands": "#H'04, R0", "references": [], "targets": []},
{"address": 0xBC47, "mnemonic": "BEQ", "operands": "loc_BD0E", "references": [], "targets": [0xBD0E]},
{"address": 0xBC4A, "mnemonic": "CMP:E", "operands": "#H'05, R0", "references": [], "targets": []},
{"address": 0xBC4C, "mnemonic": "BEQ", "operands": "loc_BD80", "references": [], "targets": [0xBD80]},
{"address": 0xBC4F, "mnemonic": "CMP:E", "operands": "#H'06, R0", "references": [], "targets": []},
{"address": 0xBC51, "mnemonic": "BEQ", "operands": "loc_BDDB", "references": [], "targets": [0xBDDB]},
{"address": 0xBC54, "mnemonic": "CMP:E", "operands": "#H'07, R0", "references": [], "targets": []},
{"address": 0xBC56, "mnemonic": "BEQ", "operands": "loc_BE05", "references": [], "targets": [0xBE05]},
]
text = generate_serial_pseudocode(payload)
self.assertIn("dispatcher split: FAA2 == 0 accepts initial/idle commands H'00, H'01, H'02, H'07; FAA2 != 0 accepts continuation commands H'04, H'05, H'06, H'07", text)
self.assertIn("bool session_active = MEM8[0xFAA2u] != 0u;", text)
self.assertIn("Initial/idle dispatcher", text)
self.assertIn("Continuation dispatcher", text)
self.assertIn("availability: valid checksum/no RX physical error && FAA2 == 0 && F861.bit7 == 0", text)
def test_protocol_pseudocode_surfaces_retry_echo_schema(self):
payload = candidate_payload()
payload["instructions"] = [
{"address": 0xBE4B, "mnemonic": "BRA", "operands": "loc_BE6D", "references": [], "targets": [0xBE6D]},
{"address": 0xBE4D, "mnemonic": "MOV:G.B", "operands": "#H'07, @H'F850", "references": [{"address": 0xF850}], "targets": []},
{"address": 0xBE52, "mnemonic": "MOV:G.B", "operands": "@H'F861, R0", "references": [{"address": 0xF861}], "targets": []},
{"address": 0xBE56, "mnemonic": "MOV:G.B", "operands": "R0, @H'F851", "references": [{"address": 0xF851}], "targets": []},
{"address": 0xBE5A, "mnemonic": "MOV:G.W", "operands": "@H'F862, R0", "references": [{"address": 0xF862}], "targets": []},
{"address": 0xBE5E, "mnemonic": "MOV:G.W", "operands": "R0, @H'F852", "references": [{"address": 0xF852}], "targets": []},
{"address": 0xBE62, "mnemonic": "MOV:G.B", "operands": "@H'F864, R0", "references": [{"address": 0xF864}], "targets": []},
{"address": 0xBE66, "mnemonic": "MOV:G.B", "operands": "R0, @H'F854", "references": [{"address": 0xF854}], "targets": []},
{"address": 0xBE6A, "mnemonic": "BSR", "operands": "loc_BA26", "references": [], "targets": [0xBA26]},
]
text = generate_serial_pseudocode(payload)
self.assertIn("response_at_BE6A: byte0=0x07; byte1=rx[1]; byte2=rx[2]; byte3=rx[3]; byte4=rx[4]", text)
self.assertIn("Observed 07 80 40 20 90 2D echoes RX payload bytes 80 40 20 90", text)
self.assertIn("not a table-derived value", text)
def test_surfaces_refined_semantic_candidates(self):
analysis = {
"protocol_semantics": [

View File

@@ -288,6 +288,8 @@ class SerialSemanticsTest(unittest.TestCase):
command_1_text = semantic_text(command_1)
self.assertIn("read", command_1_text)
self.assertIn("response", command_1_text)
self.assertIn("initial/idle", command_1_text)
self.assertIn("f852", command_1_text)
command_6 = command_item(effects, 0x06)
self.assertIsNotNone(command_6)
@@ -295,6 +297,13 @@ class SerialSemanticsTest(unittest.TestCase):
self.assertIn("write", command_6_text)
self.assertIn("secondary_value_table", command_6_text)
command_5 = command_item(effects, 0x05)
self.assertIsNotNone(command_5)
command_5_text = semantic_text(command_5)
self.assertIn("faa2 != 0", command_5_text)
self.assertIn("0040", command_5_text)
self.assertIn("no response", command_5_text)
def test_planned_response_schema_tracks_immediates_and_rx_copies(self):
semantics = only_semantics(self, planned_semantics_payload())
@@ -391,10 +400,105 @@ class SerialSemanticsTest(unittest.TestCase):
self.assertIn("be9e", gate_text)
self.assertIn("bed5", gate_text)
self.assertIn("f9c5", gate_text)
self.assertIn("commands 0x05", gate_text)
self.assertIn("0x06", gate_text)
self.assertIn("command 0x05", gate_text)
self.assertIn("selector 0x0040 has no response", gate_text)
self.assertIn("faa2 == 0", gate_text)
self.assertIn("not rom constants", gate_text)
def test_actual_dispatch_split_marks_initial_and_continuation_commands(self):
semantics = only_semantics(
self,
base_payload(
[
instruction(0xBC08, "MOV:G.B", "@H'F860, R0", [0xF860]),
instruction(0xBC0C, "AND.B", "#H'07, R0"),
instruction(0xBC0F, "TST.B", "@H'FAA2", [0xFAA2]),
instruction(0xBC13, "BNE", "loc_BC3A", targets=[0xBC3A]),
instruction(0xBC19, "BTST.B", "#7, @H'F861", [0xF861]),
instruction(0xBC20, "CMP:E", "#H'00, R0"),
instruction(0xBC22, "BEQ", "loc_BC69", targets=[0xBC69]),
instruction(0xBC24, "CMP:E", "#H'01, R0"),
instruction(0xBC26, "BEQ", "loc_BCD7", targets=[0xBCD7]),
instruction(0xBC29, "CMP:E", "#H'02, R0"),
instruction(0xBC2B, "BEQ", "loc_BD04", targets=[0xBD04]),
instruction(0xBC2E, "CMP:E", "#H'07, R0"),
instruction(0xBC30, "BEQ", "loc_BE05", targets=[0xBE05]),
instruction(0xBC45, "CMP:E", "#H'04, R0"),
instruction(0xBC47, "BEQ", "loc_BD0E", targets=[0xBD0E]),
instruction(0xBC4A, "CMP:E", "#H'05, R0"),
instruction(0xBC4C, "BEQ", "loc_BD80", targets=[0xBD80]),
instruction(0xBC4F, "CMP:E", "#H'06, R0"),
instruction(0xBC51, "BEQ", "loc_BDDB", targets=[0xBDDB]),
instruction(0xBC54, "CMP:E", "#H'07, R0"),
instruction(0xBC56, "BEQ", "loc_BE05", targets=[0xBE05]),
]
),
)
split = semantics["command_dispatch"]["state_split"]
self.assertEqual(split["initial_idle_commands"], [0, 1, 2, 7])
self.assertEqual(split["continuation_commands"], [4, 5, 6, 7])
command_1 = command_item(semantics["commands"], 0x01)
self.assertIn("FAA2 == 0", command_1["availability_conditions"])
self.assertIn("F861.bit7 == 0", command_1["availability_conditions"])
command_5 = command_item(semantics["commands"], 0x05)
self.assertIn("FAA2 != 0", command_5["availability_conditions"])
def test_actual_bcd7_command1_response_marks_f852_stale(self):
semantics = only_semantics(
self,
base_payload(
[
instruction(0xBCD4, "BRA", "loc_BE6F", targets=[0xBE6F]),
instruction(0xBCD7, "MOV:G.B", "#H'04, @H'F850", [0xF850]),
instruction(0xBCDC, "MOV:G.B", "@H'F861, R0", [0xF861]),
instruction(0xBCE0, "MOV:G.B", "R0, @H'F851", [0xF851]),
instruction(0xBCE4, "MOV:G.B", "@H'F862, R0", [0xF862]),
instruction(0xBCE8, "MOV:G.B", "R0, @H'F851", [0xF851]),
instruction(0xBCEC, "MOV:G.W", "@(-H'2000,R4), R0"),
instruction(0xBCF0, "MOV:G.B", "R0, @H'F854", [0xF854]),
instruction(0xBCF4, "SWAP.B", "R0"),
instruction(0xBCF6, "MOV:G.B", "R0, @H'F853", [0xF853]),
instruction(0xBCFA, "BSR", "loc_BA26", targets=[0xBA26]),
]
),
)
schema = semantics["response_schema"][0]
byte_sources = {item["offset"]: item["source_expression"] for item in schema["bytes"]}
self.assertEqual(byte_sources[0], "0x04")
self.assertEqual(byte_sources[1], "rx[2]")
self.assertEqual(byte_sources[2], "stale/unchanged")
self.assertEqual(byte_sources[3], "primary_value_table_candidate")
self.assertEqual(byte_sources[4], "primary_value_table_candidate")
self.assertIn("avoid a fixed 04 00 qq hi lo", semantic_text(schema))
def test_actual_be4d_retry_error_echo_copies_rx_payload(self):
semantics = only_semantics(
self,
base_payload(
[
instruction(0xBE4B, "BRA", "loc_BE6D", targets=[0xBE6D]),
instruction(0xBE4D, "MOV:G.B", "#H'07, @H'F850", [0xF850]),
instruction(0xBE52, "MOV:G.B", "@H'F861, R0", [0xF861]),
instruction(0xBE56, "MOV:G.B", "R0, @H'F851", [0xF851]),
instruction(0xBE5A, "MOV:G.W", "@H'F862, R0", [0xF862]),
instruction(0xBE5E, "MOV:G.W", "R0, @H'F852", [0xF852]),
instruction(0xBE62, "MOV:G.B", "@H'F864, R0", [0xF864]),
instruction(0xBE66, "MOV:G.B", "R0, @H'F854", [0xF854]),
instruction(0xBE6A, "BSR", "loc_BA26", targets=[0xBA26]),
]
),
)
schema = semantics["response_schema"][0]
byte_sources = {item["offset"]: item["source_expression"] for item in schema["bytes"]}
self.assertEqual(byte_sources, {0: "0x07", 1: "rx[1]", 2: "rx[2]", 3: "rx[3]", 4: "rx[4]"})
self.assertIn("80 40 20 90", semantic_text(schema))
self.assertIn("not a table", semantic_text(schema))
def test_timer_interrupt_model_surfaces_frt2_idle_heartbeat_counter(self):
semantics = only_semantics(
self,