fifo.rsannotatedfifo.rssource170 lines · 4.1 KB · raw
1use crate::vdp::{DataPortLocation, DataPortMode};
2use bincode::{Decode, Encode};
3use std::array;

Direct color DMA demos demonstrate that there's a ~3-slot latency between when an entry is written to the FIFO and when it can be popped

7pub const INITIAL_FIFO_LATENCY: u8 = 3;
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
10pub enum VramWriteFlag {
11    First,
12    Second,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
16pub enum VramWriteSize {
17    Word,
18    Byte,
19}
20
21#[derive(Debug, Clone, Encode, Decode)]
22pub struct VdpFifoEntry {
23    pub mode: DataPortMode,
24    pub location: DataPortLocation,
25    pub address: u32,
26    pub word: u16,
27    pub size: VramWriteSize,
28    pub vram_write: VramWriteFlag,
29    pub latency: u8,
30}
31
32impl VdpFifoEntry {
33    pub fn new(
34        mode: DataPortMode,
35        location: DataPortLocation,
36        address: u32,
37        word: u16,
38        size: VramWriteSize,
39    ) -> Self {
40        Self {
41            mode,
42            location,
43            address,
44            word,
45            size,
46            vram_write: VramWriteFlag::First,
47            latency: INITIAL_FIFO_LATENCY,
48        }
49    }
50}
51
52impl Default for VdpFifoEntry {
53    fn default() -> Self {
54        Self {
55            mode: DataPortMode::Read,
56            location: DataPortLocation::Vram,
57            address: 0,
58            word: 0,
59            size: VramWriteSize::Word,
60            vram_write: VramWriteFlag::First,
61            latency: INITIAL_FIFO_LATENCY,
62        }
63    }
64}
65
66const FIFO_LEN: u8 = 4;
67
68#[derive(Debug, Clone, Encode, Decode)]
69pub struct VdpFifo {
70    buffer: [VdpFifoEntry; FIFO_LEN as usize],
71    push_idx: u8,
72    pop_idx: u8,
73    len: u8,
74}
75
76impl VdpFifo {
77    pub fn new() -> Self {
78        Self {
79            buffer: array::from_fn(|_| VdpFifoEntry::default()),
80            push_idx: 0,
81            pop_idx: 0,
82            len: 0,
83        }
84    }
85
86    pub fn push(&mut self, entry: VdpFifoEntry) {
87        debug_assert!(self.len < FIFO_LEN);
88
89        self.buffer[self.push_idx as usize] = entry;
90        self.push_idx = (self.push_idx + 1) % FIFO_LEN;
91        self.len += 1;
92    }
93
94    pub fn front(&self) -> &VdpFifoEntry {
95        &self.buffer[self.pop_idx as usize]
96    }
97
98    pub fn next_slot_word(&self) -> u16 {
99        self.buffer[self.push_idx as usize].word
100    }
101
102    pub fn pop(&mut self) {
103        debug_assert_ne!(self.len, 0);
104
105        // Clue depends on FIFO entries for invalid targets taking 2 slots to pop, same as VRAM writes.
106        // Otherwise main menu graphics will be randomly corrupted
107        let front = &mut self.buffer[self.pop_idx as usize];
108        if matches!(front.location, DataPortLocation::Vram | DataPortLocation::Invalid)
109            && front.size == VramWriteSize::Word
110            && front.vram_write == VramWriteFlag::First
111        {
112            front.vram_write = VramWriteFlag::Second;
113            return;
114        }
115
116        self.pop_idx = (self.pop_idx + 1) % FIFO_LEN;
117        self.len -= 1;
118    }
119
120    pub fn decrement_latency(&mut self) {
121        for i in 0..self.len {
122            let idx = (self.pop_idx + i) % 4;
123            let entry = &mut self.buffer[idx as usize];
124            entry.latency = entry.latency.saturating_sub(1);
125        }
126    }
127
128    pub fn is_empty(&self) -> bool {
129        self.len == 0
130    }
131
132    pub fn is_full(&self) -> bool {
133        self.len == FIFO_LEN
134    }
135
136    pub fn len(&self) -> u8 {
137        self.len
138    }
139
140    pub fn iter(&self) -> VdpFifoIter<'_> {
141        VdpFifoIter { buffer: &self.buffer, idx: self.pop_idx, remaining: self.len }
142    }
143}
144
145pub struct VdpFifoIter<'fifo> {
146    buffer: &'fifo [VdpFifoEntry; FIFO_LEN as usize],
147    idx: u8,
148    remaining: u8,
149}
150
151impl<'fifo> Iterator for VdpFifoIter<'fifo> {
152    type Item = &'fifo VdpFifoEntry;
153
154    fn next(&mut self) -> Option<Self::Item> {
155        if self.remaining == 0 {
156            return None;
157        }
158
159        let value = &self.buffer[self.idx as usize];
160
161        self.idx = (self.idx + 1) % FIFO_LEN;
162        self.remaining -= 1;
163
164        Some(value)
165    }
166
167    fn size_hint(&self) -> (usize, Option<usize>) {
168        (self.remaining.into(), Some(self.remaining.into()))
169    }
170}