1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
/// PIO FIFO with configurable depth (4 normal, 8 joined).
pub struct PioFifo {
buf: [u32; 8],
head: u8,
tail: u8,
count: u8,
depth: u8,
/// Diagnostic counter — number of `push` calls that successfully
/// stored a value (FIFO had room). Pure observation, never read by
/// FIFO logic. Wraps on `u64` overflow (in practice unreachable
/// within a single emulator session). Used by the PicoGUS bring-up
/// harness to confirm the DMA→TXF path is actually landing bytes.
pub push_success: u64,
/// Diagnostic counter — number of `push` calls that found the FIFO
/// full and silently dropped the value. Paired with `push_success`;
/// non-zero in production code indicates either DMA outpacing the
/// SM (back-pressure intended) or a missing DREQ throttle.
pub push_drop: u64,
}
impl PioFifo {
pub fn new(depth: u8) -> Self {
Self {
buf: [0; 8],
head: 0,
tail: 0,
count: 0,
depth,
push_success: 0,
push_drop: 0,
}
}
/// Push a value. Returns false if the FIFO is full (value dropped).
pub fn push(&mut self, val: u32) -> bool {
if self.count >= self.depth {
self.push_drop = self.push_drop.wrapping_add(1);
return false;
}
self.buf[self.tail as usize] = val;
self.tail = (self.tail + 1) % self.depth;
self.count += 1;
self.push_success = self.push_success.wrapping_add(1);
true
}
/// Pop a value. Returns None if the FIFO is empty.
pub fn pop(&mut self) -> Option<u32> {
if self.count == 0 {
return None;
}
let val = self.buf[self.head as usize];
self.head = (self.head + 1) % self.depth;
self.count -= 1;
Some(val)
}
pub fn is_full(&self) -> bool {
self.depth > 0 && self.count >= self.depth
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn level(&self) -> u8 {
self.count
}
pub fn flush(&mut self) {
self.head = 0;
self.tail = 0;
self.count = 0;
}
pub fn set_depth(&mut self, d: u8) {
self.depth = d;
self.flush();
}
}
#[cfg(test)]
mod tests {
//! Push success / drop counter tests.
//!
//! Wired up before the counter fields existed (TDD RED phase) — the
//! original failures were on `assert_eq!(fifo.push_success, …)` /
//! `assert_eq!(fifo.push_drop, …)` because the fields weren't
//! defined. They go GREEN once `push_success` / `push_drop` land in
//! `PioFifo` and `push` increments them.
use super::*;
#[test]
fn counters_initialize_to_zero() {
let fifo = PioFifo::new(4);
assert_eq!(fifo.push_success, 0);
assert_eq!(fifo.push_drop, 0);
}
#[test]
fn push_success_count_bumps_on_successful_push() {
let mut fifo = PioFifo::new(4);
assert!(fifo.push(0xAA));
assert!(fifo.push(0xBB));
assert!(fifo.push(0xCC));
assert_eq!(fifo.push_success, 3);
assert_eq!(fifo.push_drop, 0);
}
#[test]
fn push_drop_count_bumps_when_full() {
let mut fifo = PioFifo::new(4);
// Fill it.
for v in 0..4u32 {
assert!(fifo.push(v));
}
assert_eq!(fifo.push_success, 4);
assert_eq!(fifo.push_drop, 0);
// 5th push drops.
assert!(!fifo.push(0xDEAD));
assert_eq!(fifo.push_success, 4);
assert_eq!(fifo.push_drop, 1);
// Three more pushes while full keep dropping.
for _ in 0..3 {
assert!(!fifo.push(0xBEEF));
}
assert_eq!(fifo.push_success, 4);
assert_eq!(fifo.push_drop, 4);
}
#[test]
fn counters_survive_pop_and_push_mix() {
let mut fifo = PioFifo::new(4);
for v in 0..4u32 {
assert!(fifo.push(v));
}
assert_eq!(fifo.pop(), Some(0));
assert_eq!(fifo.pop(), Some(1));
// Two free slots — two more pushes succeed.
assert!(fifo.push(0x10));
assert!(fifo.push(0x11));
assert_eq!(fifo.push_success, 6);
assert_eq!(fifo.push_drop, 0);
// Now full again — 3rd push drops.
assert!(!fifo.push(0x12));
assert_eq!(fifo.push_success, 6);
assert_eq!(fifo.push_drop, 1);
}
/// `is_full` returns false for a depth=0 FIFO (the "joined-away" side
/// of a FIFO-join pair — see `PioBlock::apply_fifo_join`). Covers the
/// short-circuit left arm of `depth > 0 && count >= depth` at line 59.
#[test]
fn is_full_is_false_on_depth_zero_fifo() {
let mut fifo = PioFifo::new(0);
assert!(!fifo.is_full(), "depth=0 FIFO must not report full");
assert!(fifo.is_empty(), "depth=0 FIFO is trivially empty");
// And push on a depth=0 FIFO always drops (covers line 36 drop arm).
assert!(!fifo.push(0xAA));
assert_eq!(fifo.push_drop, 1);
assert_eq!(fifo.push_success, 0);
// pop returns None — covers the count==0 guard of `pop`.
assert_eq!(fifo.pop(), None);
}
/// Cover `set_depth` and `flush`: after expanding depth the FIFO is
/// usable; after `flush` it drops buffered entries and reports empty.
#[test]
fn set_depth_flushes_and_allows_reuse() {
let mut fifo = PioFifo::new(4);
assert!(fifo.push(1));
assert!(fifo.push(2));
assert_eq!(fifo.level(), 2);
fifo.flush();
assert!(fifo.is_empty());
assert_eq!(fifo.level(), 0);
// Grow to 8 (FJOIN case) — push 5 values succeeds.
fifo.set_depth(8);
for v in 0..5u32 {
assert!(fifo.push(v));
}
assert_eq!(fifo.level(), 5);
}
}