Skip to main content

wots_rhdl/
engine.rs

1//! Fine-grained, BRAM-oriented SHA-profile WOTS context engine.
2//!
3//! One instance owns one four-bit scheduler context behind a shared SHA
4//! transport. The enclosing top chooses how many of the sixteen encodable
5//! context identifiers it instantiates. This engine constructs and retires the exact 1,258 compression tasks for
6//! a fused private-seed signature/public-key operation. Every segment advances
7//! independently: a resettable stage array, ready mask, and in-flight mask
8//! encode the per-segment dependency frontier. There are no whole-wave phase
9//! barriers.
10//!
11//! The dependency graph is explicit:
12//!
13//! - the private-seed hash releases the public-seed task and all 67 segment
14//!   PRFs concurrently;
15//! - the public-seed result releases all sixteen masks;
16//! - segment PRF plus mask zero release secret data, whose response releases
17//!   secret padding;
18//! - secret padding and masks one through fifteen release each chain step;
19//! - step fifteen retains the endpoint; and
20//! - each serialized public-key block starts as soon as its endpoint pair and
21//!   the preceding public-key digest are both ready.
22//!
23//! The scheduler priority is public-key, seed, public-seed, masks, then segment
24//! work. Public-key work can occupy at most one issue per compression latency,
25//! and seed/public-seed/mask classes are finite, so the rotating hierarchical
26//! segment selector cannot starve. Segment readiness is stored as nine groups
27//! of eight bits (the last five bits are permanently zero); a rotating group
28//! cursor selects one nonempty group and a fixed eight-way encoder selects its
29//! lowest ready member.
30//!
31//! State and final endpoints share two 64-entry, 256-bit memories banked by
32//! segment parity. The banks provide both endpoint operands for public-key
33//! blocks 0 through 32 without duplicating endpoint storage. Masks use a
34//! 16-entry memory. Signature segments use matching even/odd 64-entry banks so
35//! one pair read returns 64 bytes. Pair reads 0 through 32 furnish signature
36//! beats 0 through 32; pair 33 furnishes segment 66 alongside `public_seed` in
37//! beat 33, and `public_key_hash` occupies the valid half of beat 34.
38//! The unused addresses are unreachable padding imposed by power-of-two RHDL RAM depths.
39//! All wide data and RAM are resetless; separately reset ownership, ready,
40//! in-flight, request-valid, and result-valid bits determine whether the data is
41//! meaningful.
42//! The private seed is architecturally overwritten after its seed request
43//! transfers. A seven-bit counter tracks the one public-seed PRF plus 67
44//! segment PRFs that consume the derived private key; the key is overwritten
45//! when the last of those 68 requests transfers. Both registers are overwritten
46//! again when `result_taken` releases the retained result. These are logical RTL
47//! assignments only, not evidence of physical zeroization, remanence resistance,
48//! power-analysis resistance, or any other side-channel property.
49//!
50//! Each request uses a synchronous-memory prefetch followed by one stable
51//! resetless payload register. Accepting a request can launch the next prefetch
52//! in the same cycle, so a ready context can issue once every two cycles. A
53//! segment made ready by a response is excluded from selection in that response
54//! cycle, preventing same-address response-write/prefetch-read behavior. Mask
55//! retirement similarly delays all newly awakened segments for one cycle, and
56//! a newly completed endpoint cannot launch its public-key read until the next
57//! cycle.
58//!
59//! # Response and reset contract
60//!
61//! Responses may return in any order. The complete 32-bit tag must be canonical,
62//! name this context and generation, and identify a task whose in-flight bit and
63//! current stage agree. Malformed, stale, duplicate, never-issued, or
64//! stage-mismatched responses are rejected without any cryptographic state
65//! write and latch `fault`.
66//! A fault does not discard the context's retained storage: if all required
67//! tasks subsequently retire, `result_valid` and `fault` may be true together.
68//! The enclosing framer must treat that combination as a failed operation,
69//! emit no result bytes, and acknowledge it with `result_taken` to release the
70//! reserved credit. The fault remains visible through that acknowledgement
71//! cycle and clears when the retained result is released.
72//!
73//! Reset is fail closed and directly suppresses requests, response acceptance,
74//! and all memory writes, even on the asserting edge. The enclosing transport
75//! and compression lanes must share reset so old tokens are flushed. If a
76//! system cannot guarantee a shared flush, it must not reuse a generation until
77//! every pre-reset token has drained. Eight-bit generation wrap is safe only
78//! under the same no-outstanding-old-token rule.
79//!
80//! This module has source and simulation evidence only. Its logical storage
81//! mapping is an implementation intent, not evidence that a synthesis tool
82//! inferred block RAM or met resource, timing, or throughput targets.
83
84use rhdl::prelude::*;
85use rhdl_fpga::core::{
86    dff::DFF,
87    ram::synchronous::{In as SyncBramIn, SyncBRAM, Write as SyncBramWrite},
88};
89use rhdl_primitives::NoResetDff;
90use sha256_rhdl::lane::{CompressionInput, CompressionOutput};
91
92use crate::{
93    blocks::{
94        ChainBlockInput, EndpointPairInput, HashBytes, HashWords, PrfBlockInput, SecretDataInput,
95        chain_block_kernel, endpoint_final_block_kernel, endpoint_pair_block_kernel,
96        hash_words_to_bytes_kernel, initial_state_words_kernel, prf_block_kernel,
97        private_seed_block_kernel, secret_data_block_kernel, secret_padding_block_kernel,
98    },
99    digits::{MessageBytes, MessageDigitArray, message_digits_kernel},
100    tag::{TagFields, decode_tag_kernel, encode_tag_kernel, tag_is_well_formed_kernel},
101    tasks::TOTAL_TASKS,
102};
103
104/// Number of independently advancing WOTS segments.
105pub const ENGINE_SEGMENTS: usize = 67;
106/// Eight segments represented in each ready/in-flight group.
107pub const SEGMENTS_PER_GROUP: usize = 8;
108/// Number of hierarchical segment groups.
109pub const SEGMENT_GROUPS: usize = 9;
110/// Consecutive endpoint-stream blocks accelerated ahead of the PK hash.
111pub const PUBLIC_KEY_FOCUS_BLOCKS: usize = 8;
112/// Requests that consume the derived private key: one public-seed plus 67 segment PRFs.
113pub const PRIVATE_KEY_CONSUMERS: usize = 68;
114/// Address bits in each even/odd state bank.
115pub const STATE_BANK_ADDRESS_BITS: usize = 6;
116/// Logical entries in each state bank.
117pub const STATE_BANK_DEPTH: usize = 1 << STATE_BANK_ADDRESS_BITS;
118/// Address bits in the mask memory.
119pub const MASK_ADDRESS_BITS: usize = 4;
120/// Logical entries in the mask memory.
121pub const MASK_DEPTH: usize = 1 << MASK_ADDRESS_BITS;
122/// Address bits in each even/odd signature bank.
123pub const SIGNATURE_ADDRESS_BITS: usize = 6;
124/// Logical entries in each signature bank.
125pub const SIGNATURE_DEPTH: usize = 1 << SIGNATURE_ADDRESS_BITS;
126
127/// Logical state/endpoints storage bits per context.
128pub const STATE_STORAGE_BITS: usize = 2 * STATE_BANK_DEPTH * 8 * 32;
129/// Logical randomization-mask storage bits per context.
130pub const MASK_STORAGE_BITS: usize = MASK_DEPTH * 8 * 32;
131/// Logical signature storage bits per context.
132pub const SIGNATURE_STORAGE_BITS: usize = 2 * SIGNATURE_DEPTH * 8 * 32;
133/// Total logical RAM bits authored per context.
134pub const CONTEXT_LOGICAL_RAM_BITS: usize =
135    STATE_STORAGE_BITS + MASK_STORAGE_BITS + SIGNATURE_STORAGE_BITS;
136
137/// Per-segment stage code for its initial PRF.
138pub const SEGMENT_STAGE_PRF: u8 = 0;
139/// Per-segment stage code for the secret-data block.
140pub const SEGMENT_STAGE_SECRET_DATA: u8 = 1;
141/// Per-segment stage code for the secret-padding block.
142pub const SEGMENT_STAGE_SECRET_PADDING: u8 = 2;
143/// First chain stage code; stage `2 + step` names one-based `step`.
144pub const SEGMENT_STAGE_CHAIN_FIRST: u8 = 3;
145/// Last chain stage code, corresponding to chain step fifteen.
146pub const SEGMENT_STAGE_CHAIN_LAST: u8 = 17;
147/// Completed endpoint stage code.
148pub const SEGMENT_STAGE_ENDPOINT: u8 = 18;
149
150/// Nine eight-bit groups used for 67-bit segment masks.
151pub type SegmentGroups = [b8; SEGMENT_GROUPS];
152/// One five-bit dependency stage per segment.
153pub type SegmentStages = [b5; ENGINE_SEGMENTS];
154
155const _: () = {
156    assert!(SEGMENT_GROUPS * SEGMENTS_PER_GROUP >= ENGINE_SEGMENTS);
157    assert!(STATE_BANK_DEPTH == 64);
158    assert!(MASK_DEPTH == 16);
159    assert!(SIGNATURE_DEPTH == 64);
160    assert!(STATE_STORAGE_BITS == 32_768);
161    assert!(MASK_STORAGE_BITS == 4_096);
162    assert!(SIGNATURE_STORAGE_BITS == 32_768);
163    assert!(CONTEXT_LOGICAL_RAM_BITS == 69_632);
164    assert!(TOTAL_TASKS == 1_258);
165    assert!(PRIVATE_KEY_CONSUMERS < 128);
166};
167
168/// Resettable ownership, dependency, and in-flight state.
169#[allow(clippy::struct_excessive_bools)] // Each bool is an independent hardware state bit.
170#[derive(Clone, Copy, Debug, Digital, Eq, PartialEq)]
171pub struct EngineControl {
172    /// This context owns an accepted job.
173    pub active: bool,
174    /// Output-buffer credit is reserved for the active or retained job.
175    pub credit_reserved: bool,
176    /// A complete result is retained until the consumer acknowledges it.
177    pub result_valid: bool,
178    /// Sticky response-integrity failure for the current/retained job; cleared on release.
179    pub fault: bool,
180    /// Seed task is eligible to issue.
181    pub seed_ready: bool,
182    /// Seed task has issued and not retired.
183    pub seed_inflight: bool,
184    /// Public-seed task is eligible to issue.
185    pub public_seed_ready: bool,
186    /// Public-seed task has issued and not retired.
187    pub public_seed_inflight: bool,
188    /// Public-seed response has retired.
189    pub public_seed_done: bool,
190    /// Derived-private-key request transfers still outstanding.
191    pub private_key_consumers_remaining: b7,
192    /// Eligible mask tasks by PRF index.
193    pub mask_ready: b16,
194    /// Issued but unretired mask tasks.
195    pub mask_inflight: b16,
196    /// Retired mask tasks.
197    pub mask_done: b16,
198    /// Eligible per-segment tasks in hierarchical groups.
199    pub segment_ready: SegmentGroups,
200    /// Issued but unretired per-segment tasks.
201    pub segment_inflight: SegmentGroups,
202    /// Current dependency stage for every segment.
203    pub segment_stage: SegmentStages,
204    /// Segments whose chain step fifteen retired.
205    pub endpoint_done: SegmentGroups,
206    /// Rotating group cursor for segment fairness.
207    pub segment_group_cursor: b4,
208    /// Public-key block currently eligible or in flight.
209    pub public_key_block: b6,
210    /// A public-key block has issued and not retired.
211    pub public_key_inflight: bool,
212    /// Prior serialized public-key digest is available.
213    pub public_key_state_ready: bool,
214    /// Total requests accepted for this job.
215    pub total_issued: b11,
216    /// Total responses accepted for this job.
217    pub total_retired: b11,
218    /// A memory prefetch awaits payload construction.
219    pub prefetch_pending: bool,
220    /// Complete tag captured for the prefetch.
221    pub prefetch_tag: b32,
222    /// A stable request payload is presented to transport.
223    pub request_valid: bool,
224    /// Four-bit transport slot stored at job acceptance.
225    pub context: b4,
226    /// Eight-bit stale-response generation.
227    pub generation: b8,
228    /// A retained-result memory read was accepted one cycle earlier.
229    pub result_read_pending: bool,
230    /// Pair address associated with registered result-memory outputs.
231    pub result_read_pair: b6,
232}
233
234impl Default for EngineControl {
235    fn default() -> Self {
236        Self {
237            active: false,
238            credit_reserved: false,
239            result_valid: false,
240            fault: false,
241            seed_ready: false,
242            seed_inflight: false,
243            public_seed_ready: false,
244            public_seed_inflight: false,
245            public_seed_done: false,
246            private_key_consumers_remaining: b7(0),
247            mask_ready: b16(0),
248            mask_inflight: b16(0),
249            mask_done: b16(0),
250            segment_ready: [b8(0); SEGMENT_GROUPS],
251            segment_inflight: [b8(0); SEGMENT_GROUPS],
252            segment_stage: [b5(0); ENGINE_SEGMENTS],
253            endpoint_done: [b8(0); SEGMENT_GROUPS],
254            segment_group_cursor: b4(0),
255            public_key_block: b6(0),
256            public_key_inflight: false,
257            public_key_state_ready: false,
258            total_issued: b11(0),
259            total_retired: b11(0),
260            prefetch_pending: false,
261            prefetch_tag: b32(0),
262            request_valid: false,
263            context: b4(0),
264            generation: b8(0),
265            result_read_pending: false,
266            result_read_pair: b6(0),
267        }
268    }
269}
270
271/// Inputs for one single-context work engine.
272#[allow(clippy::struct_excessive_bools)] // Each bool is a distinct hardware port.
273#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
274pub struct ContextEngineInput {
275    /// Offer a new fused private-seed operation.
276    pub start_valid: bool,
277    /// Private seed consumed by the operation.
278    pub private_seed: HashBytes,
279    /// Already-hashed message selecting signature positions.
280    pub message: MessageBytes,
281    /// Four-bit transport slot assigned by the enclosing top.
282    pub context: b4,
283    /// Enclosing top confirms that `context` names an instantiated slot.
284    pub context_enabled: bool,
285    /// Generation embedded in every request tag.
286    pub generation: b8,
287    /// A downstream result-retention credit is available to reserve.
288    pub output_credit_available: bool,
289    /// The transport accepted the stable request.
290    pub request_ready: bool,
291    /// Response routed from the SHA transport.
292    pub response: CompressionOutput,
293    /// Consumer finished copying the retained result.
294    pub result_taken: bool,
295    /// Request a one-cycle-latency signature/endpoint read.
296    pub result_read_enable: bool,
297    /// Pair address `0..33` selected for retained-result readback.
298    pub result_read_pair: b6,
299}
300
301/// Status, transport request, and retained-result outputs.
302#[allow(clippy::struct_excessive_bools)] // Each bool is a distinct hardware port.
303#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
304pub struct ContextEngineOutput {
305    /// A valid start with credit and a top-enabled context can be accepted.
306    pub start_ready: bool,
307    /// A top-disabled context was offered while otherwise ready to start.
308    pub start_rejected: bool,
309    /// This context owns an operation.
310    pub active: bool,
311    /// Output credit remains reserved through retained result ownership.
312    pub output_credit_reserved: bool,
313    /// Complete request held stable while `request_ready` is false.
314    pub request: CompressionInput,
315    /// A valid response passed all in-flight and stage checks.
316    pub response_ready: bool,
317    /// A valid response was rejected without a cryptographic state write.
318    pub response_rejected: bool,
319    /// Sticky response-integrity fault; a valid faulted result must be discarded.
320    pub fault: bool,
321    /// One-cycle completion pulse on the final accepted public-key response.
322    /// Retained result data becomes valid on the following cycle.
323    pub done: bool,
324    /// One-cycle pulse when `result_taken` frees the retained credit.
325    pub output_credit_release: bool,
326    /// Complete output remains retained until `result_taken`; discard it when `fault` is true.
327    pub result_valid: bool,
328    /// Public seed of the current/retained operation.
329    pub public_seed: HashBytes,
330    /// Public-key hash of the retained operation.
331    pub public_key_hash: HashBytes,
332    /// A prior in-range result read returns this cycle.
333    pub result_read_valid: bool,
334    /// Pair address associated with the returned signature and endpoints.
335    pub result_read_pair: b6,
336    /// Even signature segment `2 * pair`.
337    pub signature_first: HashBytes,
338    /// Odd signature segment `2 * pair + 1`; invalid for pair 33.
339    pub signature_second: HashBytes,
340    /// Whether the odd member exists for the returned pair.
341    pub result_read_second_valid: bool,
342    /// Even final chain endpoint `2 * pair`.
343    pub endpoint_first: HashBytes,
344    /// Odd final chain endpoint `2 * pair + 1`; invalid for pair 33.
345    pub endpoint_second: HashBytes,
346    /// Total requests accepted for the operation.
347    pub total_issued: b11,
348    /// Total responses accepted before the current cycle.
349    pub total_retired: b11,
350    /// Next serialized public-key block, for diagnostics.
351    pub public_key_block: b6,
352    /// Derived-private-key request transfers still outstanding.
353    pub private_key_consumers_remaining: b7,
354}
355
356/// Result of the hierarchical rotating segment selector.
357#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
358pub struct SegmentSelection {
359    /// At least one segment is ready.
360    pub valid: bool,
361    /// Selected segment in `0..67`.
362    pub segment: b7,
363    /// Group cursor to retain for the next selection.
364    pub next_group: b4,
365}
366
367/// Prepared-request inputs after the synchronous memory read.
368#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
369pub struct PrepareRequestInput {
370    /// Complete canonical task fields captured by the prefetch.
371    pub fields: TagFields,
372    /// Original private seed for the seed task.
373    pub private_seed: HashBytes,
374    /// Derived private key for PRFs.
375    pub private_key: HashWords,
376    /// Derived public seed for mask PRFs.
377    pub public_seed: HashWords,
378    /// Prior serialized public-key digest.
379    pub public_key_state: HashWords,
380    /// Prefetched even state/endpoint bank output.
381    pub state_even: HashWords,
382    /// Prefetched odd state/endpoint bank output.
383    pub state_odd: HashWords,
384    /// Prefetched randomization mask.
385    pub mask: HashWords,
386}
387
388/// Read/write ports for the two signature parity banks.
389#[derive(Clone, Copy, Debug, Digital, PartialEq)]
390pub struct SignatureBanksInput {
391    /// Shared pair address for both banks.
392    pub read_addr: b6,
393    /// Even-segment write port.
394    pub even_write: SyncBramWrite<HashWords, SIGNATURE_ADDRESS_BITS>,
395    /// Odd-segment write port.
396    pub odd_write: SyncBramWrite<HashWords, SIGNATURE_ADDRESS_BITS>,
397}
398
399/// Registered outputs from both signature parity banks.
400#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
401pub struct SignatureBanksOutput {
402    /// Even segment at the prior pair address.
403    pub even: HashWords,
404    /// Odd segment at the prior pair address.
405    pub odd: HashWords,
406}
407
408/// Structural wrapper keeping the top synchronous-state tuple within Rust's
409/// tuple trait arity while preserving two physical RAM children.
410#[derive(Clone, Debug, Default, Synchronous, SynchronousDQ)]
411pub struct SignatureBanks {
412    even: SyncBRAM<HashWords, SIGNATURE_ADDRESS_BITS>,
413    odd: SyncBRAM<HashWords, SIGNATURE_ADDRESS_BITS>,
414}
415
416impl SynchronousIO for SignatureBanks {
417    type I = SignatureBanksInput;
418    type O = SignatureBanksOutput;
419    type Kernel = signature_banks_kernel;
420}
421
422/// Connectivity for [`SignatureBanks`].
423#[allow(clippy::used_underscore_binding)] // The kernel macro consumes the reset argument.
424#[kernel]
425pub fn signature_banks_kernel(
426    _clock_reset: ClockReset,
427    input: SignatureBanksInput,
428    q: SignatureBanksQ,
429) -> (SignatureBanksOutput, SignatureBanksD) {
430    (
431        SignatureBanksOutput {
432            even: q.even,
433            odd: q.odd,
434        },
435        SignatureBanksD {
436            even: SyncBramIn::<HashWords, SIGNATURE_ADDRESS_BITS> {
437                read_addr: input.read_addr,
438                write: input.even_write,
439            },
440            odd: SyncBramIn::<HashWords, SIGNATURE_ADDRESS_BITS> {
441                read_addr: input.read_addr,
442                write: input.odd_write,
443            },
444        },
445    )
446}
447
448/// Increment a segment-group cursor modulo nine.
449#[kernel]
450pub fn increment_segment_group_kernel(group: b4) -> b4 {
451    if group == b4(8) { b4(0) } else { group + b4(1) }
452}
453
454/// Read one segment bit from a nine-group mask.
455#[kernel]
456pub fn segment_bit_kernel(groups: SegmentGroups, segment: b7) -> bool {
457    let group: b4 = (segment >> 3).resize();
458    let offset: b3 = segment.resize();
459    let word = match group {
460        Bits::<4>(0) => groups[0],
461        Bits::<4>(1) => groups[1],
462        Bits::<4>(2) => groups[2],
463        Bits::<4>(3) => groups[3],
464        Bits::<4>(4) => groups[4],
465        Bits::<4>(5) => groups[5],
466        Bits::<4>(6) => groups[6],
467        Bits::<4>(7) => groups[7],
468        _ => groups[8],
469    };
470    word & (b8(1) << offset) != b8(0)
471}
472
473/// Set or clear one segment bit in a nine-group mask.
474#[kernel]
475pub fn set_segment_bit_kernel(
476    mut groups: SegmentGroups,
477    segment: b7,
478    value: bool,
479) -> SegmentGroups {
480    let group: b4 = (segment >> 3).resize();
481    let offset: b3 = segment.resize();
482    let bit = b8(1) << offset;
483    match group {
484        Bits::<4>(0) => {
485            groups[0] = if value {
486                groups[0] | bit
487            } else {
488                groups[0] & !bit
489            };
490        }
491        Bits::<4>(1) => {
492            groups[1] = if value {
493                groups[1] | bit
494            } else {
495                groups[1] & !bit
496            };
497        }
498        Bits::<4>(2) => {
499            groups[2] = if value {
500                groups[2] | bit
501            } else {
502                groups[2] & !bit
503            };
504        }
505        Bits::<4>(3) => {
506            groups[3] = if value {
507                groups[3] | bit
508            } else {
509                groups[3] & !bit
510            };
511        }
512        Bits::<4>(4) => {
513            groups[4] = if value {
514                groups[4] | bit
515            } else {
516                groups[4] & !bit
517            };
518        }
519        Bits::<4>(5) => {
520            groups[5] = if value {
521                groups[5] | bit
522            } else {
523                groups[5] & !bit
524            };
525        }
526        Bits::<4>(6) => {
527            groups[6] = if value {
528                groups[6] | bit
529            } else {
530                groups[6] & !bit
531            };
532        }
533        Bits::<4>(7) => {
534            groups[7] = if value {
535                groups[7] | bit
536            } else {
537                groups[7] & !bit
538            };
539        }
540        _ => {
541            groups[8] = if value {
542                groups[8] | bit
543            } else {
544                groups[8] & !bit
545            };
546        }
547    }
548    groups
549}
550
551/// Remove response-cycle wakeups from the selector-visible ready groups.
552#[kernel]
553pub fn exclude_segment_groups_kernel(
554    ready: SegmentGroups,
555    excluded: SegmentGroups,
556) -> SegmentGroups {
557    [
558        ready[0] & !excluded[0],
559        ready[1] & !excluded[1],
560        ready[2] & !excluded[2],
561        ready[3] & !excluded[3],
562        ready[4] & !excluded[4],
563        ready[5] & !excluded[5],
564        ready[6] & !excluded[6],
565        ready[7] & !excluded[7],
566        ready[8] & !excluded[8] & b8(0x07),
567    ]
568}
569
570/// Select one ready segment with rotating group fairness.
571///
572/// The lowest set bit wins within a group. After every selection the cursor
573/// advances to the following group, preventing one active bank of eight
574/// segments from monopolizing a context.
575#[kernel]
576pub fn select_ready_segment_kernel(ready: SegmentGroups, cursor: b4) -> SegmentSelection {
577    let mut selection = SegmentSelection {
578        next_group: cursor,
579        ..SegmentSelection::default()
580    };
581    let mut scan_group = cursor;
582    for _scan in 0..SEGMENT_GROUPS {
583        let word = match scan_group {
584            Bits::<4>(0) => ready[0],
585            Bits::<4>(1) => ready[1],
586            Bits::<4>(2) => ready[2],
587            Bits::<4>(3) => ready[3],
588            Bits::<4>(4) => ready[4],
589            Bits::<4>(5) => ready[5],
590            Bits::<4>(6) => ready[6],
591            Bits::<4>(7) => ready[7],
592            _ => ready[8] & b8(0x07),
593        };
594        let mut offset = b3(0);
595        let mut found_in_group = false;
596        if word & b8(0x01) != b8(0) {
597            offset = b3(0);
598            found_in_group = true;
599        } else if word & b8(0x02) != b8(0) {
600            offset = b3(1);
601            found_in_group = true;
602        } else if word & b8(0x04) != b8(0) {
603            offset = b3(2);
604            found_in_group = true;
605        } else if word & b8(0x08) != b8(0) {
606            offset = b3(3);
607            found_in_group = true;
608        } else if word & b8(0x10) != b8(0) {
609            offset = b3(4);
610            found_in_group = true;
611        } else if word & b8(0x20) != b8(0) {
612            offset = b3(5);
613            found_in_group = true;
614        } else if word & b8(0x40) != b8(0) {
615            offset = b3(6);
616            found_in_group = true;
617        } else if word & b8(0x80) != b8(0) {
618            offset = b3(7);
619            found_in_group = true;
620        }
621        if !selection.valid && found_in_group {
622            let group_wide: b7 = scan_group.resize();
623            let offset_wide: b7 = offset.resize();
624            selection.valid = true;
625            selection.segment = (group_wide << 3) + offset_wide;
626            selection.next_group = increment_segment_group_kernel(scan_group);
627        }
628        scan_group = increment_segment_group_kernel(scan_group);
629    }
630    selection
631}
632
633/// Convert a segment stage to its one canonical compression-task tag.
634#[kernel]
635pub fn segment_task_fields_kernel(
636    stage: b5,
637    segment: b7,
638    context: b4,
639    generation: b8,
640) -> TagFields {
641    let mut fields = TagFields {
642        context,
643        segment,
644        generation,
645        ..TagFields::default()
646    };
647    match stage {
648        Bits::<5>(0) => {
649            fields.kind = b3(3);
650        }
651        Bits::<5>(1) => {
652            fields.kind = b3(4);
653        }
654        Bits::<5>(2) => {
655            fields.kind = b3(5);
656            fields.block_index = b6(1);
657        }
658        _ => {
659            fields.kind = b3(6);
660            fields.chain_step = (stage - b5(2)).resize();
661        }
662    }
663    fields
664}
665
666/// Return the randomization mask required by a segment stage.
667///
668/// Secret data needs mask zero, secret padding needs no mask, and a chain stage
669/// needs its one-based chain-step mask. PRF and endpoint stages return zero but
670/// callers do not treat that value as a dependency.
671#[kernel]
672pub fn segment_required_mask_kernel(stage: b5) -> b4 {
673    if stage == b5(1) {
674        b4(0)
675    } else if stage >= b5(3) && stage <= b5(17) {
676        (stage - b5(2)).resize()
677    } else {
678        b4(0)
679    }
680}
681
682/// Return whether one serialized public-key block has all endpoints available.
683#[kernel]
684pub fn public_key_endpoints_ready_kernel(endpoint_done: SegmentGroups, block: b6) -> bool {
685    let mut ready = false;
686    if block < b6(33) {
687        let block_wide: b7 = block.resize();
688        let first = block_wide << 1;
689        ready = segment_bit_kernel(endpoint_done, first)
690            && segment_bit_kernel(endpoint_done, first + b7(1));
691    } else if block == b6(33) {
692        ready = segment_bit_kernel(endpoint_done, b7(66));
693    }
694    ready
695}
696
697/// Select a ready segment in an eight-block endpoint-stream lookahead window.
698///
699/// Before public-key block `n` issues the window begins at `n`; while `n` is in
700/// flight it begins at `n + 1`. The first ready segment in blocks from the
701/// window start through `start + 7` wins. Each selected segment immediately
702/// becomes in flight and cannot issue again until its 64-cycle response, so the
703/// lookahead cannot continuously monopolize issue slots; the rotating general
704/// selector consumes the gaps. The scan saturates at block 33 and never wraps.
705#[allow(clippy::assign_op_pattern)] // Compound assignment is not lowered by pinned RHDL.
706#[kernel]
707pub fn select_public_key_focus_segment_kernel(
708    ready: SegmentGroups,
709    public_key_block: b6,
710    public_key_inflight: bool,
711) -> SegmentSelection {
712    let mut focus_block = public_key_block;
713    if public_key_inflight && public_key_block < b6(33) {
714        focus_block = public_key_block + b6(1);
715    }
716    let mut selection = SegmentSelection::default();
717    for _lookahead in 0..PUBLIC_KEY_FOCUS_BLOCKS {
718        if !selection.valid && focus_block < b6(33) {
719            let block_wide: b7 = focus_block.resize();
720            let first = block_wide << 1;
721            let second = first + b7(1);
722            if segment_bit_kernel(ready, first) {
723                selection.valid = true;
724                selection.segment = first;
725            } else if segment_bit_kernel(ready, second) {
726                selection.valid = true;
727                selection.segment = second;
728            }
729        } else if !selection.valid && focus_block == b6(33) && segment_bit_kernel(ready, b7(66)) {
730            selection.valid = true;
731            selection.segment = b7(66);
732        }
733        if focus_block < b6(33) {
734            focus_block = focus_block + b6(1);
735        }
736    }
737    selection
738}
739
740/// Wake every dependency-waiting segment that needs a newly retired mask.
741#[allow(clippy::needless_range_loop)] // Iterators are not lowered by pinned RHDL kernels.
742#[kernel]
743pub fn wake_segments_for_mask_kernel(
744    stages: SegmentStages,
745    mut ready: SegmentGroups,
746    inflight: SegmentGroups,
747    retired_mask: b4,
748) -> SegmentGroups {
749    for segment in 0..ENGINE_SEGMENTS {
750        let segment_index = b7(segment as u128);
751        let stage = stages[segment];
752        let waiting = !segment_bit_kernel(ready, segment_index)
753            && !segment_bit_kernel(inflight, segment_index);
754        let needs_retired_mask = (stage == b5(1) && retired_mask == b4(0))
755            || (stage >= b5(3)
756                && stage <= b5(17)
757                && segment_required_mask_kernel(stage) == retired_mask);
758        if waiting && needs_retired_mask {
759            ready = set_segment_bit_kernel(ready, segment_index, true);
760        }
761    }
762    ready
763}
764
765/// Mark both segments sharing a parity-bank address as response-cycle hazards.
766#[kernel]
767pub fn state_pair_exclusion_kernel(segment: b7) -> SegmentGroups {
768    let mut excluded = [b8(0); SEGMENT_GROUPS];
769    excluded = set_segment_bit_kernel(excluded, segment, true);
770    if segment & b7(1) == b7(0) {
771        if segment < b7(66) {
772            excluded = set_segment_bit_kernel(excluded, segment + b7(1), true);
773        }
774    } else {
775        excluded = set_segment_bit_kernel(excluded, segment - b7(1), true);
776    }
777    excluded
778}
779
780/// Bitwise union of two segment masks.
781#[kernel]
782pub fn union_segment_groups_kernel(left: SegmentGroups, right: SegmentGroups) -> SegmentGroups {
783    [
784        left[0] | right[0],
785        left[1] | right[1],
786        left[2] | right[2],
787        left[3] | right[3],
788        left[4] | right[4],
789        left[5] | right[5],
790        left[6] | right[6],
791        left[7] | right[7],
792        (left[8] | right[8]) & b8(0x07),
793    ]
794}
795
796/// Construct a complete compression request from prefetched context storage.
797#[kernel]
798pub fn prepare_request_kernel(input: PrepareRequestInput) -> CompressionInput {
799    let initial = initial_state_words_kernel(b1(0));
800    let private_key = hash_words_to_bytes_kernel(input.private_key);
801    let public_seed = hash_words_to_bytes_kernel(input.public_seed);
802    let state_words = if input.fields.segment & b7(1) == b7(0) {
803        input.state_even
804    } else {
805        input.state_odd
806    };
807    let state = hash_words_to_bytes_kernel(state_words);
808    let mask = hash_words_to_bytes_kernel(input.mask);
809    let mut chaining = initial;
810    let mut block = private_seed_block_kernel(input.private_seed);
811
812    match input.fields.kind {
813        Bits::<3>(0) => {}
814        Bits::<3>(1) => {
815            block = prf_block_kernel(PrfBlockInput {
816                seed: private_key,
817                index: b16(0),
818            });
819        }
820        Bits::<3>(2) => {
821            block = prf_block_kernel(PrfBlockInput {
822                seed: public_seed,
823                index: input.fields.segment.resize(),
824            });
825        }
826        Bits::<3>(3) => {
827            let segment_index: b16 = input.fields.segment.resize();
828            block = prf_block_kernel(PrfBlockInput {
829                seed: private_key,
830                index: segment_index + b16(1),
831            });
832        }
833        Bits::<3>(4) => {
834            block = secret_data_block_kernel(SecretDataInput {
835                function_key: mask,
836                secret_prf: state,
837            });
838        }
839        Bits::<3>(5) => {
840            chaining = state_words;
841            block = secret_padding_block_kernel(b1(0));
842        }
843        Bits::<3>(6) => {
844            block = chain_block_kernel(ChainBlockInput { state, mask });
845        }
846        _ => {
847            if input.fields.block_index == b6(0) {
848                chaining = initial;
849            } else {
850                chaining = input.public_key_state;
851            }
852            if input.fields.block_index == b6(33) {
853                block = endpoint_final_block_kernel(hash_words_to_bytes_kernel(input.state_even));
854            } else {
855                block = endpoint_pair_block_kernel(EndpointPairInput {
856                    first: hash_words_to_bytes_kernel(input.state_even),
857                    second: hash_words_to_bytes_kernel(input.state_odd),
858                });
859            }
860        }
861    }
862
863    CompressionInput {
864        chaining,
865        block,
866        tag: encode_tag_kernel(input.fields),
867        valid: true,
868    }
869}
870
871/// Complete one-context fine-grained WOTS work engine.
872#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
873#[rhdl(dq_no_prefix)]
874pub struct SingleContextWorkEngine {
875    control: DFF<EngineControl>,
876    request: NoResetDff<CompressionInput>,
877    private_seed: NoResetDff<HashBytes>,
878    message_digits: NoResetDff<MessageDigitArray>,
879    private_key: NoResetDff<HashWords>,
880    public_seed: NoResetDff<HashWords>,
881    public_key_state: NoResetDff<HashWords>,
882    state_even: SyncBRAM<HashWords, STATE_BANK_ADDRESS_BITS>,
883    state_odd: SyncBRAM<HashWords, STATE_BANK_ADDRESS_BITS>,
884    masks: SyncBRAM<HashWords, MASK_ADDRESS_BITS>,
885    signature: SignatureBanks,
886}
887
888impl Default for SingleContextWorkEngine {
889    fn default() -> Self {
890        Self {
891            control: DFF::new(EngineControl::default()),
892            request: NoResetDff::new(),
893            private_seed: NoResetDff::new(),
894            message_digits: NoResetDff::new(),
895            private_key: NoResetDff::new(),
896            public_seed: NoResetDff::new(),
897            public_key_state: NoResetDff::new(),
898            state_even: SyncBRAM::default(),
899            state_odd: SyncBRAM::default(),
900            masks: SyncBRAM::default(),
901            signature: SignatureBanks::default(),
902        }
903    }
904}
905
906impl SynchronousIO for SingleContextWorkEngine {
907    type I = ContextEngineInput;
908    type O = ContextEngineOutput;
909    type Kernel = single_context_work_engine_kernel;
910}
911
912/// State transition and memory connectivity for [`SingleContextWorkEngine`].
913#[allow(clippy::assign_op_pattern, clippy::match_same_arms)] // Keep RHDL-lowerable syntax.
914#[kernel]
915pub fn single_context_work_engine_kernel(
916    clock_reset: ClockReset,
917    input: ContextEngineInput,
918    q: Q,
919) -> (ContextEngineOutput, D) {
920    let resetting = clock_reset.reset.any();
921    let zero_bytes = [b8(0); 32];
922    let zero_words = [b32(0); 8];
923    let mut control = q.control;
924    let mut next_request = q.request;
925    let mut next_private_seed = q.private_seed;
926    let mut next_message_digits = q.message_digits;
927    let mut next_private_key = q.private_key;
928    let mut next_public_seed = q.public_seed;
929    let mut next_public_key_state = q.public_key_state;
930
931    let mut state_read_addr = b6(0);
932    let mut mask_read_addr = b4(0);
933    let mut signature_read_addr = b6(0);
934    let mut state_even_write = SyncBramWrite::<HashWords, STATE_BANK_ADDRESS_BITS> {
935        addr: b6(0),
936        value: zero_words,
937        enable: false,
938    };
939    let mut state_odd_write = SyncBramWrite::<HashWords, STATE_BANK_ADDRESS_BITS> {
940        addr: b6(0),
941        value: zero_words,
942        enable: false,
943    };
944    let mut mask_write = SyncBramWrite::<HashWords, MASK_ADDRESS_BITS> {
945        addr: b4(0),
946        value: zero_words,
947        enable: false,
948    };
949    let mut signature_even_write = SyncBramWrite::<HashWords, SIGNATURE_ADDRESS_BITS> {
950        addr: b6(0),
951        value: zero_words,
952        enable: false,
953    };
954    let mut signature_odd_write = SyncBramWrite::<HashWords, SIGNATURE_ADDRESS_BITS> {
955        addr: b6(0),
956        value: zero_words,
957        enable: false,
958    };
959
960    let start_ready = !resetting
961        && !q.control.active
962        && !q.control.result_valid
963        && input.output_credit_available
964        && input.context_enabled;
965    let start_accepted = input.start_valid && start_ready;
966    let start_rejected = !resetting
967        && input.start_valid
968        && !q.control.active
969        && !q.control.result_valid
970        && input.output_credit_available
971        && !input.context_enabled;
972
973    let credit_release =
974        !resetting && !q.control.active && q.control.result_valid && input.result_taken;
975
976    if credit_release {
977        control.result_valid = false;
978        control.credit_reserved = false;
979        control.fault = false;
980        control.result_read_pending = false;
981        control.private_key_consumers_remaining = b7(0);
982        next_private_seed = zero_bytes;
983        next_private_key = zero_words;
984    }
985
986    if start_accepted {
987        control.active = true;
988        control.credit_reserved = true;
989        control.result_valid = false;
990        control.fault = false;
991        control.seed_ready = true;
992        control.seed_inflight = false;
993        control.public_seed_ready = false;
994        control.public_seed_inflight = false;
995        control.public_seed_done = false;
996        control.private_key_consumers_remaining = b7(68);
997        control.mask_ready = b16(0);
998        control.mask_inflight = b16(0);
999        control.mask_done = b16(0);
1000        control.segment_ready = [b8(0); SEGMENT_GROUPS];
1001        control.segment_inflight = [b8(0); SEGMENT_GROUPS];
1002        control.segment_stage = [b5(0); ENGINE_SEGMENTS];
1003        control.endpoint_done = [b8(0); SEGMENT_GROUPS];
1004        control.segment_group_cursor = b4(0);
1005        control.public_key_block = b6(0);
1006        control.public_key_inflight = false;
1007        control.public_key_state_ready = true;
1008        control.total_issued = b11(0);
1009        control.total_retired = b11(0);
1010        control.prefetch_pending = false;
1011        control.request_valid = false;
1012        control.context = input.context;
1013        control.generation = input.generation;
1014        control.result_read_pending = false;
1015        next_private_seed = input.private_seed;
1016        next_message_digits = message_digits_kernel(input.message);
1017    }
1018
1019    // Request acceptance transfers ownership to the fixed-latency transport.
1020    // Mark the exact task in flight before choosing another prefetch.
1021    let request_accepted =
1022        !resetting && q.control.active && q.control.request_valid && input.request_ready;
1023    if request_accepted {
1024        let issued_fields = decode_tag_kernel(q.request.tag);
1025        control.request_valid = false;
1026        control.total_issued = q.control.total_issued + b11(1);
1027        match issued_fields.kind {
1028            Bits::<3>(0) => {
1029                control.seed_ready = false;
1030                control.seed_inflight = true;
1031                // The stable request register now owns the only live seed
1032                // input needed by the compression transport.
1033                next_private_seed = zero_bytes;
1034            }
1035            Bits::<3>(1) => {
1036                control.public_seed_ready = false;
1037                control.public_seed_inflight = true;
1038            }
1039            Bits::<3>(2) => {
1040                let bit = b16(1) << issued_fields.segment;
1041                control.mask_ready = q.control.mask_ready & !bit;
1042                control.mask_inflight = q.control.mask_inflight | bit;
1043            }
1044            Bits::<3>(3) => {
1045                control.segment_ready =
1046                    set_segment_bit_kernel(q.control.segment_ready, issued_fields.segment, false);
1047                control.segment_inflight =
1048                    set_segment_bit_kernel(q.control.segment_inflight, issued_fields.segment, true);
1049            }
1050            Bits::<3>(4) => {
1051                control.segment_ready =
1052                    set_segment_bit_kernel(q.control.segment_ready, issued_fields.segment, false);
1053                control.segment_inflight =
1054                    set_segment_bit_kernel(q.control.segment_inflight, issued_fields.segment, true);
1055            }
1056            Bits::<3>(5) => {
1057                control.segment_ready =
1058                    set_segment_bit_kernel(q.control.segment_ready, issued_fields.segment, false);
1059                control.segment_inflight =
1060                    set_segment_bit_kernel(q.control.segment_inflight, issued_fields.segment, true);
1061            }
1062            Bits::<3>(6) => {
1063                control.segment_ready =
1064                    set_segment_bit_kernel(q.control.segment_ready, issued_fields.segment, false);
1065                control.segment_inflight =
1066                    set_segment_bit_kernel(q.control.segment_inflight, issued_fields.segment, true);
1067            }
1068            _ => {
1069                control.public_key_inflight = true;
1070                control.public_key_state_ready = false;
1071            }
1072        }
1073        let private_key_consumer = issued_fields.kind == b3(1) || issued_fields.kind == b3(3);
1074        if private_key_consumer {
1075            if q.control.private_key_consumers_remaining == b7(0) {
1076                // A consumer beyond the exact 68-request lifetime indicates
1077                // scheduler corruption. Keep the request accounting visible
1078                // but fail the retained operation closed.
1079                control.fault = true;
1080            } else {
1081                control.private_key_consumers_remaining =
1082                    q.control.private_key_consumers_remaining - b7(1);
1083                if q.control.private_key_consumers_remaining == b7(1) {
1084                    next_private_key = zero_words;
1085                }
1086            }
1087        }
1088    }
1089
1090    let response_fields = decode_tag_kernel(input.response.tag);
1091    let response_common = !resetting
1092        && q.control.active
1093        && input.response.valid
1094        && tag_is_well_formed_kernel(input.response.tag)
1095        && response_fields.context == q.control.context
1096        && response_fields.generation == q.control.generation;
1097
1098    let seed_response = response_common && response_fields.kind == b3(0) && q.control.seed_inflight;
1099    let public_seed_response =
1100        response_common && response_fields.kind == b3(1) && q.control.public_seed_inflight;
1101    let bounded_response_segment = if response_fields.segment < b7(67) {
1102        response_fields.segment
1103    } else {
1104        b7(0)
1105    };
1106    let bounded_mask_index = if response_fields.segment < b7(16) {
1107        response_fields.segment
1108    } else {
1109        b7(0)
1110    };
1111    let mask_response_bit = b16(1) << bounded_mask_index;
1112    let mask_response = response_common
1113        && response_fields.kind == b3(2)
1114        && q.control.mask_inflight & mask_response_bit != b16(0);
1115    let segment_response_inflight =
1116        segment_bit_kernel(q.control.segment_inflight, bounded_response_segment);
1117    let segment_response_expected = segment_task_fields_kernel(
1118        q.control.segment_stage[bounded_response_segment],
1119        bounded_response_segment,
1120        q.control.context,
1121        q.control.generation,
1122    );
1123    let segment_response = response_common
1124        && response_fields.kind >= b3(3)
1125        && response_fields.kind <= b3(6)
1126        && segment_response_inflight
1127        && input.response.tag == encode_tag_kernel(segment_response_expected);
1128    let public_key_response = response_common
1129        && response_fields.kind == b3(7)
1130        && q.control.public_key_inflight
1131        && response_fields.block_index == q.control.public_key_block;
1132    let response_accepted = seed_response
1133        || public_seed_response
1134        || mask_response
1135        || segment_response
1136        || public_key_response;
1137    let response_rejected = !resetting && input.response.valid && !response_accepted;
1138    let mut done = false;
1139    let mut response_cycle_excluded = [b8(0); SEGMENT_GROUPS];
1140    let mut public_key_response_guard = false;
1141
1142    if response_rejected {
1143        control.fault = true;
1144    }
1145
1146    if response_accepted {
1147        control.total_retired = q.control.total_retired + b11(1);
1148    }
1149
1150    if seed_response {
1151        next_private_key = input.response.digest;
1152        control.seed_inflight = false;
1153        control.public_seed_ready = true;
1154        control.segment_ready = [
1155            b8(0xff),
1156            b8(0xff),
1157            b8(0xff),
1158            b8(0xff),
1159            b8(0xff),
1160            b8(0xff),
1161            b8(0xff),
1162            b8(0xff),
1163            b8(0x07),
1164        ];
1165        // These tasks consume only the newly registered private key and cannot
1166        // create a same-address BRAM read/write collision.
1167    }
1168
1169    if public_seed_response {
1170        next_public_seed = input.response.digest;
1171        control.public_seed_inflight = false;
1172        control.public_seed_done = true;
1173        control.mask_ready = b16(0xffff);
1174    }
1175
1176    if mask_response {
1177        control.mask_inflight = control.mask_inflight & !mask_response_bit;
1178        control.mask_done = control.mask_done | mask_response_bit;
1179        mask_write = SyncBramWrite::<HashWords, MASK_ADDRESS_BITS> {
1180            addr: response_fields.segment.resize(),
1181            value: input.response.digest,
1182            enable: true,
1183        };
1184        let retired_mask: b4 = response_fields.segment.resize();
1185        let ready_before_wake = control.segment_ready;
1186        control.segment_ready = wake_segments_for_mask_kernel(
1187            control.segment_stage,
1188            control.segment_ready,
1189            control.segment_inflight,
1190            retired_mask,
1191        );
1192        response_cycle_excluded = union_segment_groups_kernel(
1193            response_cycle_excluded,
1194            exclude_segment_groups_kernel(control.segment_ready, ready_before_wake),
1195        );
1196    }
1197
1198    if segment_response {
1199        let segment = response_fields.segment;
1200        let segment_addr: b6 = (segment >> 1).resize();
1201        control.segment_inflight = set_segment_bit_kernel(control.segment_inflight, segment, false);
1202        // Both parity banks receive the same read address. Exclude this segment
1203        // and its pair mate so an otherwise-unused bank output never performs
1204        // an undefined same-address read while the response writes its mate.
1205        response_cycle_excluded = union_segment_groups_kernel(
1206            response_cycle_excluded,
1207            state_pair_exclusion_kernel(segment),
1208        );
1209        if segment & b7(1) == b7(0) {
1210            state_even_write = SyncBramWrite::<HashWords, STATE_BANK_ADDRESS_BITS> {
1211                addr: segment_addr,
1212                value: input.response.digest,
1213                enable: true,
1214            };
1215        } else {
1216            state_odd_write = SyncBramWrite::<HashWords, STATE_BANK_ADDRESS_BITS> {
1217                addr: segment_addr,
1218                value: input.response.digest,
1219                enable: true,
1220            };
1221        }
1222
1223        let current_stage = q.control.segment_stage[segment];
1224        let mut next_stage = b5(18);
1225        let mut dependent_ready = false;
1226        if current_stage == b5(0) {
1227            next_stage = b5(1);
1228            dependent_ready = control.mask_done & b16(1) != b16(0);
1229        } else if current_stage == b5(1) {
1230            next_stage = b5(2);
1231            dependent_ready = true;
1232        } else if current_stage == b5(2) {
1233            next_stage = b5(3);
1234            dependent_ready = control.mask_done & b16(2) != b16(0);
1235            let digit = q.message_digits[segment];
1236            if digit == b4(0) {
1237                signature_even_write = SyncBramWrite::<HashWords, SIGNATURE_ADDRESS_BITS> {
1238                    addr: (segment >> 1).resize(),
1239                    value: input.response.digest,
1240                    enable: true,
1241                };
1242                if segment & b7(1) != b7(0) {
1243                    signature_even_write.enable = false;
1244                    signature_odd_write = SyncBramWrite::<HashWords, SIGNATURE_ADDRESS_BITS> {
1245                        addr: (segment >> 1).resize(),
1246                        value: input.response.digest,
1247                        enable: true,
1248                    };
1249                }
1250            }
1251        } else if current_stage >= b5(3) && current_stage < b5(17) {
1252            next_stage = current_stage + b5(1);
1253            let required = segment_required_mask_kernel(next_stage);
1254            dependent_ready = control.mask_done & (b16(1) << required) != b16(0);
1255            let digit = q.message_digits[segment];
1256            let completed_step: b4 = (current_stage - b5(2)).resize();
1257            if digit == completed_step {
1258                signature_even_write = SyncBramWrite::<HashWords, SIGNATURE_ADDRESS_BITS> {
1259                    addr: (segment >> 1).resize(),
1260                    value: input.response.digest,
1261                    enable: true,
1262                };
1263                if segment & b7(1) != b7(0) {
1264                    signature_even_write.enable = false;
1265                    signature_odd_write = SyncBramWrite::<HashWords, SIGNATURE_ADDRESS_BITS> {
1266                        addr: (segment >> 1).resize(),
1267                        value: input.response.digest,
1268                        enable: true,
1269                    };
1270                }
1271            }
1272        } else {
1273            control.endpoint_done = set_segment_bit_kernel(q.control.endpoint_done, segment, true);
1274            public_key_response_guard = true;
1275            let digit = q.message_digits[segment];
1276            if digit == b4(15) {
1277                signature_even_write = SyncBramWrite::<HashWords, SIGNATURE_ADDRESS_BITS> {
1278                    addr: (segment >> 1).resize(),
1279                    value: input.response.digest,
1280                    enable: true,
1281                };
1282                if segment & b7(1) != b7(0) {
1283                    signature_even_write.enable = false;
1284                    signature_odd_write = SyncBramWrite::<HashWords, SIGNATURE_ADDRESS_BITS> {
1285                        addr: (segment >> 1).resize(),
1286                        value: input.response.digest,
1287                        enable: true,
1288                    };
1289                }
1290            }
1291        }
1292        control.segment_stage[segment] = next_stage;
1293        if dependent_ready {
1294            control.segment_ready = set_segment_bit_kernel(control.segment_ready, segment, true);
1295            response_cycle_excluded =
1296                set_segment_bit_kernel(response_cycle_excluded, segment, true);
1297        }
1298    }
1299
1300    if public_key_response {
1301        next_public_key_state = input.response.digest;
1302        control.public_key_inflight = false;
1303        control.public_key_state_ready = true;
1304        if q.control.public_key_block == b6(33) {
1305            let exact_final = q.control.total_issued == b11(1258)
1306                && q.control.total_retired == b11(1257)
1307                && q.control.private_key_consumers_remaining == b7(0)
1308                && q.control.endpoint_done[0] == b8(0xff)
1309                && q.control.endpoint_done[1] == b8(0xff)
1310                && q.control.endpoint_done[2] == b8(0xff)
1311                && q.control.endpoint_done[3] == b8(0xff)
1312                && q.control.endpoint_done[4] == b8(0xff)
1313                && q.control.endpoint_done[5] == b8(0xff)
1314                && q.control.endpoint_done[6] == b8(0xff)
1315                && q.control.endpoint_done[7] == b8(0xff)
1316                && q.control.endpoint_done[8] == b8(0x07);
1317            if exact_final {
1318                control.active = false;
1319                control.credit_reserved = true;
1320                control.result_valid = true;
1321                control.prefetch_pending = false;
1322                control.request_valid = false;
1323                done = true;
1324            } else {
1325                control.fault = true;
1326            }
1327        } else {
1328            control.public_key_block = q.control.public_key_block + b6(1);
1329        }
1330    }
1331
1332    // Complete the prior synchronous-memory prefetch into the stable request
1333    // payload. The separately reset request_valid bit owns these resetless bits.
1334    if !resetting && q.control.active && q.control.prefetch_pending && !q.control.request_valid {
1335        next_request = prepare_request_kernel(PrepareRequestInput {
1336            fields: decode_tag_kernel(q.control.prefetch_tag),
1337            private_seed: q.private_seed,
1338            private_key: q.private_key,
1339            public_seed: q.public_seed,
1340            public_key_state: q.public_key_state,
1341            state_even: q.state_even,
1342            state_odd: q.state_odd,
1343            mask: q.masks,
1344        });
1345        control.prefetch_pending = false;
1346        control.request_valid = true;
1347    }
1348
1349    // Choose a new prefetch only if the payload slot will be free. Response-
1350    // cycle exclusions enforce the one-cycle write-to-read guard.
1351    let request_slot_free = !control.request_valid && !control.prefetch_pending;
1352    let visible_segment_ready =
1353        exclude_segment_groups_kernel(control.segment_ready, response_cycle_excluded);
1354    let segment_selection =
1355        select_ready_segment_kernel(visible_segment_ready, control.segment_group_cursor);
1356    let focus_selection = select_public_key_focus_segment_kernel(
1357        visible_segment_ready,
1358        control.public_key_block,
1359        control.public_key_inflight,
1360    );
1361    let public_key_ready = !public_key_response_guard
1362        && !control.public_key_inflight
1363        && control.public_key_state_ready
1364        && public_key_endpoints_ready_kernel(control.endpoint_done, control.public_key_block);
1365    let mut selected_valid = false;
1366    let mut selected_fields = TagFields {
1367        context: control.context,
1368        generation: control.generation,
1369        ..TagFields::default()
1370    };
1371
1372    if control.active && request_slot_free {
1373        if public_key_ready {
1374            selected_valid = true;
1375            selected_fields.kind = b3(7);
1376            selected_fields.block_index = control.public_key_block;
1377        } else if control.seed_ready {
1378            selected_valid = true;
1379            selected_fields.kind = b3(0);
1380        } else if control.public_seed_ready {
1381            selected_valid = true;
1382            selected_fields.kind = b3(1);
1383        } else if control.mask_ready != b16(0) {
1384            selected_valid = true;
1385            selected_fields.kind = b3(2);
1386            if control.mask_ready & b16(0x0001) != b16(0) {
1387                selected_fields.segment = b7(0);
1388            } else if control.mask_ready & b16(0x0002) != b16(0) {
1389                selected_fields.segment = b7(1);
1390            } else if control.mask_ready & b16(0x0004) != b16(0) {
1391                selected_fields.segment = b7(2);
1392            } else if control.mask_ready & b16(0x0008) != b16(0) {
1393                selected_fields.segment = b7(3);
1394            } else if control.mask_ready & b16(0x0010) != b16(0) {
1395                selected_fields.segment = b7(4);
1396            } else if control.mask_ready & b16(0x0020) != b16(0) {
1397                selected_fields.segment = b7(5);
1398            } else if control.mask_ready & b16(0x0040) != b16(0) {
1399                selected_fields.segment = b7(6);
1400            } else if control.mask_ready & b16(0x0080) != b16(0) {
1401                selected_fields.segment = b7(7);
1402            } else if control.mask_ready & b16(0x0100) != b16(0) {
1403                selected_fields.segment = b7(8);
1404            } else if control.mask_ready & b16(0x0200) != b16(0) {
1405                selected_fields.segment = b7(9);
1406            } else if control.mask_ready & b16(0x0400) != b16(0) {
1407                selected_fields.segment = b7(10);
1408            } else if control.mask_ready & b16(0x0800) != b16(0) {
1409                selected_fields.segment = b7(11);
1410            } else if control.mask_ready & b16(0x1000) != b16(0) {
1411                selected_fields.segment = b7(12);
1412            } else if control.mask_ready & b16(0x2000) != b16(0) {
1413                selected_fields.segment = b7(13);
1414            } else if control.mask_ready & b16(0x4000) != b16(0) {
1415                selected_fields.segment = b7(14);
1416            } else {
1417                selected_fields.segment = b7(15);
1418            }
1419        } else if focus_selection.valid {
1420            selected_valid = true;
1421            selected_fields = segment_task_fields_kernel(
1422                control.segment_stage[focus_selection.segment],
1423                focus_selection.segment,
1424                control.context,
1425                control.generation,
1426            );
1427        } else if segment_selection.valid {
1428            selected_valid = true;
1429            selected_fields = segment_task_fields_kernel(
1430                control.segment_stage[segment_selection.segment],
1431                segment_selection.segment,
1432                control.context,
1433                control.generation,
1434            );
1435            control.segment_group_cursor = segment_selection.next_group;
1436        }
1437    }
1438
1439    if selected_valid {
1440        control.prefetch_pending = true;
1441        control.prefetch_tag = encode_tag_kernel(selected_fields);
1442        if selected_fields.kind >= b3(3) && selected_fields.kind <= b3(6) {
1443            state_read_addr = (selected_fields.segment >> 1).resize();
1444        } else if selected_fields.kind == b3(7) {
1445            state_read_addr = selected_fields.block_index;
1446        }
1447        if selected_fields.kind == b3(4) {
1448            mask_read_addr = b4(0);
1449        } else if selected_fields.kind == b3(6) {
1450            mask_read_addr = selected_fields.chain_step;
1451        }
1452    }
1453
1454    // Retained-result reads use the same state banks only after the scheduler
1455    // has released the context.
1456    control.result_read_pending = false;
1457    if !resetting
1458        && q.control.result_valid
1459        && input.result_read_enable
1460        && input.result_read_pair < b6(34)
1461        && !input.result_taken
1462    {
1463        state_read_addr = input.result_read_pair;
1464        signature_read_addr = input.result_read_pair;
1465        control.result_read_pending = true;
1466        control.result_read_pair = input.result_read_pair;
1467    }
1468
1469    // Resetless memories must be explicitly write-disabled on reset assertion;
1470    // they do not observe the reset member of ClockReset themselves.
1471    if resetting {
1472        state_even_write.enable = false;
1473        state_odd_write.enable = false;
1474        mask_write.enable = false;
1475        signature_even_write.enable = false;
1476        signature_odd_write.enable = false;
1477    }
1478
1479    let mut request = q.request;
1480    request.valid = !resetting && q.control.request_valid;
1481    let output = ContextEngineOutput {
1482        start_ready,
1483        start_rejected,
1484        active: !resetting && q.control.active,
1485        output_credit_reserved: !resetting && q.control.credit_reserved,
1486        request,
1487        response_ready: response_accepted,
1488        response_rejected,
1489        fault: !resetting && q.control.fault,
1490        done,
1491        output_credit_release: credit_release,
1492        result_valid: !resetting && q.control.result_valid,
1493        public_seed: hash_words_to_bytes_kernel(q.public_seed),
1494        public_key_hash: hash_words_to_bytes_kernel(q.public_key_state),
1495        result_read_valid: !resetting && q.control.result_valid && q.control.result_read_pending,
1496        result_read_pair: q.control.result_read_pair,
1497        signature_first: hash_words_to_bytes_kernel(q.signature.even),
1498        signature_second: hash_words_to_bytes_kernel(q.signature.odd),
1499        result_read_second_valid: !resetting
1500            && q.control.result_valid
1501            && q.control.result_read_pending
1502            && q.control.result_read_pair < b6(33),
1503        endpoint_first: hash_words_to_bytes_kernel(q.state_even),
1504        endpoint_second: hash_words_to_bytes_kernel(q.state_odd),
1505        total_issued: q.control.total_issued,
1506        total_retired: q.control.total_retired,
1507        public_key_block: q.control.public_key_block,
1508        private_key_consumers_remaining: q.control.private_key_consumers_remaining,
1509    };
1510
1511    let d = D {
1512        control,
1513        request: next_request,
1514        private_seed: next_private_seed,
1515        message_digits: next_message_digits,
1516        private_key: next_private_key,
1517        public_seed: next_public_seed,
1518        public_key_state: next_public_key_state,
1519        state_even: SyncBramIn::<HashWords, STATE_BANK_ADDRESS_BITS> {
1520            read_addr: state_read_addr,
1521            write: state_even_write,
1522        },
1523        state_odd: SyncBramIn::<HashWords, STATE_BANK_ADDRESS_BITS> {
1524            read_addr: state_read_addr,
1525            write: state_odd_write,
1526        },
1527        masks: SyncBramIn::<HashWords, MASK_ADDRESS_BITS> {
1528            read_addr: mask_read_addr,
1529            write: mask_write,
1530        },
1531        signature: SignatureBanksInput {
1532            read_addr: signature_read_addr,
1533            even_write: signature_even_write,
1534            odd_write: signature_odd_write,
1535        },
1536    };
1537    (output, d)
1538}