Skip to main content

u280_shell_rhdl/
shell.rs

1//! Production U280 shell controller and typed signer boundary.
2
3use rhdl::prelude::*;
4use rhdl_fpga::core::{dff::DFF, ram::synchronous::Write as SyncBramWrite};
5use rhdl_primitives::NoResetDff;
6use wots_rhdl::{SignerOutputBeat, SignerTopInput, SignerTopOutput};
7
8use crate::{
9    abi::{
10        self, ERROR_BUFFER, ERROR_CONFIGURATION, ERROR_COUNT, ERROR_FRAME, ERROR_INPUT_AXI,
11        ERROR_OUTPUT_AXI, ERROR_SCOREBOARD, ERROR_SIGNER_COMPLETION, ERROR_SIGNER_FAULT,
12        ERROR_SOFT_ABORT, ERROR_SUMMARY_AXI, OUTPUT_AXI_LEN, TERMINAL_ABORTED_BITS,
13        TERMINAL_CONFIGURATION_BITS, TERMINAL_INPUT_AXI_BITS, TERMINAL_OUTPUT_AXI_BITS,
14        TERMINAL_PROTOCOL_BITS, TERMINAL_SIGNER_BITS, TERMINAL_SUCCESS_BITS,
15        TERMINAL_SUMMARY_AXI_BITS,
16    },
17    axi::{
18        AXI_BURST_INCR, AXI_CACHE_NORMAL, AXI_LOCK_NORMAL, AXI_RESP_DECERR, AXI_RESP_OKAY,
19        AXI_RESP_SLVERR, AXI_SIZE_64_BYTES, Axi512ReadKernelToMemory, Axi512ReadMemoryToKernel,
20        Axi512WriteKernelToMemory, Axi512WriteMemoryToKernel, AxiLiteHostToKernel,
21        AxiLiteKernelToHost, idle_read_master_kernel,
22    },
23    control::{
24        LaunchConfig, RunState, SummaryFields, SummaryWriteStatus, apply_write_strobes_kernel,
25        high_word_kernel, last_status_kernel, low_word_kernel, replace_high_word_kernel,
26        replace_low_word_kernel, summary_line_kernel, validate_config_kernel,
27    },
28    frame_store::{
29        FRAME_STORE_ADDRESS_BITS, FrameStore, FrameStoreInput, captured_beat_is_canonical_kernel,
30        frame_store_address_kernel,
31    },
32    reader::{InputJob, ReaderState, input_job_address_kernel, input_response_is_canonical_kernel},
33    scoreboard::{
34        JobEntry, ScoreboardEvents, scoreboard_occupancy_kernel, scoreboard_update_kernel,
35    },
36    writer::{
37        FrameQueue, FrameQueueOperation, MemoryCompletionTiming, WriterState,
38        frame_queue_update_kernel, frame_slot_is_reserved_kernel, output_job_address_kernel,
39        output_response_is_canonical_kernel, record_memory_completion_kernel,
40    },
41};
42
43/// Stable generated module name consumed by the wire-only U280 adapter.
44pub const U280_HBM_SHELL_MODULE: &str = "hashsigs_u280_hbm_shell_rhdl";
45
46/// Render the complete shell hierarchy without claiming parser or implementation evidence.
47///
48/// The caller must independently parse, simulate, synthesize, implement, and
49/// execute the returned RTL before making claims at those evidence tiers.
50///
51/// # Errors
52///
53/// Returns an error when RHDL cannot construct or render the shell descriptor.
54pub fn try_u280_hbm_shell_unchecked_verilog() -> Result<String, RHDLError> {
55    let descriptor = U280HbmShell::default().descriptor(U280_HBM_SHELL_MODULE.into())?;
56    let hdl = descriptor.hdl_unchecked()?;
57    Ok(hdl.modules.pretty())
58}
59
60/// All external inputs to the RHDL shell.
61#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
62pub struct HbmShellInput {
63    /// AXI4-Lite control slave request channels.
64    pub control: AxiLiteHostToKernel,
65    /// HBM0 response channels.
66    pub input_hbm: Axi512ReadMemoryToKernel,
67    /// HBM1 response channels.
68    pub output_hbm: Axi512WriteMemoryToKernel,
69    /// Separately generated SHA-profile signer outputs.
70    pub signer: SignerTopOutput,
71}
72
73/// All external outputs from the RHDL shell.
74#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
75pub struct HbmShellOutput {
76    /// AXI4-Lite control slave response channels.
77    pub control: AxiLiteKernelToHost,
78    /// HBM0 read request channels.
79    pub input_hbm: Axi512ReadKernelToMemory,
80    /// HBM1 write request channels.
81    pub output_hbm: Axi512WriteKernelToMemory,
82    /// Inputs to the separately generated SHA-profile signer.
83    pub signer: SignerTopInput,
84    /// Synchronous signer-only reset, asserted for exactly two drain-complete cycles.
85    pub signer_soft_reset: bool,
86    /// Level interrupt after global and per-source masking.
87    pub interrupt: bool,
88}
89
90/// Resettable shell ownership and audit state.
91#[allow(clippy::struct_excessive_bools)] // Each bit represents independent protocol ownership.
92#[derive(Clone, Copy, Debug, Digital, Eq, PartialEq)]
93pub struct ShellControl {
94    /// Whole-run state.
95    pub run_state: RunState,
96    /// Host launch arguments.
97    pub config: LaunchConfig,
98    /// HLS-compatible sticky completion bit.
99    pub ap_done: bool,
100    /// Global interrupt enable.
101    pub gier: bool,
102    /// Done/error interrupt mask.
103    pub ier: b2,
104    /// Toggle-on-write sticky done/error status.
105    pub isr: b2,
106    /// Most recent terminal code.
107    pub terminal_code: b8,
108    /// Sticky union of run error causes.
109    pub error_flags: b16,
110    /// First job associated with an error.
111    pub first_error_job: b32,
112    /// First error detail.
113    pub first_error_detail: b32,
114    /// Terminal summary address was accepted.
115    pub summary_aw_handshake: bool,
116    /// Terminal summary data was accepted.
117    pub summary_w_handshake: bool,
118    /// Terminal summary response was drained.
119    pub summary_b_handshake: bool,
120    /// Terminal summary response was exact ID-zero `OKAY`.
121    pub summary_b_canonical: bool,
122    /// Independently retained AXI4-Lite write address.
123    pub axil_aw_pending: bool,
124    /// Retained AXI4-Lite write address.
125    pub axil_awaddr: b8,
126    /// Configuration/run lock state sampled when the write address transferred.
127    pub axil_aw_locked_at_accept: bool,
128    /// Independently retained AXI4-Lite write data.
129    pub axil_w_pending: bool,
130    /// Retained AXI4-Lite write data.
131    pub axil_wdata: b32,
132    /// Retained AXI4-Lite byte strobes.
133    pub axil_wstrb: b4,
134    /// Configuration/run lock state sampled when the write data transferred.
135    pub axil_w_locked_at_accept: bool,
136    /// Retained AXI4-Lite write response.
137    pub axil_bvalid: bool,
138    /// Retained AXI4-Lite write response code.
139    pub axil_bresp: b2,
140    /// Retained AXI4-Lite read response.
141    pub axil_rvalid: bool,
142    /// Retained AXI4-Lite read data.
143    pub axil_rdata: b32,
144    /// Retained AXI4-Lite read response code.
145    pub axil_rresp: b2,
146    /// The retained read response came from `AP_CTRL`.
147    pub axil_r_ap_ctrl: bool,
148    /// HBM0 single-outstanding state.
149    pub reader_state: ReaderState,
150    /// Sequential job currently owned by the reader.
151    pub reader_job: b32,
152    /// A checked HBM0 line is retained.
153    pub input_job_valid: bool,
154    /// Signer ownership table.
155    pub scoreboard: [JobEntry; abi::LIVE_JOB_SLOTS],
156    /// One registered signer beat is retained before validation.
157    pub ingress_valid: bool,
158    /// A frame currently owns one BRAM slot.
159    pub capture_active: bool,
160    /// BRAM slot owned by capture.
161    pub capture_slot: b1,
162    /// Next capture beat in `0..=34`.
163    pub capture_beat: b6,
164    /// Frame job identity retained from beat zero.
165    pub capture_job: b32,
166    /// Frame source cluster retained from beat zero.
167    pub capture_cluster: b2,
168    /// Oldest-first queue of complete BRAM frames.
169    pub frame_queue: FrameQueue,
170    /// HBM1 payload writer state.
171    pub writer_state: WriterState,
172    /// Committed writer slot.
173    pub writer_slot: b1,
174    /// Committed writer job.
175    pub writer_job: b32,
176    /// Next data beat to transfer.
177    pub writer_beat: b6,
178    /// Next BRAM beat to request.
179    pub read_issue_beat: b6,
180    /// The current BRAM output belongs to the writer prefetch.
181    pub read_pending: bool,
182    /// Registered write-prefetch occupancy in `0..=2`.
183    pub prefetch_count: b2,
184    /// A protocol-invalid payload B was drained before final W acceptance.
185    pub writer_early_b_drained: bool,
186    /// Abort-mode uncommitted-state cleanup has occurred.
187    pub abort_cleaned: bool,
188    /// Accepted HBM0 addresses.
189    pub input_ar_count: b16,
190    /// Checked HBM0 responses.
191    pub input_r_count: b16,
192    /// Signer admissions.
193    pub core_admission_count: b16,
194    /// Complete canonical frames captured.
195    pub frame_capture_count: b16,
196    /// Explicit signer error completions.
197    pub core_error_count: b16,
198    /// Accepted payload write addresses.
199    pub payload_aw_count: b16,
200    /// Drained payload write responses.
201    pub payload_b_count: b16,
202    /// Accepted payload data beats.
203    pub payload_w_count: b32,
204    /// Payloads retired by exact ID-zero `OKAY` B responses.
205    pub memory_completed_count: b16,
206    /// Canonical payload B retirement timing, excluding the summary response.
207    pub memory_completion_timing: MemoryCompletionTiming,
208    /// Execute/abort cycle cursor.
209    pub run_cycle: b64,
210    /// Execute/abort payload interval.
211    pub payload_cycles: b64,
212    /// Cycles the signer output was unable to enter the ingress register.
213    pub output_stall_cycles: b64,
214    /// At least one signer admission has occurred.
215    pub first_admission_seen: bool,
216    /// Cycle of first signer admission.
217    pub first_admission_cycle: b64,
218    /// At least one complete frame has reached BRAM.
219    pub first_capture_seen: bool,
220    /// Cycle of first complete frame capture.
221    pub first_capture_cycle: b64,
222    /// First admission through last complete frame, inclusive.
223    pub core_latency_cycles: b64,
224    /// First-to-last complete-frame span.
225    pub capture_span_cycles: b64,
226}
227
228impl Default for ShellControl {
229    fn default() -> Self {
230        Self {
231            run_state: RunState::Idle,
232            config: LaunchConfig::default(),
233            ap_done: false,
234            gier: false,
235            ier: b2(0),
236            isr: b2(0),
237            terminal_code: TERMINAL_SUCCESS_BITS,
238            error_flags: b16(0),
239            first_error_job: b32(0xffff_ffff),
240            first_error_detail: b32(0),
241            summary_aw_handshake: false,
242            summary_w_handshake: false,
243            summary_b_handshake: false,
244            summary_b_canonical: false,
245            axil_aw_pending: false,
246            axil_awaddr: b8(0),
247            axil_aw_locked_at_accept: false,
248            axil_w_pending: false,
249            axil_wdata: b32(0),
250            axil_wstrb: b4(0),
251            axil_w_locked_at_accept: false,
252            axil_bvalid: false,
253            axil_bresp: AXI_RESP_OKAY,
254            axil_rvalid: false,
255            axil_rdata: b32(0),
256            axil_rresp: AXI_RESP_OKAY,
257            axil_r_ap_ctrl: false,
258            reader_state: ReaderState::Idle,
259            reader_job: b32(0),
260            input_job_valid: false,
261            scoreboard: [JobEntry::default(); abi::LIVE_JOB_SLOTS],
262            ingress_valid: false,
263            capture_active: false,
264            capture_slot: b1(0),
265            capture_beat: b6(0),
266            capture_job: b32(0),
267            capture_cluster: b2(0),
268            frame_queue: FrameQueue::default(),
269            writer_state: WriterState::Idle,
270            writer_slot: b1(0),
271            writer_job: b32(0),
272            writer_beat: b6(0),
273            read_issue_beat: b6(0),
274            read_pending: false,
275            prefetch_count: b2(0),
276            writer_early_b_drained: false,
277            abort_cleaned: false,
278            input_ar_count: b16(0),
279            input_r_count: b16(0),
280            core_admission_count: b16(0),
281            frame_capture_count: b16(0),
282            core_error_count: b16(0),
283            payload_aw_count: b16(0),
284            payload_b_count: b16(0),
285            payload_w_count: b32(0),
286            memory_completed_count: b16(0),
287            memory_completion_timing: MemoryCompletionTiming::default(),
288            run_cycle: b64(0),
289            payload_cycles: b64(0),
290            output_stall_cycles: b64(0),
291            first_admission_seen: false,
292            first_admission_cycle: b64(0),
293            first_capture_seen: false,
294            first_capture_cycle: b64(0),
295            core_latency_cycles: b64(0),
296            capture_span_cycles: b64(0),
297        }
298    }
299}
300
301/// RHDL shell with resetless payload registers and BRAM frame storage.
302#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
303pub struct U280HbmShell {
304    control: DFF<ShellControl>,
305    input_job: NoResetDff<InputJob>,
306    ingress: NoResetDff<SignerOutputBeat>,
307    prefetch0: NoResetDff<[b8; 64]>,
308    prefetch1: NoResetDff<[b8; 64]>,
309    frame_store: FrameStore,
310}
311
312impl Default for U280HbmShell {
313    fn default() -> Self {
314        Self {
315            control: DFF::new(ShellControl::default()),
316            input_job: NoResetDff::new(),
317            ingress: NoResetDff::new(),
318            prefetch0: NoResetDff::new(),
319            prefetch1: NoResetDff::new(),
320            frame_store: FrameStore::default(),
321        }
322    }
323}
324
325impl SynchronousIO for U280HbmShell {
326    type I = HbmShellInput;
327    type O = HbmShellOutput;
328    type Kernel = u280_hbm_shell_kernel;
329}
330
331/// Pack live queue state for the read-only diagnostic register.
332#[kernel]
333pub fn queue_status_kernel(control: ShellControl) -> b32 {
334    let scoreboard: b32 = scoreboard_occupancy_kernel(control.scoreboard).resize();
335    let frames: b32 = control.frame_queue.count.resize();
336    let reader: b32 = match control.reader_state {
337        ReaderState::Idle => b32(0),
338        ReaderState::Address => b32(1),
339        ReaderState::Response => b32(2),
340        ReaderState::Hold => b32(3),
341    };
342    let writer: b32 = match control.writer_state {
343        WriterState::Idle => b32(0),
344        WriterState::Address => b32(1),
345        WriterState::Data => b32(2),
346        WriterState::Response => b32(3),
347    };
348    scoreboard
349        | (frames << 4)
350        | (bool_word_kernel(control.ingress_valid) << 6)
351        | (bool_word_kernel(control.capture_active) << 7)
352        | (reader << 8)
353        | (writer << 10)
354        | (control.prefetch_count.resize() << 12)
355}
356
357/// Convert one control bit to a diagnostic word without a Rust-only cast.
358#[kernel]
359pub fn bool_word_kernel(value: bool) -> b32 {
360    if value { b32(1) } else { b32(0) }
361}
362
363/// Transition for the complete U280 transport shell.
364#[kernel]
365#[allow(
366    clippy::assign_op_pattern,
367    clippy::field_reassign_with_default,
368    clippy::if_not_else,
369    clippy::match_same_arms,
370    clippy::needless_range_loop,
371    clippy::too_many_lines
372)] // Preserve the audited fail-first decode, reserved-address inventory, and staged output priority.
373pub fn u280_hbm_shell_kernel(
374    clock_reset: ClockReset,
375    input: HbmShellInput,
376    q: U280HbmShellQ,
377) -> (HbmShellOutput, U280HbmShellD) {
378    let resetting = clock_reset.reset.any();
379    let mut control = q.control;
380    let mut input_job = q.input_job;
381    let mut ingress = q.ingress;
382    let mut prefetch0 = q.prefetch0;
383    let mut prefetch1 = q.prefetch1;
384    let mut frame_store = FrameStoreInput::default();
385
386    let idle_for_registers = q.control.run_state == RunState::Idle;
387    let axil_awready = !q.control.axil_aw_pending && !q.control.axil_bvalid;
388    let axil_wready = !q.control.axil_w_pending && !q.control.axil_bvalid;
389    let axil_arready = !q.control.axil_rvalid;
390    let axil_b_handshake = q.control.axil_bvalid && input.control.bready;
391    let axil_r_handshake = q.control.axil_rvalid && input.control.rready;
392    let mut start_command = false;
393    let mut abort_command = false;
394
395    if axil_b_handshake {
396        control.axil_bvalid = false;
397    }
398    if axil_r_handshake {
399        control.axil_rvalid = false;
400        if q.control.axil_r_ap_ctrl {
401            control.ap_done = false;
402        }
403    }
404    if axil_awready && input.control.awvalid {
405        control.axil_aw_pending = true;
406        control.axil_awaddr = input.control.awaddr;
407        control.axil_aw_locked_at_accept = !idle_for_registers;
408    }
409    if axil_wready && input.control.wvalid {
410        control.axil_w_pending = true;
411        control.axil_wdata = input.control.wdata;
412        control.axil_wstrb = input.control.wstrb;
413        control.axil_w_locked_at_accept = !idle_for_registers;
414    }
415
416    if q.control.axil_aw_pending && q.control.axil_w_pending && !q.control.axil_bvalid {
417        let address = q.control.axil_awaddr;
418        let aligned = address & b8(3) == b8(0);
419        let lane0_written = q.control.axil_wstrb & b4(1) != b4(0);
420        let locked = q.control.axil_aw_locked_at_accept
421            || q.control.axil_w_locked_at_accept
422            || !idle_for_registers;
423        let mut response = AXI_RESP_OKAY;
424        if !aligned {
425            response = AXI_RESP_DECERR;
426        } else {
427            match address {
428                Bits::<8>(0x00) => {
429                    if lane0_written && q.control.axil_wdata & b32(1) != b32(0) {
430                        if !locked {
431                            start_command = true;
432                        } else {
433                            response = AXI_RESP_SLVERR;
434                        }
435                    }
436                }
437                Bits::<8>(0x04) => {
438                    if lane0_written {
439                        control.gier = q.control.axil_wdata & b32(1) != b32(0);
440                    }
441                }
442                Bits::<8>(0x08) => {
443                    if lane0_written {
444                        control.ier = q.control.axil_wdata.resize();
445                    }
446                }
447                Bits::<8>(0x0c) => {
448                    if lane0_written {
449                        let toggles: b2 = q.control.axil_wdata.resize();
450                        control.isr = q.control.isr ^ toggles;
451                    }
452                }
453                Bits::<8>(0x10) => {
454                    if !locked {
455                        control.config.input_base = replace_low_word_kernel(
456                            q.control.config.input_base,
457                            q.control.axil_wdata,
458                            q.control.axil_wstrb,
459                        );
460                    } else {
461                        response = AXI_RESP_SLVERR;
462                    }
463                }
464                Bits::<8>(0x14) => {
465                    if !locked {
466                        control.config.input_base = replace_high_word_kernel(
467                            q.control.config.input_base,
468                            q.control.axil_wdata,
469                            q.control.axil_wstrb,
470                        );
471                    } else {
472                        response = AXI_RESP_SLVERR;
473                    }
474                }
475                Bits::<8>(0x1c) => {
476                    if !locked {
477                        control.config.output_base = replace_low_word_kernel(
478                            q.control.config.output_base,
479                            q.control.axil_wdata,
480                            q.control.axil_wstrb,
481                        );
482                    } else {
483                        response = AXI_RESP_SLVERR;
484                    }
485                }
486                Bits::<8>(0x20) => {
487                    if !locked {
488                        control.config.output_base = replace_high_word_kernel(
489                            q.control.config.output_base,
490                            q.control.axil_wdata,
491                            q.control.axil_wstrb,
492                        );
493                    } else {
494                        response = AXI_RESP_SLVERR;
495                    }
496                }
497                Bits::<8>(0x28) => {
498                    if !locked {
499                        control.config.summary_base = replace_low_word_kernel(
500                            q.control.config.summary_base,
501                            q.control.axil_wdata,
502                            q.control.axil_wstrb,
503                        );
504                    } else {
505                        response = AXI_RESP_SLVERR;
506                    }
507                }
508                Bits::<8>(0x2c) => {
509                    if !locked {
510                        control.config.summary_base = replace_high_word_kernel(
511                            q.control.config.summary_base,
512                            q.control.axil_wdata,
513                            q.control.axil_wstrb,
514                        );
515                    } else {
516                        response = AXI_RESP_SLVERR;
517                    }
518                }
519                Bits::<8>(0x34) => {
520                    if !locked {
521                        control.config.batch = apply_write_strobes_kernel(
522                            q.control.config.batch,
523                            q.control.axil_wdata,
524                            q.control.axil_wstrb,
525                        );
526                    } else {
527                        response = AXI_RESP_SLVERR;
528                    }
529                }
530                Bits::<8>(0x38) => {
531                    if !locked {
532                        control.config.abi_word = apply_write_strobes_kernel(
533                            q.control.config.abi_word,
534                            q.control.axil_wdata,
535                            q.control.axil_wstrb,
536                        );
537                    } else {
538                        response = AXI_RESP_SLVERR;
539                    }
540                }
541                Bits::<8>(0x3c) => {
542                    if !locked {
543                        control.config.nonce = replace_low_word_kernel(
544                            q.control.config.nonce,
545                            q.control.axil_wdata,
546                            q.control.axil_wstrb,
547                        );
548                    } else {
549                        response = AXI_RESP_SLVERR;
550                    }
551                }
552                Bits::<8>(0x40) => {
553                    if !locked {
554                        control.config.nonce = replace_high_word_kernel(
555                            q.control.config.nonce,
556                            q.control.axil_wdata,
557                            q.control.axil_wstrb,
558                        );
559                    } else {
560                        response = AXI_RESP_SLVERR;
561                    }
562                }
563                Bits::<8>(0x18) => {}
564                Bits::<8>(0x24) => {}
565                Bits::<8>(0x30) => {}
566                Bits::<8>(0x44) => {}
567                Bits::<8>(0x48) => {
568                    if lane0_written && q.control.axil_wdata & b32(1) != b32(0) {
569                        let abortable_now = q.control.run_state == RunState::Validate
570                            || q.control.run_state == RunState::Execute;
571                        if locked && abortable_now {
572                            abort_command = true;
573                        } else {
574                            // A split command accepted during a run must never
575                            // turn into a successful no-op after that run has
576                            // crossed its terminal boundary.
577                            response = AXI_RESP_SLVERR;
578                        }
579                    }
580                }
581                Bits::<8>(0x4c) => response = AXI_RESP_SLVERR,
582                Bits::<8>(0x50) => response = AXI_RESP_SLVERR,
583                Bits::<8>(0x54) => response = AXI_RESP_SLVERR,
584                Bits::<8>(0x58) => response = AXI_RESP_SLVERR,
585                Bits::<8>(0x5c) => response = AXI_RESP_SLVERR,
586                Bits::<8>(0x60) => response = AXI_RESP_SLVERR,
587                Bits::<8>(0x64) => response = AXI_RESP_SLVERR,
588                Bits::<8>(0x68) => response = AXI_RESP_SLVERR,
589                Bits::<8>(0x6c) => response = AXI_RESP_SLVERR,
590                Bits::<8>(0x70) => response = AXI_RESP_SLVERR,
591                Bits::<8>(0x74) => response = AXI_RESP_SLVERR,
592                Bits::<8>(0x78) => response = AXI_RESP_SLVERR,
593                Bits::<8>(0x7c) => response = AXI_RESP_SLVERR,
594                Bits::<8>(0x80) => response = AXI_RESP_SLVERR,
595                _ => {
596                    response = AXI_RESP_DECERR;
597                }
598            }
599        }
600        control.axil_aw_pending = false;
601        control.axil_aw_locked_at_accept = false;
602        control.axil_w_pending = false;
603        control.axil_w_locked_at_accept = false;
604        control.axil_bvalid = true;
605        control.axil_bresp = response;
606    }
607
608    if axil_arready && input.control.arvalid {
609        let address = input.control.araddr;
610        let aligned = address & b8(3) == b8(0);
611        let idle = q.control.run_state == RunState::Idle || q.control.run_state == RunState::Done;
612        let ready = q.control.run_state == RunState::Idle;
613        let ap_ctrl: b32 = (bool_word_kernel(q.control.ap_done) << 1)
614            | (bool_word_kernel(idle) << 2)
615            | (bool_word_kernel(ready) << 3);
616        let last_status = last_status_kernel(
617            q.control.terminal_code,
618            q.control.error_flags,
619            SummaryWriteStatus {
620                aw: q.control.summary_aw_handshake,
621                w: q.control.summary_w_handshake,
622                b: q.control.summary_b_handshake,
623                b_canonical: q.control.summary_b_canonical,
624            },
625        );
626        let progress0: b32 = q.control.core_admission_count.resize()
627            | (q.control.frame_capture_count.resize() << 16);
628        let progress1: b32 =
629            q.control.memory_completed_count.resize() | (q.control.core_error_count.resize() << 16);
630        let mut response = AXI_RESP_OKAY;
631        let mut data = b32(0);
632        let mut is_ap_ctrl = false;
633        if !aligned {
634            response = AXI_RESP_DECERR;
635        } else {
636            match address {
637                Bits::<8>(0x00) => {
638                    data = ap_ctrl;
639                    is_ap_ctrl = true;
640                }
641                Bits::<8>(0x04) => data = bool_word_kernel(q.control.gier),
642                Bits::<8>(0x08) => data = q.control.ier.resize(),
643                Bits::<8>(0x0c) => data = q.control.isr.resize(),
644                Bits::<8>(0x10) => data = low_word_kernel(q.control.config.input_base),
645                Bits::<8>(0x14) => data = high_word_kernel(q.control.config.input_base),
646                Bits::<8>(0x1c) => data = low_word_kernel(q.control.config.output_base),
647                Bits::<8>(0x20) => data = high_word_kernel(q.control.config.output_base),
648                Bits::<8>(0x28) => data = low_word_kernel(q.control.config.summary_base),
649                Bits::<8>(0x2c) => data = high_word_kernel(q.control.config.summary_base),
650                Bits::<8>(0x34) => data = q.control.config.batch,
651                Bits::<8>(0x38) => data = q.control.config.abi_word,
652                Bits::<8>(0x3c) => data = low_word_kernel(q.control.config.nonce),
653                Bits::<8>(0x40) => data = high_word_kernel(q.control.config.nonce),
654                Bits::<8>(0x18) => {}
655                Bits::<8>(0x24) => {}
656                Bits::<8>(0x30) => {}
657                Bits::<8>(0x44) => {}
658                Bits::<8>(0x48) => {}
659                Bits::<8>(0x4c) => data = last_status,
660                Bits::<8>(0x50) => data = progress0,
661                Bits::<8>(0x54) => data = progress1,
662                Bits::<8>(0x58) => data = low_word_kernel(q.control.output_stall_cycles),
663                Bits::<8>(0x5c) => data = high_word_kernel(q.control.output_stall_cycles),
664                Bits::<8>(0x60) => data = queue_status_kernel(q.control),
665                Bits::<8>(0x64) => data = q.control.first_error_job,
666                Bits::<8>(0x68) => data = q.control.first_error_detail,
667                Bits::<8>(0x6c) => {
668                    data = low_word_kernel(q.control.memory_completion_timing.first);
669                }
670                Bits::<8>(0x70) => {
671                    data = high_word_kernel(q.control.memory_completion_timing.first);
672                }
673                Bits::<8>(0x74) => {
674                    data = low_word_kernel(q.control.memory_completion_timing.last);
675                }
676                Bits::<8>(0x78) => {
677                    data = high_word_kernel(q.control.memory_completion_timing.last);
678                }
679                Bits::<8>(0x7c) => {
680                    data = low_word_kernel(q.control.memory_completion_timing.span);
681                }
682                Bits::<8>(0x80) => {
683                    data = high_word_kernel(q.control.memory_completion_timing.span);
684                }
685                _ => response = AXI_RESP_DECERR,
686            }
687        }
688        control.axil_rvalid = true;
689        control.axil_rdata = data;
690        control.axil_rresp = response;
691        control.axil_r_ap_ctrl = is_ap_ctrl;
692    }
693
694    if q.control.run_state == RunState::Done {
695        control.run_state = RunState::Idle;
696    }
697
698    if start_command {
699        let retained_config = control.config;
700        let retained_gier = control.gier;
701        let retained_ier = control.ier;
702        let retained_isr = control.isr;
703        let retained_axil_aw_pending = control.axil_aw_pending;
704        let retained_axil_awaddr = control.axil_awaddr;
705        let retained_axil_aw_locked_at_accept = control.axil_aw_locked_at_accept;
706        let retained_axil_w_pending = control.axil_w_pending;
707        let retained_axil_wdata = control.axil_wdata;
708        let retained_axil_wstrb = control.axil_wstrb;
709        let retained_axil_w_locked_at_accept = control.axil_w_locked_at_accept;
710        let retained_axil_bvalid = control.axil_bvalid;
711        let retained_axil_bresp = control.axil_bresp;
712        let retained_axil_rvalid = control.axil_rvalid;
713        let retained_axil_rdata = control.axil_rdata;
714        let retained_axil_rresp = control.axil_rresp;
715        let retained_axil_r_ap_ctrl = control.axil_r_ap_ctrl;
716        control = ShellControl::default();
717        control.config = retained_config;
718        control.gier = retained_gier;
719        control.ier = retained_ier;
720        control.isr = retained_isr;
721        control.axil_aw_pending = retained_axil_aw_pending;
722        control.axil_awaddr = retained_axil_awaddr;
723        control.axil_aw_locked_at_accept = retained_axil_aw_locked_at_accept;
724        control.axil_w_pending = retained_axil_w_pending;
725        control.axil_wdata = retained_axil_wdata;
726        control.axil_wstrb = retained_axil_wstrb;
727        control.axil_w_locked_at_accept = retained_axil_w_locked_at_accept;
728        control.axil_bvalid = retained_axil_bvalid;
729        control.axil_bresp = retained_axil_bresp;
730        control.axil_rvalid = retained_axil_rvalid;
731        control.axil_rdata = retained_axil_rdata;
732        control.axil_rresp = retained_axil_rresp;
733        control.axil_r_ap_ctrl = retained_axil_r_ap_ctrl;
734        control.run_state = RunState::Validate;
735        control.ap_done = false;
736    }
737
738    let scoreboard_occupancy = scoreboard_occupancy_kernel(q.control.scoreboard);
739    let slot0_free = !frame_slot_is_reserved_kernel(
740        q.control.frame_queue,
741        q.control.capture_active,
742        q.control.capture_slot,
743        b1(0),
744    );
745    let slot1_free = !frame_slot_is_reserved_kernel(
746        q.control.frame_queue,
747        q.control.capture_active,
748        q.control.capture_slot,
749        b1(1),
750    );
751    let capture_has_capacity = q.control.capture_active || slot0_free || slot1_free;
752    let ingress_consumed = q.control.run_state == RunState::Execute
753        && q.control.error_flags == b16(0)
754        && q.control.ingress_valid
755        && !abort_command;
756    let ingress_elastic_capacity = !q.control.ingress_valid || ingress_consumed;
757    let hold_final_without_refill = q.control.ingress_valid && q.ingress.beat.last;
758    let signer_output_ready = q.control.run_state == RunState::Execute
759        && q.control.error_flags == b16(0)
760        && capture_has_capacity
761        && ingress_elastic_capacity
762        && !hold_final_without_refill
763        && !abort_command;
764    let signer_start_valid = q.control.run_state == RunState::Execute
765        && q.control.error_flags == b16(0)
766        && q.control.input_job_valid
767        && scoreboard_occupancy < b4(abi::LIVE_JOB_SLOTS as u128)
768        && !abort_command;
769    let signer_start_handshake = signer_start_valid && input.signer.start_ready;
770    let signer_output_handshake = signer_output_ready && input.signer.output_valid;
771    let signer_error_ready = q.control.run_state == RunState::Execute
772        && q.control.error_flags == b16(0)
773        && !abort_command;
774    let signer_error_handshake = signer_error_ready && input.signer.error_valid;
775    let transport_active = q.control.run_state == RunState::Validate
776        || q.control.run_state == RunState::Execute
777        || q.control.run_state == RunState::AbortDrain;
778    let input_ar_handshake =
779        q.control.reader_state == ReaderState::Address && input.input_hbm.arready && !resetting;
780    let input_response_owned =
781        q.control.reader_state == ReaderState::Response || input_ar_handshake;
782    let input_r_handshake = input_response_owned && input.input_hbm.rvalid && !resetting;
783    let writer_w_handshake = q.control.writer_state == WriterState::Data
784        && q.control.prefetch_count != b2(0)
785        && input.output_hbm.wready
786        && !resetting;
787    let writer_final_w_handshake = writer_w_handshake && q.control.writer_beat == b6(34);
788    let output_response_owned = q.control.writer_state == WriterState::Response
789        || (writer_final_w_handshake && !q.control.writer_early_b_drained);
790    let output_b_handshake = output_response_owned && input.output_hbm.bvalid && !resetting;
791    let early_final_b_handshake = q.control.writer_state == WriterState::Data
792        && q.control.writer_beat == b6(34)
793        && !q.control.writer_early_b_drained
794        && input.output_hbm.bvalid
795        && !writer_final_w_handshake
796        && !resetting;
797    let summary_aw_handshake =
798        q.control.run_state == RunState::SummaryAw && input.output_hbm.awready && !resetting;
799    let summary_w_handshake = q.control.run_state == RunState::SummaryW
800        && q.control.summary_aw_handshake
801        && input.output_hbm.wready
802        && !resetting;
803    // A response belongs to the terminal write only after both request
804    // channels have transferred.  Preserve the legal zero-latency case in
805    // which W and B transfer together, but never let an older held B become
806    // evidence for a write whose W channel is still stalled.
807    let summary_response_owned = (q.control.run_state == RunState::SummaryB
808        && q.control.summary_aw_handshake
809        && q.control.summary_w_handshake)
810        || summary_w_handshake;
811    let summary_b_handshake = summary_response_owned && input.output_hbm.bvalid && !resetting;
812    let summary_sequence_active = q.control.run_state == RunState::CoreReset0
813        || q.control.run_state == RunState::CoreReset1
814        || q.control.run_state == RunState::SummaryAw
815        || q.control.run_state == RunState::SummaryW
816        || q.control.run_state == RunState::SummaryB;
817    let premature_summary_b_handshake =
818        summary_sequence_active && !summary_response_owned && input.output_hbm.bvalid && !resetting;
819    let unexpected_input_r = transport_active && input.input_hbm.rvalid && !input_response_owned;
820    let unexpected_output_b = transport_active && input.output_hbm.bvalid && !output_response_owned;
821    let drain_orphan_input_r = transport_active
822        && (q.control.reader_state == ReaderState::Idle
823            || q.control.reader_state == ReaderState::Hold);
824    let drain_orphan_output_b = transport_active && q.control.writer_state == WriterState::Idle;
825
826    let mut new_failure = false;
827    let mut new_error_flags = b16(0);
828    let mut new_terminal_code = TERMINAL_SUCCESS_BITS;
829    let mut new_error_job = b32(0xffff_ffff);
830    let mut new_error_detail = b32(0);
831    let mut scoreboard_events = ScoreboardEvents::default();
832    let mut enqueue_frame = false;
833    let mut enqueue_slot = b1(0);
834    let mut enqueue_job = b32(0);
835    let mut dequeue_frame = false;
836
837    if q.control.run_state == RunState::Validate {
838        let validation = validate_config_kernel(q.control.config);
839        if validation.valid && !abort_command {
840            control.run_state = RunState::Execute;
841            if q.control.config.batch != b32(0) {
842                control.reader_state = ReaderState::Address;
843            }
844        } else if !validation.valid {
845            if !new_failure {
846                new_terminal_code = TERMINAL_CONFIGURATION_BITS;
847                new_error_job = b32(0xffff_ffff);
848                new_error_detail = validation.detail;
849            }
850            new_failure = true;
851            new_error_flags = new_error_flags | b16(ERROR_CONFIGURATION);
852        }
853    }
854
855    if q.control.run_state == RunState::Execute || q.control.run_state == RunState::AbortDrain {
856        control.run_cycle = q.control.run_cycle + b64(1);
857        control.payload_cycles = q.control.payload_cycles + b64(1);
858    }
859
860    if unexpected_input_r {
861        if !new_failure {
862            new_terminal_code = TERMINAL_INPUT_AXI_BITS;
863            new_error_job = if q.control.reader_state == ReaderState::Address {
864                q.control.reader_job
865            } else {
866                b32(0xffff_ffff)
867            };
868            new_error_detail = b32(0x6001)
869                | (input.input_hbm.rresp.resize() << 16)
870                | (input.input_hbm.rid.resize() << 24)
871                | (bool_word_kernel(input.input_hbm.rlast) << 25);
872        }
873        new_failure = true;
874        new_error_flags = new_error_flags | b16(ERROR_INPUT_AXI);
875        if q.control.reader_state == ReaderState::Address && !input_ar_handshake {
876            // RREADY is pre-armed from the registered Address state.  If the
877            // response arrives before AR acceptance, it is drained as an
878            // orphan and no read transaction remains to complete.
879            control.reader_state = ReaderState::Idle;
880        }
881    }
882    if unexpected_output_b {
883        if !new_failure {
884            new_terminal_code = TERMINAL_OUTPUT_AXI_BITS;
885            new_error_job = if q.control.writer_state == WriterState::Idle {
886                b32(0xffff_ffff)
887            } else {
888                q.control.writer_job
889            };
890            new_error_detail = b32(0x6002)
891                | (input.output_hbm.bresp.resize() << 16)
892                | (input.output_hbm.bid.resize() << 24);
893        }
894        new_failure = true;
895        new_error_flags = new_error_flags | b16(ERROR_OUTPUT_AXI);
896    }
897    if premature_summary_b_handshake {
898        if !new_failure {
899            new_terminal_code = TERMINAL_SUMMARY_AXI_BITS;
900            new_error_job = b32(0xffff_ffff);
901            new_error_detail = b32(0x6003)
902                | (input.output_hbm.bresp.resize() << 16)
903                | (input.output_hbm.bid.resize() << 24);
904        }
905        new_failure = true;
906        new_error_flags = new_error_flags | b16(ERROR_SUMMARY_AXI);
907    }
908
909    if input_ar_handshake {
910        control.input_ar_count = q.control.input_ar_count + b16(1);
911        control.reader_state = ReaderState::Response;
912    }
913    if input_r_handshake {
914        let response_ok = input_response_is_canonical_kernel(
915            input.input_hbm.rid,
916            input.input_hbm.rresp,
917            input.input_hbm.rlast,
918        );
919        if response_ok {
920            control.input_r_count = q.control.input_r_count + b16(1);
921            if q.control.run_state == RunState::Execute {
922                input_job = InputJob {
923                    data: input.input_hbm.rdata,
924                    job_id: q.control.reader_job,
925                };
926                control.input_job_valid = true;
927                control.reader_state = ReaderState::Hold;
928            } else {
929                control.reader_state = ReaderState::Idle;
930            }
931        } else {
932            control.reader_state = ReaderState::Idle;
933            if q.control.run_state == RunState::Execute
934                || q.control.run_state == RunState::AbortDrain
935            {
936                if !new_failure {
937                    new_terminal_code = TERMINAL_INPUT_AXI_BITS;
938                    new_error_job = q.control.reader_job;
939                    new_error_detail = input.input_hbm.rresp.resize()
940                        | (input.input_hbm.rid.resize() << 8)
941                        | (bool_word_kernel(input.input_hbm.rlast) << 9);
942                }
943                new_failure = true;
944                new_error_flags = new_error_flags | b16(ERROR_INPUT_AXI);
945            }
946        }
947    }
948
949    if q.control.reader_state == ReaderState::Hold {
950        if q.control.run_state == RunState::AbortDrain {
951            control.input_job_valid = false;
952            control.reader_state = ReaderState::Idle;
953        } else if signer_start_handshake {
954            control.input_job_valid = false;
955            control.core_admission_count = q.control.core_admission_count + b16(1);
956            scoreboard_events.admit = true;
957            scoreboard_events.admit_job = q.input_job.job_id;
958            scoreboard_events.admit_cluster = input.signer.start_cluster;
959            if !q.control.first_admission_seen {
960                control.first_admission_seen = true;
961                control.first_admission_cycle = q.control.run_cycle;
962            }
963            let next_job = q.control.reader_job + b32(1);
964            control.reader_job = next_job;
965            if next_job < q.control.config.batch {
966                control.reader_state = ReaderState::Address;
967            } else {
968                control.reader_state = ReaderState::Idle;
969            }
970        }
971    }
972
973    if q.control.run_state == RunState::Execute && q.control.error_flags == b16(0) {
974        if input.signer.start_accepted != signer_start_handshake {
975            if !new_failure {
976                new_terminal_code = TERMINAL_SIGNER_BITS;
977                new_error_job = if signer_start_valid {
978                    q.input_job.job_id
979                } else {
980                    b32(0xffff_ffff)
981                };
982                new_error_detail = b32(0x1001);
983            }
984            new_failure = true;
985            new_error_flags = new_error_flags | b16(ERROR_SIGNER_FAULT);
986        }
987        if input.signer.delivered_final
988            != (signer_output_handshake && input.signer.output.beat.last)
989        {
990            if !new_failure {
991                new_error_job = input.signer.output.beat.job_id;
992                new_error_detail = b32(0x1002);
993                new_terminal_code = TERMINAL_SIGNER_BITS;
994            }
995            new_failure = true;
996            new_error_flags = new_error_flags | b16(ERROR_SIGNER_FAULT);
997        }
998        if input.signer.delivered_error != signer_error_handshake {
999            if !new_failure {
1000                new_error_job = input.signer.error.job_id;
1001                new_error_detail = b32(0x1003);
1002                new_terminal_code = TERMINAL_SIGNER_BITS;
1003            }
1004            new_failure = true;
1005            new_error_flags = new_error_flags | b16(ERROR_SIGNER_FAULT);
1006        }
1007        if input.signer.fault {
1008            if !new_failure {
1009                new_error_job = b32(0xffff_ffff);
1010                new_error_detail = b32(0x1004);
1011                new_terminal_code = TERMINAL_SIGNER_BITS;
1012            }
1013            new_failure = true;
1014            new_error_flags = new_error_flags | b16(ERROR_SIGNER_FAULT);
1015        }
1016        if input.signer.output_valid && !signer_output_ready {
1017            control.output_stall_cycles = q.control.output_stall_cycles + b64(1);
1018        }
1019    }
1020
1021    if ingress_consumed {
1022        let expected_beat = if q.control.capture_active {
1023            q.control.capture_beat
1024        } else {
1025            b6(0)
1026        };
1027        let canonical = captured_beat_is_canonical_kernel(
1028            q.ingress,
1029            expected_beat,
1030            q.control.capture_active,
1031            q.control.capture_job,
1032            q.control.capture_cluster,
1033        );
1034        control.ingress_valid = false;
1035        if canonical {
1036            let selected_slot = if q.control.capture_active {
1037                q.control.capture_slot
1038            } else if slot0_free {
1039                b1(0)
1040            } else {
1041                b1(1)
1042            };
1043            frame_store.write = SyncBramWrite::<[b8; 64], FRAME_STORE_ADDRESS_BITS> {
1044                addr: frame_store_address_kernel(selected_slot, expected_beat),
1045                value: q.ingress.beat.data,
1046                enable: true,
1047            };
1048            if !q.control.capture_active {
1049                control.capture_active = true;
1050                control.capture_slot = selected_slot;
1051                control.capture_job = q.ingress.beat.job_id;
1052                control.capture_cluster = q.ingress.cluster;
1053                control.capture_beat = b6(1);
1054                scoreboard_events.frame_start = true;
1055                scoreboard_events.frame_start_job = q.ingress.beat.job_id;
1056                scoreboard_events.frame_start_cluster = q.ingress.cluster;
1057            } else if expected_beat == b6(34) {
1058                control.capture_active = false;
1059                control.capture_beat = b6(0);
1060                control.frame_capture_count = q.control.frame_capture_count + b16(1);
1061                enqueue_frame = true;
1062                enqueue_slot = q.control.capture_slot;
1063                enqueue_job = q.control.capture_job;
1064                scoreboard_events.frame_finish = true;
1065                scoreboard_events.frame_finish_job = q.control.capture_job;
1066                if q.control.first_admission_seen {
1067                    control.core_latency_cycles =
1068                        q.control.run_cycle - q.control.first_admission_cycle + b64(1);
1069                }
1070                if q.control.first_capture_seen {
1071                    control.capture_span_cycles =
1072                        q.control.run_cycle - q.control.first_capture_cycle;
1073                } else {
1074                    control.first_capture_seen = true;
1075                    control.first_capture_cycle = q.control.run_cycle;
1076                    control.capture_span_cycles = b64(0);
1077                }
1078            } else {
1079                control.capture_beat = expected_beat + b6(1);
1080            }
1081        } else {
1082            if !new_failure {
1083                new_error_job = q.ingress.beat.job_id;
1084                new_error_detail = expected_beat.resize()
1085                    | (q.ingress.cluster.resize() << 8)
1086                    | (bool_word_kernel(q.ingress.beat.last) << 16);
1087                new_terminal_code = TERMINAL_PROTOCOL_BITS;
1088            }
1089            new_failure = true;
1090            new_error_flags = new_error_flags | b16(ERROR_FRAME);
1091        }
1092    }
1093
1094    if signer_output_handshake {
1095        ingress = input.signer.output;
1096        control.ingress_valid = true;
1097    }
1098
1099    if signer_error_handshake {
1100        scoreboard_events.error = true;
1101        scoreboard_events.error_job = input.signer.error.job_id;
1102        scoreboard_events.error_cluster = input.signer.error.cluster;
1103        control.core_error_count = q.control.core_error_count + b16(1);
1104        if !new_failure {
1105            new_error_job = input.signer.error.job_id;
1106            new_error_detail =
1107                input.signer.error.context.resize() | (input.signer.error.cluster.resize() << 8);
1108            new_terminal_code = TERMINAL_SIGNER_BITS;
1109        }
1110        new_failure = true;
1111        new_error_flags = new_error_flags | b16(ERROR_SIGNER_COMPLETION);
1112    }
1113
1114    let mut next_prefetch_count = q.control.prefetch_count;
1115    let prefetch_push = q.control.read_pending;
1116    if writer_w_handshake && prefetch_push {
1117        if q.control.prefetch_count == b2(1) {
1118            prefetch0 = q.frame_store;
1119            next_prefetch_count = b2(1);
1120        } else if q.control.prefetch_count == b2(2) {
1121            prefetch0 = q.prefetch1;
1122            prefetch1 = q.frame_store;
1123            next_prefetch_count = b2(2);
1124        } else {
1125            if !new_failure {
1126                new_error_job = q.control.writer_job;
1127                new_error_detail = b32(0x2001);
1128                new_terminal_code = TERMINAL_PROTOCOL_BITS;
1129            }
1130            new_failure = true;
1131            new_error_flags = new_error_flags | b16(ERROR_BUFFER);
1132        }
1133    } else if writer_w_handshake {
1134        if q.control.prefetch_count == b2(1) {
1135            next_prefetch_count = b2(0);
1136        } else if q.control.prefetch_count == b2(2) {
1137            prefetch0 = q.prefetch1;
1138            next_prefetch_count = b2(1);
1139        } else {
1140            if !new_failure {
1141                new_error_job = q.control.writer_job;
1142                new_error_detail = b32(0x2002);
1143                new_terminal_code = TERMINAL_PROTOCOL_BITS;
1144            }
1145            new_failure = true;
1146            new_error_flags = new_error_flags | b16(ERROR_BUFFER);
1147        }
1148    } else if prefetch_push {
1149        if q.control.prefetch_count == b2(0) {
1150            prefetch0 = q.frame_store;
1151            next_prefetch_count = b2(1);
1152        } else if q.control.prefetch_count == b2(1) {
1153            prefetch1 = q.frame_store;
1154            next_prefetch_count = b2(2);
1155        } else {
1156            if !new_failure {
1157                new_error_job = q.control.writer_job;
1158                new_error_detail = b32(0x2003);
1159                new_terminal_code = TERMINAL_PROTOCOL_BITS;
1160            }
1161            new_failure = true;
1162            new_error_flags = new_error_flags | b16(ERROR_BUFFER);
1163        }
1164    }
1165    control.prefetch_count = next_prefetch_count;
1166    control.read_pending = false;
1167
1168    if q.control.writer_state == WriterState::Idle
1169        && q.control.run_state == RunState::Execute
1170        && q.control.error_flags == b16(0)
1171        && q.control.frame_queue.count != b2(0)
1172        && !new_failure
1173        && !abort_command
1174    {
1175        control.writer_state = WriterState::Address;
1176        control.writer_slot = q.control.frame_queue.head_slot;
1177        control.writer_job = q.control.frame_queue.head_job;
1178        control.writer_beat = b6(0);
1179        control.read_issue_beat = b6(1);
1180        control.read_pending = true;
1181        control.prefetch_count = b2(0);
1182        control.writer_early_b_drained = false;
1183        frame_store.read_addr = frame_store_address_kernel(q.control.frame_queue.head_slot, b6(0));
1184    } else if q.control.writer_state == WriterState::Address && input.output_hbm.awready {
1185        control.writer_state = WriterState::Data;
1186        control.payload_aw_count = q.control.payload_aw_count + b16(1);
1187    }
1188
1189    if writer_w_handshake {
1190        control.payload_w_count = q.control.payload_w_count + b32(1);
1191        if q.control.writer_beat == b6(34) {
1192            if q.control.writer_early_b_drained {
1193                // The invalid early response was already consumed and
1194                // counted.  Completing WLAST closes the owned payload without
1195                // waiting forever for a second response.
1196                control.writer_state = WriterState::Idle;
1197                control.writer_early_b_drained = false;
1198                dequeue_frame = true;
1199            } else {
1200                control.writer_state = WriterState::Response;
1201            }
1202            control.writer_beat = b6(0);
1203        } else {
1204            control.writer_beat = q.control.writer_beat + b6(1);
1205        }
1206    }
1207
1208    if early_final_b_handshake {
1209        control.payload_b_count = q.control.payload_b_count + b16(1);
1210        control.writer_early_b_drained = true;
1211    }
1212
1213    if output_b_handshake {
1214        control.payload_b_count = q.control.payload_b_count + b16(1);
1215        control.writer_state = WriterState::Idle;
1216        control.prefetch_count = b2(0);
1217        control.read_pending = false;
1218        control.writer_early_b_drained = false;
1219        dequeue_frame = true;
1220        let response_ok =
1221            output_response_is_canonical_kernel(input.output_hbm.bid, input.output_hbm.bresp);
1222        if response_ok {
1223            control.memory_completed_count = q.control.memory_completed_count + b16(1);
1224            control.memory_completion_timing = record_memory_completion_kernel(
1225                q.control.memory_completion_timing,
1226                q.control.run_cycle,
1227                true,
1228            );
1229        } else {
1230            if !new_failure {
1231                new_error_job = q.control.writer_job;
1232                new_error_detail =
1233                    input.output_hbm.bresp.resize() | (input.output_hbm.bid.resize() << 8);
1234                new_terminal_code = TERMINAL_OUTPUT_AXI_BITS;
1235            }
1236            new_failure = true;
1237            new_error_flags = new_error_flags | b16(ERROR_OUTPUT_AXI);
1238        }
1239    }
1240
1241    let writer_still_prefetching = (control.writer_state == WriterState::Address
1242        || control.writer_state == WriterState::Data)
1243        && control.read_issue_beat < b6(35);
1244    if !control.read_pending && writer_still_prefetching && control.prefetch_count < b2(2) {
1245        frame_store.read_addr =
1246            frame_store_address_kernel(control.writer_slot, control.read_issue_beat);
1247        control.read_issue_beat = control.read_issue_beat + b6(1);
1248        control.read_pending = true;
1249    }
1250
1251    let scoreboard_update = scoreboard_update_kernel(q.control.scoreboard, scoreboard_events);
1252    control.scoreboard = scoreboard_update.entries;
1253    if scoreboard_update.fatal {
1254        if !new_failure {
1255            new_error_job = scoreboard_update.failing_job;
1256            new_error_detail = scoreboard_update.detail.resize();
1257            new_terminal_code = TERMINAL_PROTOCOL_BITS;
1258        }
1259        new_failure = true;
1260        new_error_flags = new_error_flags | b16(ERROR_SCOREBOARD);
1261    }
1262
1263    let queue_update = frame_queue_update_kernel(
1264        q.control.frame_queue,
1265        FrameQueueOperation {
1266            dequeue: dequeue_frame,
1267            enqueue: enqueue_frame,
1268            slot: enqueue_slot,
1269            job: enqueue_job,
1270        },
1271    );
1272    control.frame_queue = queue_update.queue;
1273    if queue_update.fatal {
1274        if !new_failure {
1275            new_error_job = enqueue_job;
1276            new_error_detail = b32(0x3001);
1277            new_terminal_code = TERMINAL_PROTOCOL_BITS;
1278        }
1279        new_failure = true;
1280        new_error_flags = new_error_flags | b16(ERROR_BUFFER);
1281    }
1282
1283    if abort_command
1284        && (q.control.run_state == RunState::Validate || q.control.run_state == RunState::Execute)
1285    {
1286        if !new_failure {
1287            new_error_job = b32(0xffff_ffff);
1288            new_error_detail = b32(0x4001);
1289            new_terminal_code = TERMINAL_ABORTED_BITS;
1290        }
1291        new_failure = true;
1292        new_error_flags = new_error_flags | b16(ERROR_SOFT_ABORT);
1293    }
1294
1295    if new_failure {
1296        if q.control.error_flags == b16(0) {
1297            control.first_error_job = new_error_job;
1298            control.first_error_detail = new_error_detail;
1299            control.terminal_code = new_terminal_code;
1300        }
1301        control.error_flags = q.control.error_flags | new_error_flags;
1302        if q.control.run_state == RunState::Execute || q.control.run_state == RunState::Validate {
1303            control.run_state = RunState::AbortDrain;
1304            control.abort_cleaned = false;
1305        }
1306        // Roll back only work that was not already asserted from q-state.
1307        // Address/response states remain owned and therefore continue below.
1308        if q.control.reader_state == ReaderState::Hold {
1309            control.reader_state = ReaderState::Idle;
1310            control.input_job_valid = false;
1311        }
1312        if q.control.writer_state == WriterState::Idle {
1313            control.writer_state = WriterState::Idle;
1314            control.read_pending = false;
1315            control.prefetch_count = b2(0);
1316            control.writer_early_b_drained = false;
1317        }
1318    }
1319
1320    if q.control.run_state == RunState::AbortDrain && !q.control.abort_cleaned {
1321        control.abort_cleaned = true;
1322        control.input_job_valid = false;
1323        control.ingress_valid = false;
1324        control.capture_active = false;
1325        control.capture_beat = b6(0);
1326        if control.reader_state == ReaderState::Hold {
1327            control.reader_state = ReaderState::Idle;
1328        }
1329        if control.writer_state == WriterState::Idle {
1330            control.frame_queue = FrameQueue::default();
1331            control.prefetch_count = b2(0);
1332            control.read_pending = false;
1333            control.writer_early_b_drained = false;
1334        } else {
1335            control.frame_queue = FrameQueue {
1336                count: b2(1),
1337                head_slot: control.writer_slot,
1338                head_job: control.writer_job,
1339                tail_slot: b1(0),
1340                tail_job: b32(0),
1341            };
1342        }
1343    }
1344
1345    if q.control.run_state == RunState::AbortDrain
1346        && control.abort_cleaned
1347        && control.reader_state == ReaderState::Idle
1348        && control.writer_state == WriterState::Idle
1349        && !control.read_pending
1350    {
1351        control.run_state = RunState::CoreReset0;
1352    }
1353    if q.control.run_state == RunState::CoreReset0 {
1354        control.scoreboard = [JobEntry::default(); abi::LIVE_JOB_SLOTS];
1355        control.run_state = RunState::CoreReset1;
1356    }
1357    if q.control.run_state == RunState::CoreReset1 {
1358        control.run_state = RunState::SummaryAw;
1359    }
1360
1361    if q.control.run_state == RunState::Execute && !new_failure {
1362        let batch16: b16 = q.control.config.batch.resize();
1363        let expected_w =
1364            (q.control.config.batch << 5) + (q.control.config.batch << 1) + q.control.config.batch;
1365        let next_occupancy = scoreboard_occupancy_kernel(control.scoreboard);
1366        let closed = control.reader_state == ReaderState::Idle
1367            && !control.input_job_valid
1368            && !control.ingress_valid
1369            && !control.capture_active
1370            && control.frame_queue.count == b2(0)
1371            && control.writer_state == WriterState::Idle
1372            && !control.read_pending
1373            && control.prefetch_count == b2(0)
1374            && next_occupancy == b4(0)
1375            && control.core_admission_count == batch16;
1376        if closed {
1377            let exact = control.input_ar_count == batch16
1378                && control.input_r_count == batch16
1379                && control.core_admission_count == batch16
1380                && control.frame_capture_count == batch16
1381                && control.core_error_count == b16(0)
1382                && control.payload_aw_count == batch16
1383                && control.payload_b_count == batch16
1384                && control.payload_w_count == expected_w
1385                && control.memory_completed_count == batch16
1386                && control.error_flags == b16(0);
1387            if exact {
1388                control.terminal_code = TERMINAL_SUCCESS_BITS;
1389                control.run_state = RunState::SummaryAw;
1390            } else {
1391                control.error_flags = control.error_flags | b16(ERROR_COUNT);
1392                control.terminal_code = TERMINAL_PROTOCOL_BITS;
1393                control.first_error_job = b32(0xffff_ffff);
1394                control.first_error_detail = b32(0x5001);
1395                control.run_state = RunState::AbortDrain;
1396                control.abort_cleaned = false;
1397            }
1398        }
1399    }
1400
1401    if summary_aw_handshake {
1402        control.summary_aw_handshake = true;
1403        control.run_state = RunState::SummaryW;
1404    }
1405    if summary_w_handshake {
1406        control.summary_w_handshake = true;
1407        control.run_state = RunState::SummaryB;
1408    }
1409    if summary_b_handshake {
1410        let summary_ok =
1411            output_response_is_canonical_kernel(input.output_hbm.bid, input.output_hbm.bresp);
1412        control.summary_b_handshake = true;
1413        control.summary_b_canonical = summary_ok;
1414        if !summary_ok {
1415            if q.control.error_flags == b16(0) {
1416                control.first_error_job = b32(0xffff_ffff);
1417                control.first_error_detail =
1418                    input.output_hbm.bresp.resize() | (input.output_hbm.bid.resize() << 8);
1419            }
1420            control.error_flags = q.control.error_flags | b16(ERROR_SUMMARY_AXI);
1421            control.terminal_code = TERMINAL_SUMMARY_AXI_BITS;
1422        }
1423        control.ap_done = true;
1424        control.isr = control.isr | b2(1);
1425        if !summary_ok || q.control.error_flags != b16(0) {
1426            control.isr = control.isr | b2(2);
1427        }
1428        control.run_state = RunState::Done;
1429    }
1430    if q.control.run_state == RunState::SummaryB && premature_summary_b_handshake {
1431        // A malformed state image without both request handshakes cannot own a
1432        // response or issue another request.  Drain the orphan, publish only
1433        // failure evidence, and terminate instead of waiting forever.
1434        control.ap_done = true;
1435        control.isr = control.isr | b2(3);
1436        control.run_state = RunState::Done;
1437    }
1438
1439    let mut seed = [b8(0); 32];
1440    let mut message = [b8(0); 32];
1441    for lane in 0..32 {
1442        seed[lane] = q.input_job.data[lane];
1443        message[lane] = q.input_job.data[32 + lane];
1444    }
1445    let signer = SignerTopInput {
1446        start_valid: signer_start_valid && !resetting,
1447        private_seed: seed,
1448        message,
1449        job_id: q.input_job.job_id,
1450        output_ready: signer_output_ready && !resetting,
1451        error_ready: signer_error_ready && !resetting,
1452    };
1453
1454    let mut input_hbm = idle_read_master_kernel();
1455    input_hbm.rready = (drain_orphan_input_r
1456        || q.control.reader_state == ReaderState::Address
1457        || q.control.reader_state == ReaderState::Response)
1458        && !resetting;
1459    if q.control.reader_state == ReaderState::Address {
1460        input_hbm.arvalid = !resetting;
1461        input_hbm.araddr =
1462            input_job_address_kernel(q.control.config.input_base, q.control.reader_job);
1463    }
1464    if q.control.reader_state == ReaderState::Response {
1465        input_hbm.rready = !resetting;
1466    }
1467
1468    let summary_fields = SummaryFields {
1469        terminal_code: q.control.terminal_code,
1470        nonce: q.control.config.nonce,
1471        payload_cycles: q.control.payload_cycles,
1472        core_latency_cycles: q.control.core_latency_cycles,
1473        capture_span_cycles: q.control.capture_span_cycles,
1474        batch: q.control.config.batch.resize(),
1475        input_ar: q.control.input_ar_count,
1476        input_r: q.control.input_r_count,
1477        core_admissions: q.control.core_admission_count,
1478        frames_captured: q.control.frame_capture_count,
1479        core_errors: q.control.core_error_count,
1480        payload_aw: q.control.payload_aw_count,
1481        payload_b: q.control.payload_b_count,
1482        payload_w: q.control.payload_w_count,
1483        memory_completed: q.control.memory_completed_count,
1484        error_flags: q.control.error_flags,
1485    };
1486    let mut output_hbm = Axi512WriteKernelToMemory::default();
1487    output_hbm.bready = (drain_orphan_output_b
1488        || q.control.writer_state == WriterState::Response
1489        || (q.control.writer_state == WriterState::Data
1490            && q.control.writer_beat == b6(34)
1491            && !q.control.writer_early_b_drained)
1492        // READY may be asserted before VALID.  Keeping it asserted across the
1493        // terminal sequence drains an illegal stale response immediately and
1494        // also permits a compliant zero-latency W/B response.
1495        || summary_sequence_active)
1496        && !resetting;
1497    if q.control.writer_state == WriterState::Address {
1498        output_hbm.awvalid = !resetting;
1499        output_hbm.awaddr =
1500            output_job_address_kernel(q.control.config.output_base, q.control.writer_job);
1501        output_hbm.awlen = OUTPUT_AXI_LEN;
1502    } else if q.control.writer_state == WriterState::Data {
1503        output_hbm.wvalid = q.control.prefetch_count != b2(0) && !resetting;
1504        output_hbm.wdata = q.prefetch0;
1505        output_hbm.wstrb = if q.control.writer_beat == b6(34) {
1506            b64(0x0000_0000_ffff_ffff)
1507        } else {
1508            b64(0xffff_ffff_ffff_ffff)
1509        };
1510        output_hbm.wlast = q.control.writer_beat == b6(34);
1511    } else if q.control.writer_state == WriterState::Response {
1512        output_hbm.bready = !resetting;
1513    } else if q.control.run_state == RunState::SummaryAw {
1514        output_hbm.awvalid = !resetting;
1515        output_hbm.awaddr = q.control.config.summary_base;
1516        output_hbm.awlen = b8(0);
1517    } else if q.control.run_state == RunState::SummaryW {
1518        output_hbm.wvalid = !resetting;
1519        output_hbm.wdata = summary_line_kernel(summary_fields);
1520        output_hbm.wstrb = b64(0xffff_ffff_ffff_ffff);
1521        output_hbm.wlast = true;
1522    } else if q.control.run_state == RunState::SummaryB {
1523        output_hbm.bready = !resetting;
1524    }
1525    output_hbm.awid = b1(0);
1526    output_hbm.awsize = AXI_SIZE_64_BYTES;
1527    output_hbm.awburst = AXI_BURST_INCR;
1528    output_hbm.awlock = AXI_LOCK_NORMAL;
1529    output_hbm.awcache = AXI_CACHE_NORMAL;
1530    output_hbm.awprot = b3(0);
1531    output_hbm.awqos = b4(0);
1532    output_hbm.awregion = b4(0);
1533
1534    let output = HbmShellOutput {
1535        control: AxiLiteKernelToHost {
1536            awready: axil_awready && !resetting,
1537            wready: axil_wready && !resetting,
1538            bvalid: q.control.axil_bvalid && !resetting,
1539            bresp: q.control.axil_bresp,
1540            arready: axil_arready && !resetting,
1541            rvalid: q.control.axil_rvalid && !resetting,
1542            rdata: q.control.axil_rdata,
1543            rresp: q.control.axil_rresp,
1544        },
1545        input_hbm,
1546        output_hbm,
1547        signer,
1548        signer_soft_reset: !resetting
1549            && (q.control.run_state == RunState::CoreReset0
1550                || q.control.run_state == RunState::CoreReset1),
1551        interrupt: !resetting && q.control.gier && ((q.control.ier & q.control.isr) != b2(0)),
1552    };
1553
1554    if resetting {
1555        control = ShellControl::default();
1556        frame_store = FrameStoreInput::default();
1557    }
1558    let d = U280HbmShellD {
1559        control,
1560        input_job,
1561        ingress,
1562        prefetch0,
1563        prefetch1,
1564        frame_store,
1565    };
1566    (output, d)
1567}
1568
1569#[cfg(test)]
1570mod tests {
1571    use super::*;
1572
1573    fn pending_abort(mut control: ShellControl) -> ShellControl {
1574        control.run_state = RunState::Execute;
1575        control.axil_aw_pending = true;
1576        control.axil_awaddr = b8(abi::register::COMMAND);
1577        control.axil_w_pending = true;
1578        control.axil_wdata = b32(1);
1579        control.axil_wstrb = b4(1);
1580        control
1581    }
1582
1583    fn shell_q(control: ShellControl) -> U280HbmShellQ {
1584        U280HbmShellQ {
1585            control,
1586            input_job: InputJob::default(),
1587            ingress: SignerOutputBeat::default(),
1588            prefetch0: [b8(0); 64],
1589            prefetch1: [b8(0); 64],
1590            frame_store: [b8(0); 64],
1591        }
1592    }
1593
1594    fn active_clock() -> ClockReset {
1595        clock_reset(clock(false), reset(false))
1596    }
1597
1598    fn status_for(control: ShellControl) -> b32 {
1599        last_status_kernel(
1600            control.terminal_code,
1601            control.error_flags,
1602            SummaryWriteStatus {
1603                aw: control.summary_aw_handshake,
1604                w: control.summary_w_handshake,
1605                b: control.summary_b_handshake,
1606                b_canonical: control.summary_b_canonical,
1607            },
1608        )
1609    }
1610
1611    fn summary_response_input(
1612        awready: bool,
1613        wready: bool,
1614        bvalid: bool,
1615        bid: b1,
1616        bresp: b2,
1617    ) -> HbmShellInput {
1618        HbmShellInput {
1619            output_hbm: Axi512WriteMemoryToKernel {
1620                awready,
1621                wready,
1622                bvalid,
1623                bid,
1624                bresp,
1625            },
1626            ..HbmShellInput::default()
1627        }
1628    }
1629
1630    #[test]
1631    fn axil_aw_before_w_cannot_turn_a_busy_start_into_a_stale_idle_start() {
1632        let mut active = ShellControl::default();
1633        active.run_state = RunState::Execute;
1634        active.config.batch = b32(1);
1635        active.reader_state = ReaderState::Hold;
1636        active.input_job_valid = true;
1637        let (_output, aw_d) = u280_hbm_shell_kernel(
1638            active_clock(),
1639            HbmShellInput {
1640                control: AxiLiteHostToKernel {
1641                    awvalid: true,
1642                    awaddr: b8(abi::register::AP_CTRL),
1643                    ..AxiLiteHostToKernel::default()
1644                },
1645                ..HbmShellInput::default()
1646            },
1647            shell_q(active),
1648        );
1649        assert!(aw_d.control.axil_aw_pending);
1650        assert!(aw_d.control.axil_aw_locked_at_accept);
1651
1652        let mut crossed_boundary = aw_d.control;
1653        crossed_boundary.run_state = RunState::Idle;
1654        let (_output, w_d) = u280_hbm_shell_kernel(
1655            active_clock(),
1656            HbmShellInput {
1657                control: AxiLiteHostToKernel {
1658                    wvalid: true,
1659                    wdata: b32(1),
1660                    wstrb: b4(1),
1661                    ..AxiLiteHostToKernel::default()
1662                },
1663                ..HbmShellInput::default()
1664            },
1665            shell_q(crossed_boundary),
1666        );
1667        assert!(w_d.control.axil_w_pending);
1668        assert!(!w_d.control.axil_w_locked_at_accept);
1669
1670        let (_output, rejected) = u280_hbm_shell_kernel(
1671            active_clock(),
1672            HbmShellInput::default(),
1673            shell_q(w_d.control),
1674        );
1675        assert!(rejected.control.axil_bvalid);
1676        assert_eq!(rejected.control.axil_bresp, AXI_RESP_SLVERR);
1677        assert_eq!(rejected.control.run_state, RunState::Idle);
1678    }
1679
1680    #[test]
1681    fn axil_w_before_aw_retains_the_busy_configuration_lock() {
1682        let mut active = ShellControl::default();
1683        active.run_state = RunState::Execute;
1684        active.config.batch = b32(7);
1685        active.reader_state = ReaderState::Hold;
1686        active.input_job_valid = true;
1687        let (_output, w_d) = u280_hbm_shell_kernel(
1688            active_clock(),
1689            HbmShellInput {
1690                control: AxiLiteHostToKernel {
1691                    wvalid: true,
1692                    wdata: b32(99),
1693                    wstrb: b4(0xf),
1694                    ..AxiLiteHostToKernel::default()
1695                },
1696                ..HbmShellInput::default()
1697            },
1698            shell_q(active),
1699        );
1700        assert!(w_d.control.axil_w_locked_at_accept);
1701
1702        let mut crossed_boundary = w_d.control;
1703        crossed_boundary.run_state = RunState::Idle;
1704        let (_output, aw_d) = u280_hbm_shell_kernel(
1705            active_clock(),
1706            HbmShellInput {
1707                control: AxiLiteHostToKernel {
1708                    awvalid: true,
1709                    awaddr: b8(abi::register::BATCH),
1710                    ..AxiLiteHostToKernel::default()
1711                },
1712                ..HbmShellInput::default()
1713            },
1714            shell_q(crossed_boundary),
1715        );
1716        let (_output, rejected) = u280_hbm_shell_kernel(
1717            active_clock(),
1718            HbmShellInput::default(),
1719            shell_q(aw_d.control),
1720        );
1721        assert_eq!(rejected.control.axil_bresp, AXI_RESP_SLVERR);
1722        assert_eq!(rejected.control.config.batch, b32(7));
1723    }
1724
1725    #[test]
1726    fn axil_simultaneous_idle_pair_commits_but_busy_pair_remains_locked() {
1727        let idle = ShellControl::default();
1728        let pair = HbmShellInput {
1729            control: AxiLiteHostToKernel {
1730                awvalid: true,
1731                awaddr: b8(abi::register::BATCH),
1732                wvalid: true,
1733                wdata: b32(123),
1734                wstrb: b4(0xf),
1735                ..AxiLiteHostToKernel::default()
1736            },
1737            ..HbmShellInput::default()
1738        };
1739        let (_output, captured) = u280_hbm_shell_kernel(active_clock(), pair, shell_q(idle));
1740        assert!(!captured.control.axil_aw_locked_at_accept);
1741        assert!(!captured.control.axil_w_locked_at_accept);
1742        let (_output, committed) = u280_hbm_shell_kernel(
1743            active_clock(),
1744            HbmShellInput::default(),
1745            shell_q(captured.control),
1746        );
1747        assert_eq!(committed.control.axil_bresp, AXI_RESP_OKAY);
1748        assert_eq!(committed.control.config.batch, b32(123));
1749
1750        let mut active = ShellControl::default();
1751        active.run_state = RunState::Execute;
1752        active.config.batch = b32(1);
1753        active.reader_state = ReaderState::Hold;
1754        active.input_job_valid = true;
1755        let (_output, captured_busy) = u280_hbm_shell_kernel(active_clock(), pair, shell_q(active));
1756        assert!(captured_busy.control.axil_aw_locked_at_accept);
1757        assert!(captured_busy.control.axil_w_locked_at_accept);
1758        let mut crossed_boundary = captured_busy.control;
1759        crossed_boundary.run_state = RunState::Idle;
1760        let (_output, rejected) = u280_hbm_shell_kernel(
1761            active_clock(),
1762            HbmShellInput::default(),
1763            shell_q(crossed_boundary),
1764        );
1765        assert_eq!(rejected.control.axil_bresp, AXI_RESP_SLVERR);
1766        assert_eq!(rejected.control.config.batch, b32(1));
1767    }
1768
1769    #[test]
1770    fn axil_abort_intent_is_executed_while_active_and_rejected_after_boundary() {
1771        let mut active = ShellControl::default();
1772        active.run_state = RunState::Execute;
1773        active.config.batch = b32(1);
1774        active.reader_state = ReaderState::Hold;
1775        active.input_job_valid = true;
1776        let abort_pair = HbmShellInput {
1777            control: AxiLiteHostToKernel {
1778                awvalid: true,
1779                awaddr: b8(abi::register::COMMAND),
1780                wvalid: true,
1781                wdata: b32(1),
1782                wstrb: b4(1),
1783                ..AxiLiteHostToKernel::default()
1784            },
1785            ..HbmShellInput::default()
1786        };
1787        let (_output, captured) =
1788            u280_hbm_shell_kernel(active_clock(), abort_pair, shell_q(active));
1789        let (_output, aborted) = u280_hbm_shell_kernel(
1790            active_clock(),
1791            HbmShellInput::default(),
1792            shell_q(captured.control),
1793        );
1794        assert_eq!(aborted.control.axil_bresp, AXI_RESP_OKAY);
1795        assert_eq!(aborted.control.run_state, RunState::AbortDrain);
1796        assert_ne!(aborted.control.error_flags & b16(ERROR_SOFT_ABORT), b16(0));
1797
1798        let mut split_active = active;
1799        let (_output, aw_d) = u280_hbm_shell_kernel(
1800            active_clock(),
1801            HbmShellInput {
1802                control: AxiLiteHostToKernel {
1803                    awvalid: true,
1804                    awaddr: b8(abi::register::COMMAND),
1805                    ..AxiLiteHostToKernel::default()
1806                },
1807                ..HbmShellInput::default()
1808            },
1809            shell_q(split_active),
1810        );
1811        split_active = aw_d.control;
1812        split_active.run_state = RunState::Idle;
1813        let (_output, w_d) = u280_hbm_shell_kernel(
1814            active_clock(),
1815            HbmShellInput {
1816                control: AxiLiteHostToKernel {
1817                    wvalid: true,
1818                    wdata: b32(1),
1819                    wstrb: b4(1),
1820                    ..AxiLiteHostToKernel::default()
1821                },
1822                ..HbmShellInput::default()
1823            },
1824            shell_q(split_active),
1825        );
1826        let (_output, stale) = u280_hbm_shell_kernel(
1827            active_clock(),
1828            HbmShellInput::default(),
1829            shell_q(w_d.control),
1830        );
1831        assert_eq!(stale.control.axil_bresp, AXI_RESP_SLVERR);
1832        assert_eq!(stale.control.run_state, RunState::Idle);
1833        assert_eq!(stale.control.error_flags & b16(ERROR_SOFT_ABORT), b16(0));
1834    }
1835
1836    #[test]
1837    fn same_cycle_abort_suppresses_signer_admission_and_next_input_ar() {
1838        let mut control = pending_abort(ShellControl::default());
1839        control.reader_state = ReaderState::Hold;
1840        control.input_job_valid = true;
1841        control.config.batch = b32(2);
1842        let mut q = shell_q(control);
1843        q.input_job.job_id = b32(0);
1844        let input = HbmShellInput {
1845            signer: SignerTopOutput {
1846                start_ready: true,
1847                ..SignerTopOutput::default()
1848            },
1849            ..HbmShellInput::default()
1850        };
1851        let (output, d) = u280_hbm_shell_kernel(active_clock(), input, q);
1852        assert!(!output.signer.start_valid);
1853        assert_eq!(d.control.core_admission_count, b16(0));
1854        assert_ne!(d.control.reader_state, ReaderState::Address);
1855        assert_eq!(d.control.run_state, RunState::AbortDrain);
1856    }
1857
1858    #[test]
1859    fn same_cycle_abort_discards_held_ingress_without_starting_a_frame() {
1860        let mut control = pending_abort(ShellControl::default());
1861        control.ingress_valid = true;
1862        let mut q = shell_q(control);
1863        q.ingress = SignerOutputBeat {
1864            beat: wots_rhdl::LaneLocalBeat {
1865                data: [b8(0x5a); 64],
1866                keep: b64(0xffff_ffff_ffff_ffff),
1867                last: false,
1868                job_id: b32(7),
1869            },
1870            cluster: b2(0),
1871        };
1872        let (output, d) = u280_hbm_shell_kernel(active_clock(), HbmShellInput::default(), q);
1873        assert!(!output.signer.output_ready);
1874        assert!(!d.frame_store.write.enable);
1875        assert!(!d.control.capture_active);
1876        assert_eq!(d.control.frame_queue.count, b2(0));
1877        assert_eq!(d.control.run_state, RunState::AbortDrain);
1878    }
1879
1880    #[test]
1881    fn same_cycle_abort_never_commits_an_idle_writer() {
1882        let mut control = pending_abort(ShellControl::default());
1883        control.frame_queue = FrameQueue {
1884            count: b2(1),
1885            head_slot: b1(0),
1886            head_job: b32(9),
1887            ..FrameQueue::default()
1888        };
1889        let q = shell_q(control);
1890        let (output, d) = u280_hbm_shell_kernel(active_clock(), HbmShellInput::default(), q);
1891        assert!(!output.output_hbm.awvalid);
1892        assert_eq!(d.control.writer_state, WriterState::Idle);
1893        assert_eq!(d.control.payload_aw_count, b16(0));
1894        assert_eq!(d.control.run_state, RunState::AbortDrain);
1895    }
1896
1897    #[test]
1898    fn orphan_input_response_is_drained_and_fails_closed() {
1899        let mut control = ShellControl::default();
1900        control.run_state = RunState::Execute;
1901        let q = shell_q(control);
1902        let input = HbmShellInput {
1903            input_hbm: Axi512ReadMemoryToKernel {
1904                rvalid: true,
1905                rlast: true,
1906                ..Axi512ReadMemoryToKernel::default()
1907            },
1908            ..HbmShellInput::default()
1909        };
1910        let (output, d) = u280_hbm_shell_kernel(active_clock(), input, q);
1911        assert!(output.input_hbm.rready);
1912        assert_eq!(d.control.run_state, RunState::AbortDrain);
1913        assert_ne!(d.control.error_flags & b16(ERROR_INPUT_AXI), b16(0));
1914    }
1915
1916    #[test]
1917    fn orphan_output_response_is_drained_and_fails_closed() {
1918        let mut control = ShellControl::default();
1919        control.run_state = RunState::Execute;
1920        let q = shell_q(control);
1921        let input = HbmShellInput {
1922            output_hbm: Axi512WriteMemoryToKernel {
1923                bvalid: true,
1924                ..Axi512WriteMemoryToKernel::default()
1925            },
1926            ..HbmShellInput::default()
1927        };
1928        let (output, d) = u280_hbm_shell_kernel(active_clock(), input, q);
1929        assert!(output.output_hbm.bready);
1930        assert_eq!(d.control.run_state, RunState::AbortDrain);
1931        assert_ne!(d.control.error_flags & b16(ERROR_OUTPUT_AXI), b16(0));
1932    }
1933
1934    #[test]
1935    fn input_address_and_canonical_response_can_transfer_in_one_cycle() {
1936        let mut control = ShellControl::default();
1937        control.run_state = RunState::Execute;
1938        control.config.batch = b32(1);
1939        control.reader_state = ReaderState::Address;
1940        control.reader_job = b32(0);
1941        let data = [b8(0x5a); 64];
1942        let input = HbmShellInput {
1943            input_hbm: Axi512ReadMemoryToKernel {
1944                arready: true,
1945                rvalid: true,
1946                rdata: data,
1947                rid: b1(0),
1948                rresp: AXI_RESP_OKAY,
1949                rlast: true,
1950            },
1951            ..HbmShellInput::default()
1952        };
1953        let (output, d) = u280_hbm_shell_kernel(active_clock(), input, shell_q(control));
1954        assert!(output.input_hbm.arvalid);
1955        assert!(output.input_hbm.rready);
1956        assert_eq!(d.control.input_ar_count, b16(1));
1957        assert_eq!(d.control.input_r_count, b16(1));
1958        assert_eq!(d.control.reader_state, ReaderState::Hold);
1959        assert!(d.control.input_job_valid);
1960        assert_eq!(d.input_job.data, data);
1961        assert_eq!(d.control.error_flags & b16(ERROR_INPUT_AXI), b16(0));
1962    }
1963
1964    #[test]
1965    fn input_response_without_arready_is_prearmed_drained_and_fails_closed() {
1966        let mut control = ShellControl::default();
1967        control.run_state = RunState::Execute;
1968        control.config.batch = b32(1);
1969        control.reader_state = ReaderState::Address;
1970        control.reader_job = b32(0);
1971        let input = HbmShellInput {
1972            input_hbm: Axi512ReadMemoryToKernel {
1973                arready: false,
1974                rvalid: true,
1975                rid: b1(0),
1976                rresp: AXI_RESP_OKAY,
1977                rlast: true,
1978                ..Axi512ReadMemoryToKernel::default()
1979            },
1980            ..HbmShellInput::default()
1981        };
1982        let (output, d) = u280_hbm_shell_kernel(active_clock(), input, shell_q(control));
1983        assert!(output.input_hbm.arvalid);
1984        assert!(output.input_hbm.rready, "RREADY is pre-armed from q-state");
1985        assert_eq!(d.control.input_ar_count, b16(0));
1986        assert_eq!(d.control.input_r_count, b16(0));
1987        assert_eq!(d.control.reader_state, ReaderState::Idle);
1988        assert_eq!(d.control.run_state, RunState::AbortDrain);
1989        assert_ne!(d.control.error_flags & b16(ERROR_INPUT_AXI), b16(0));
1990    }
1991
1992    #[test]
1993    fn final_payload_w_and_canonical_b_can_retire_in_one_cycle() {
1994        let mut control = ShellControl::default();
1995        control.run_state = RunState::Execute;
1996        control.config.batch = b32(1);
1997        control.writer_state = WriterState::Data;
1998        control.writer_job = b32(0);
1999        control.writer_beat = b6(34);
2000        control.prefetch_count = b2(1);
2001        control.payload_aw_count = b16(1);
2002        control.frame_queue = FrameQueue {
2003            count: b2(1),
2004            head_slot: b1(0),
2005            head_job: b32(0),
2006            ..FrameQueue::default()
2007        };
2008        control.run_cycle = b64(91);
2009        let input = HbmShellInput {
2010            output_hbm: Axi512WriteMemoryToKernel {
2011                wready: true,
2012                bvalid: true,
2013                bid: b1(0),
2014                bresp: AXI_RESP_OKAY,
2015                ..Axi512WriteMemoryToKernel::default()
2016            },
2017            ..HbmShellInput::default()
2018        };
2019        let (output, d) = u280_hbm_shell_kernel(active_clock(), input, shell_q(control));
2020        assert!(output.output_hbm.wvalid);
2021        assert!(output.output_hbm.wlast);
2022        assert!(output.output_hbm.bready);
2023        assert_eq!(d.control.writer_state, WriterState::Idle);
2024        assert_eq!(d.control.payload_w_count, b32(1));
2025        assert_eq!(d.control.payload_b_count, b16(1));
2026        assert_eq!(d.control.memory_completed_count, b16(1));
2027        assert_eq!(d.control.frame_queue.count, b2(0));
2028        assert_eq!(d.control.error_flags & b16(ERROR_OUTPUT_AXI), b16(0));
2029    }
2030
2031    #[test]
2032    fn final_payload_b_without_wready_is_prearmed_drained_and_does_not_deadlock() {
2033        let mut control = ShellControl::default();
2034        control.run_state = RunState::Execute;
2035        control.config.batch = b32(1);
2036        control.writer_state = WriterState::Data;
2037        control.writer_job = b32(0);
2038        control.writer_beat = b6(34);
2039        control.prefetch_count = b2(1);
2040        control.frame_queue = FrameQueue {
2041            count: b2(1),
2042            head_slot: b1(0),
2043            head_job: b32(0),
2044            ..FrameQueue::default()
2045        };
2046        let early_b = HbmShellInput {
2047            output_hbm: Axi512WriteMemoryToKernel {
2048                wready: false,
2049                bvalid: true,
2050                bid: b1(0),
2051                bresp: AXI_RESP_OKAY,
2052                ..Axi512WriteMemoryToKernel::default()
2053            },
2054            ..HbmShellInput::default()
2055        };
2056        let (early_output, early_d) =
2057            u280_hbm_shell_kernel(active_clock(), early_b, shell_q(control));
2058        assert!(early_output.output_hbm.wvalid);
2059        assert!(early_output.output_hbm.wlast);
2060        assert!(
2061            early_output.output_hbm.bready,
2062            "BREADY is pre-armed from q-state, independent of WREADY"
2063        );
2064        assert_eq!(early_d.control.run_state, RunState::AbortDrain);
2065        assert_eq!(early_d.control.writer_state, WriterState::Data);
2066        assert!(early_d.control.writer_early_b_drained);
2067        assert_eq!(early_d.control.payload_b_count, b16(1));
2068        assert_eq!(early_d.control.memory_completed_count, b16(0));
2069        assert_ne!(early_d.control.error_flags & b16(ERROR_OUTPUT_AXI), b16(0));
2070
2071        let (final_output, final_d) = u280_hbm_shell_kernel(
2072            active_clock(),
2073            HbmShellInput {
2074                output_hbm: Axi512WriteMemoryToKernel {
2075                    wready: true,
2076                    ..Axi512WriteMemoryToKernel::default()
2077                },
2078                ..HbmShellInput::default()
2079            },
2080            shell_q(early_d.control),
2081        );
2082        assert!(final_output.output_hbm.wvalid);
2083        assert!(final_output.output_hbm.wlast);
2084        assert!(!final_output.output_hbm.bready);
2085        assert_eq!(final_d.control.writer_state, WriterState::Idle);
2086        assert!(!final_d.control.writer_early_b_drained);
2087        assert_eq!(final_d.control.frame_queue.count, b2(0));
2088        assert_eq!(final_d.control.payload_w_count, b32(1));
2089        assert_eq!(final_d.control.payload_b_count, b16(1));
2090    }
2091
2092    #[test]
2093    fn payload_b_before_wlast_fails_closed_without_accepting_the_response() {
2094        let mut control = ShellControl::default();
2095        control.run_state = RunState::Execute;
2096        control.config.batch = b32(1);
2097        control.writer_state = WriterState::Data;
2098        control.writer_job = b32(0);
2099        control.writer_beat = b6(33);
2100        control.prefetch_count = b2(1);
2101        control.frame_queue = FrameQueue {
2102            count: b2(1),
2103            head_slot: b1(0),
2104            head_job: b32(0),
2105            ..FrameQueue::default()
2106        };
2107        let input = HbmShellInput {
2108            output_hbm: Axi512WriteMemoryToKernel {
2109                bvalid: true,
2110                bid: b1(0),
2111                bresp: AXI_RESP_OKAY,
2112                ..Axi512WriteMemoryToKernel::default()
2113            },
2114            ..HbmShellInput::default()
2115        };
2116        let (output, d) = u280_hbm_shell_kernel(active_clock(), input, shell_q(control));
2117        assert!(!output.output_hbm.bready);
2118        assert_eq!(d.control.payload_b_count, b16(0));
2119        assert_eq!(d.control.run_state, RunState::AbortDrain);
2120        assert_ne!(d.control.error_flags & b16(ERROR_OUTPUT_AXI), b16(0));
2121    }
2122
2123    #[test]
2124    fn held_canonical_b_from_summary_aw_is_drained_and_never_reports_success() {
2125        let mut control = ShellControl::default();
2126        control.run_state = RunState::SummaryAw;
2127        control.config.summary_base = b64(0x0000_0003_0000_0000);
2128
2129        let (first_output, first_d) = u280_hbm_shell_kernel(
2130            active_clock(),
2131            summary_response_input(false, false, true, b1(0), AXI_RESP_OKAY),
2132            shell_q(control),
2133        );
2134        assert!(first_output.output_hbm.awvalid);
2135        assert!(first_output.output_hbm.bready);
2136        assert_eq!(first_d.control.run_state, RunState::SummaryAw);
2137        assert!(!first_d.control.summary_aw_handshake);
2138        assert!(!first_d.control.summary_w_handshake);
2139        assert!(!first_d.control.summary_b_handshake);
2140        assert_ne!(first_d.control.error_flags & b16(ERROR_SUMMARY_AXI), b16(0));
2141        assert_eq!(first_d.control.terminal_code, TERMINAL_SUMMARY_AXI_BITS);
2142        assert!(!crate::control::last_status_is_host_success_kernel(
2143            status_for(first_d.control)
2144        ));
2145
2146        let (aw_output, aw_d) = u280_hbm_shell_kernel(
2147            active_clock(),
2148            summary_response_input(true, false, true, b1(0), AXI_RESP_OKAY),
2149            shell_q(first_d.control),
2150        );
2151        assert!(aw_output.output_hbm.awvalid);
2152        assert!(aw_output.output_hbm.bready);
2153        assert_eq!(aw_d.control.run_state, RunState::SummaryW);
2154        assert!(aw_d.control.summary_aw_handshake);
2155        assert!(!aw_d.control.summary_w_handshake);
2156        assert!(!aw_d.control.summary_b_handshake);
2157
2158        let (stalled_output, stalled_d) = u280_hbm_shell_kernel(
2159            active_clock(),
2160            summary_response_input(false, false, true, b1(0), AXI_RESP_OKAY),
2161            shell_q(aw_d.control),
2162        );
2163        assert!(stalled_output.output_hbm.wvalid);
2164        assert!(stalled_output.output_hbm.bready);
2165        assert_eq!(stalled_d.control.run_state, RunState::SummaryW);
2166        assert!(!stalled_d.control.summary_w_handshake);
2167        assert!(!stalled_d.control.summary_b_handshake);
2168
2169        let (close_output, close_d) = u280_hbm_shell_kernel(
2170            active_clock(),
2171            summary_response_input(false, true, true, b1(0), AXI_RESP_OKAY),
2172            shell_q(stalled_d.control),
2173        );
2174        assert!(close_output.output_hbm.wvalid);
2175        assert!(close_output.output_hbm.bready);
2176        assert_eq!(close_d.control.run_state, RunState::Done);
2177        assert!(close_d.control.summary_w_handshake);
2178        assert!(close_d.control.summary_b_handshake);
2179        assert!(close_d.control.summary_b_canonical);
2180        assert!(close_d.control.ap_done);
2181        assert_ne!(close_d.control.error_flags & b16(ERROR_SUMMARY_AXI), b16(0));
2182        assert_eq!(close_d.control.terminal_code, TERMINAL_SUMMARY_AXI_BITS);
2183        assert!(!crate::control::last_status_is_host_success_kernel(
2184            status_for(close_d.control)
2185        ));
2186    }
2187
2188    #[test]
2189    fn held_canonical_b_during_stalled_summary_w_is_sticky_failure() {
2190        let mut control = ShellControl::default();
2191        control.run_state = RunState::SummaryW;
2192        control.summary_aw_handshake = true;
2193
2194        let (first_output, first_d) = u280_hbm_shell_kernel(
2195            active_clock(),
2196            summary_response_input(false, false, true, b1(0), AXI_RESP_OKAY),
2197            shell_q(control),
2198        );
2199        assert!(first_output.output_hbm.wvalid);
2200        assert!(first_output.output_hbm.bready);
2201        assert_eq!(first_d.control.run_state, RunState::SummaryW);
2202        assert!(!first_d.control.summary_w_handshake);
2203        assert!(!first_d.control.summary_b_handshake);
2204        assert_ne!(first_d.control.error_flags & b16(ERROR_SUMMARY_AXI), b16(0));
2205
2206        let (held_output, held_d) = u280_hbm_shell_kernel(
2207            active_clock(),
2208            summary_response_input(false, false, true, b1(0), AXI_RESP_OKAY),
2209            shell_q(first_d.control),
2210        );
2211        assert!(held_output.output_hbm.wvalid);
2212        assert!(held_output.output_hbm.bready);
2213        assert_eq!(held_d.control.run_state, RunState::SummaryW);
2214        assert!(!held_d.control.summary_w_handshake);
2215        assert!(!held_d.control.summary_b_handshake);
2216
2217        let (w_output, w_d) = u280_hbm_shell_kernel(
2218            active_clock(),
2219            summary_response_input(false, true, false, b1(0), AXI_RESP_OKAY),
2220            shell_q(held_d.control),
2221        );
2222        assert!(w_output.output_hbm.wvalid);
2223        assert!(w_output.output_hbm.bready);
2224        assert_eq!(w_d.control.run_state, RunState::SummaryB);
2225        assert!(w_d.control.summary_w_handshake);
2226        assert!(!w_d.control.summary_b_handshake);
2227
2228        let (b_output, b_d) = u280_hbm_shell_kernel(
2229            active_clock(),
2230            summary_response_input(false, false, true, b1(0), AXI_RESP_OKAY),
2231            shell_q(w_d.control),
2232        );
2233        assert!(b_output.output_hbm.bready);
2234        assert_eq!(b_d.control.run_state, RunState::Done);
2235        assert!(b_d.control.summary_b_handshake);
2236        assert!(b_d.control.summary_b_canonical);
2237        assert_ne!(b_d.control.error_flags & b16(ERROR_SUMMARY_AXI), b16(0));
2238        assert_eq!(b_d.control.terminal_code, TERMINAL_SUMMARY_AXI_BITS);
2239        assert!(!crate::control::last_status_is_host_success_kernel(
2240            status_for(b_d.control)
2241        ));
2242    }
2243
2244    #[test]
2245    fn premature_summary_b_rejects_wrong_id_and_non_okay_responses() {
2246        for run_state in [RunState::SummaryAw, RunState::SummaryW] {
2247            for (bid, bresp) in [
2248                (b1(1), AXI_RESP_OKAY),
2249                (b1(0), AXI_RESP_SLVERR),
2250                (b1(0), AXI_RESP_DECERR),
2251                (b1(1), AXI_RESP_SLVERR),
2252            ] {
2253                let mut control = ShellControl::default();
2254                control.run_state = run_state;
2255                control.summary_aw_handshake = run_state == RunState::SummaryW;
2256                let (output, d) = u280_hbm_shell_kernel(
2257                    active_clock(),
2258                    summary_response_input(false, false, true, bid, bresp),
2259                    shell_q(control),
2260                );
2261                assert_eq!(output.output_hbm.awvalid, run_state == RunState::SummaryAw);
2262                assert_eq!(output.output_hbm.wvalid, run_state == RunState::SummaryW);
2263                assert!(output.output_hbm.bready);
2264                assert_eq!(d.control.run_state, run_state);
2265                assert!(!d.control.summary_b_handshake);
2266                assert!(!d.control.summary_b_canonical);
2267                assert_eq!(d.control.terminal_code, TERMINAL_SUMMARY_AXI_BITS);
2268                assert_ne!(d.control.error_flags & b16(ERROR_SUMMARY_AXI), b16(0));
2269                assert_eq!(
2270                    d.control.first_error_detail,
2271                    b32(0x6003) | (bresp.resize() << 16) | (bid.resize() << 24)
2272                );
2273                assert!(!crate::control::last_status_is_host_success_kernel(
2274                    status_for(d.control)
2275                ));
2276            }
2277        }
2278    }
2279
2280    #[test]
2281    fn legal_same_cycle_summary_w_and_b_retire_as_success() {
2282        let mut control = ShellControl::default();
2283        control.run_state = RunState::SummaryW;
2284        control.summary_aw_handshake = true;
2285        let (output, d) = u280_hbm_shell_kernel(
2286            active_clock(),
2287            summary_response_input(false, true, true, b1(0), AXI_RESP_OKAY),
2288            shell_q(control),
2289        );
2290        assert!(output.output_hbm.wvalid);
2291        assert!(output.output_hbm.wlast);
2292        assert!(output.output_hbm.bready);
2293        assert_eq!(d.control.run_state, RunState::Done);
2294        assert!(d.control.summary_aw_handshake);
2295        assert!(d.control.summary_w_handshake);
2296        assert!(d.control.summary_b_handshake);
2297        assert!(d.control.summary_b_canonical);
2298        assert_eq!(d.control.error_flags, b16(0));
2299        assert_eq!(d.control.terminal_code, TERMINAL_SUCCESS_BITS);
2300        assert!(crate::control::last_status_is_host_success_kernel(
2301            status_for(d.control)
2302        ));
2303    }
2304
2305    #[test]
2306    fn premature_b_during_core_reset_is_drained_before_summary_ownership() {
2307        let mut control = ShellControl::default();
2308        control.run_state = RunState::CoreReset0;
2309        let (output, first_d) = u280_hbm_shell_kernel(
2310            active_clock(),
2311            summary_response_input(false, false, true, b1(0), AXI_RESP_OKAY),
2312            shell_q(control),
2313        );
2314        assert!(output.output_hbm.bready);
2315        assert_eq!(first_d.control.run_state, RunState::CoreReset1);
2316        assert!(!first_d.control.summary_aw_handshake);
2317        assert!(!first_d.control.summary_w_handshake);
2318        assert!(!first_d.control.summary_b_handshake);
2319        assert_ne!(first_d.control.error_flags & b16(ERROR_SUMMARY_AXI), b16(0));
2320
2321        let (_output, second_d) = u280_hbm_shell_kernel(
2322            active_clock(),
2323            HbmShellInput::default(),
2324            shell_q(first_d.control),
2325        );
2326        assert_eq!(second_d.control.run_state, RunState::SummaryAw);
2327        assert!(!crate::control::last_status_is_host_success_kernel(
2328            status_for(second_d.control)
2329        ));
2330    }
2331
2332    #[test]
2333    fn summary_b_without_request_ownership_drains_and_terminates_failed() {
2334        let mut control = ShellControl::default();
2335        control.run_state = RunState::SummaryB;
2336        let (output, d) = u280_hbm_shell_kernel(
2337            active_clock(),
2338            summary_response_input(false, false, true, b1(0), AXI_RESP_OKAY),
2339            shell_q(control),
2340        );
2341        assert!(output.output_hbm.bready);
2342        assert_eq!(d.control.run_state, RunState::Done);
2343        assert!(d.control.ap_done);
2344        assert!(!d.control.summary_aw_handshake);
2345        assert!(!d.control.summary_w_handshake);
2346        assert!(!d.control.summary_b_handshake);
2347        assert!(!d.control.summary_b_canonical);
2348        assert_ne!(d.control.error_flags & b16(ERROR_SUMMARY_AXI), b16(0));
2349        assert_eq!(d.control.terminal_code, TERMINAL_SUMMARY_AXI_BITS);
2350        assert!(!crate::control::last_status_is_host_success_kernel(
2351            status_for(d.control)
2352        ));
2353    }
2354
2355    #[test]
2356    fn zero_batch_performs_only_the_successful_summary_transaction() {
2357        let mut control = ShellControl::default();
2358        control.run_state = RunState::Validate;
2359        control.config = LaunchConfig {
2360            input_base: b64(0x0000_0001_0000_0000),
2361            output_base: b64(0x0000_0002_0000_0000),
2362            summary_base: b64(0x0000_0003_0000_0000),
2363            batch: b32(0),
2364            abi_word: b32(abi::HASHSIGS_HBM_ABI_V1 as u128),
2365            nonce: b64(0x0123_4567_89ab_cdef),
2366        };
2367
2368        let (validate_output, validate_d) =
2369            u280_hbm_shell_kernel(active_clock(), HbmShellInput::default(), shell_q(control));
2370        assert_eq!(validate_d.control.run_state, RunState::Execute);
2371        assert!(!validate_output.input_hbm.arvalid);
2372        assert!(!validate_output.output_hbm.awvalid);
2373
2374        let (close_output, close_d) = u280_hbm_shell_kernel(
2375            active_clock(),
2376            HbmShellInput::default(),
2377            shell_q(validate_d.control),
2378        );
2379        assert_eq!(close_d.control.run_state, RunState::SummaryAw);
2380        assert!(!close_output.input_hbm.arvalid);
2381        assert!(!close_output.output_hbm.awvalid);
2382
2383        let (aw_output, aw_d) = u280_hbm_shell_kernel(
2384            active_clock(),
2385            HbmShellInput {
2386                output_hbm: Axi512WriteMemoryToKernel {
2387                    awready: true,
2388                    ..Axi512WriteMemoryToKernel::default()
2389                },
2390                ..HbmShellInput::default()
2391            },
2392            shell_q(close_d.control),
2393        );
2394        assert!(aw_output.output_hbm.awvalid);
2395        assert_eq!(aw_output.output_hbm.awaddr, control.config.summary_base);
2396        assert_eq!(aw_output.output_hbm.awlen, b8(0));
2397        assert_eq!(aw_d.control.run_state, RunState::SummaryW);
2398
2399        let (w_output, w_d) = u280_hbm_shell_kernel(
2400            active_clock(),
2401            HbmShellInput {
2402                output_hbm: Axi512WriteMemoryToKernel {
2403                    wready: true,
2404                    ..Axi512WriteMemoryToKernel::default()
2405                },
2406                ..HbmShellInput::default()
2407            },
2408            shell_q(aw_d.control),
2409        );
2410        assert!(w_output.output_hbm.wvalid);
2411        assert!(w_output.output_hbm.wlast);
2412        assert_eq!(w_output.output_hbm.wstrb, b64(0xffff_ffff_ffff_ffff));
2413        assert_eq!(w_d.control.run_state, RunState::SummaryB);
2414
2415        let (b_output, b_d) = u280_hbm_shell_kernel(
2416            active_clock(),
2417            HbmShellInput {
2418                output_hbm: Axi512WriteMemoryToKernel {
2419                    bvalid: true,
2420                    bid: b1(0),
2421                    bresp: AXI_RESP_OKAY,
2422                    ..Axi512WriteMemoryToKernel::default()
2423                },
2424                ..HbmShellInput::default()
2425            },
2426            shell_q(w_d.control),
2427        );
2428        assert!(b_output.output_hbm.bready);
2429        assert_eq!(b_d.control.run_state, RunState::Done);
2430        assert!(b_d.control.ap_done);
2431        assert_eq!(b_d.control.terminal_code, TERMINAL_SUCCESS_BITS);
2432        assert_eq!(b_d.control.error_flags, b16(0));
2433        assert_eq!(b_d.control.input_ar_count, b16(0));
2434        assert_eq!(b_d.control.input_r_count, b16(0));
2435        assert_eq!(b_d.control.core_admission_count, b16(0));
2436        assert_eq!(b_d.control.frame_capture_count, b16(0));
2437        assert_eq!(b_d.control.payload_aw_count, b16(0));
2438        assert_eq!(b_d.control.payload_w_count, b32(0));
2439        assert_eq!(b_d.control.payload_b_count, b16(0));
2440        assert_eq!(b_d.control.memory_completed_count, b16(0));
2441        let status = last_status_kernel(
2442            b_d.control.terminal_code,
2443            b_d.control.error_flags,
2444            SummaryWriteStatus {
2445                aw: b_d.control.summary_aw_handshake,
2446                w: b_d.control.summary_w_handshake,
2447                b: b_d.control.summary_b_handshake,
2448                b_canonical: b_d.control.summary_b_canonical,
2449            },
2450        );
2451        assert_eq!(status, b32(0x0f00_0000));
2452        assert!(crate::control::last_status_is_host_success_kernel(status));
2453    }
2454}