Skip to main content

u280_shell_rhdl/
writer.rs

1//! HBM1 payload writer state, frame queue, and address helpers.
2
3use rhdl::prelude::*;
4
5use crate::axi::AXI_RESP_OKAY;
6
7/// HBM1 payload writer state.
8#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
9pub enum WriterState {
10    /// No frame is committed to HBM1.
11    #[default]
12    Idle,
13    /// `AWVALID` is asserted; the frame is committed even before acceptance.
14    Address,
15    /// Exactly 35 data handshakes are required.
16    Data,
17    /// The sole payload response is being drained.
18    Response,
19}
20
21/// Oldest-first queue of the two complete BRAM frame slots.
22#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
23pub struct FrameQueue {
24    /// Occupancy in `0..=2`.
25    pub count: b2,
26    /// Oldest slot.
27    pub head_slot: b1,
28    /// Job stored in the oldest slot.
29    pub head_job: b32,
30    /// Newest slot when count is two.
31    pub tail_slot: b1,
32    /// Job stored in the newest slot.
33    pub tail_job: b32,
34}
35
36/// One dequeue and one enqueue can occur on the same cycle.
37#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
38pub struct FrameQueueOperation {
39    /// Remove the oldest slot after its B response is drained.
40    pub dequeue: bool,
41    /// Append one newly completed canonical frame.
42    pub enqueue: bool,
43    /// New slot identifier.
44    pub slot: b1,
45    /// New frame job identity.
46    pub job: b32,
47}
48
49/// Updated queue plus an impossible underflow/overflow indication.
50#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
51pub struct FrameQueueUpdate {
52    /// Next queue contents.
53    pub queue: FrameQueue,
54    /// Operation was not legal for the prior occupancy.
55    pub fatal: bool,
56}
57
58/// Apply simultaneous retirement and capture without losing queue order.
59#[kernel]
60pub fn frame_queue_update_kernel(
61    current: FrameQueue,
62    operation: FrameQueueOperation,
63) -> FrameQueueUpdate {
64    let mut next = current;
65    let mut fatal = current.count > b2(2);
66    if operation.dequeue && operation.enqueue {
67        if current.count == b2(1) {
68            next.count = b2(1);
69            next.head_slot = operation.slot;
70            next.head_job = operation.job;
71        } else if current.count == b2(2) {
72            next.count = b2(2);
73            next.head_slot = current.tail_slot;
74            next.head_job = current.tail_job;
75            next.tail_slot = operation.slot;
76            next.tail_job = operation.job;
77        } else {
78            fatal = true;
79        }
80    } else if operation.dequeue {
81        if current.count == b2(1) {
82            next.count = b2(0);
83        } else if current.count == b2(2) {
84            next.count = b2(1);
85            next.head_slot = current.tail_slot;
86            next.head_job = current.tail_job;
87        } else {
88            fatal = true;
89        }
90    } else if operation.enqueue {
91        if current.count == b2(0) {
92            next.count = b2(1);
93            next.head_slot = operation.slot;
94            next.head_job = operation.job;
95        } else if current.count == b2(1) {
96            next.count = b2(2);
97            next.tail_slot = operation.slot;
98            next.tail_job = operation.job;
99        } else {
100            fatal = true;
101        }
102    }
103    FrameQueueUpdate { queue: next, fatal }
104}
105
106/// Derive the 4 KiB output-slot address without a multiplier.
107#[kernel]
108pub fn output_job_address_kernel(base: b64, job_id: b32) -> b64 {
109    let wide_job: b64 = job_id.resize();
110    base + (wide_job << 12)
111}
112
113/// Payload and summary responses accept only ID-zero `OKAY`.
114#[kernel]
115pub fn output_response_is_canonical_kernel(bid: b1, bresp: b2) -> bool {
116    bid == b1(0) && bresp == AXI_RESP_OKAY
117}
118
119/// A slot is unavailable while queued or reserved by active capture.
120#[kernel]
121pub fn frame_slot_is_reserved_kernel(
122    queue: FrameQueue,
123    capture_active: bool,
124    capture_slot: b1,
125    candidate: b1,
126) -> bool {
127    let capture_match = capture_active && capture_slot == candidate;
128    let head_match = queue.count != b2(0) && queue.head_slot == candidate;
129    let tail_match = queue.count == b2(2) && queue.tail_slot == candidate;
130    capture_match || head_match || tail_match
131}
132
133/// Timing of canonical payload B retirements, independent of summary traffic.
134#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
135pub struct MemoryCompletionTiming {
136    /// At least one payload retired on exact ID-zero `OKAY`.
137    pub seen: bool,
138    /// Cycle of the first canonical payload B handshake.
139    pub first: b64,
140    /// Cycle of the most recent canonical payload B handshake.
141    pub last: b64,
142    /// `last - first`, zero until at least two payloads retire.
143    pub span: b64,
144}
145
146/// Record one payload response only when it is canonical and successful.
147#[kernel]
148pub fn record_memory_completion_kernel(
149    current: MemoryCompletionTiming,
150    cycle: b64,
151    canonical_payload_b: bool,
152) -> MemoryCompletionTiming {
153    let mut next = current;
154    if canonical_payload_b {
155        if current.seen {
156            next.last = cycle;
157            next.span = cycle - current.first;
158        } else {
159            next.seen = true;
160            next.first = cycle;
161            next.last = cycle;
162            next.span = b64(0);
163        }
164    }
165    next
166}