pwm.rsannotatedpwm.rssource385 lines · 12.0 KB · raw
1//! 32X PWM sound chip
2
3mod debug;
4
5use crate::api::Sega32XAudioOutput;
6use crate::registers::SystemRegisters;
7use bincode::{Decode, Encode};
8use jgenesis_common::frontend::TimingMode;
9use jgenesis_common::num::GetBit;
10use std::cmp;
11use std::collections::VecDeque;
12use std::fmt::{Display, Formatter};
13
14// 53.693175 MHz * 3 / 7 / (1047 - 1) ~= 22 KHz
15const TWENTY_TWO_KHZ_CYCLE_REGISTER: u16 = 1047;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
18pub enum OutputDirection {
19    #[default]
20    Off = 0,
21    Same = 1,
22    Opposite = 2,
23    Prohibited = 3,
24}
25
26impl OutputDirection {
27    fn from_value(value: u16) -> Self {
28        match value & 3 {
29            0 => Self::Off,
30            1 => Self::Same,
31            2 => Self::Opposite,
32            3 => Self::Prohibited,
33            _ => unreachable!("value & 3 is always <= 3"),
34        }
35    }
36
37    fn is_off(self) -> bool {
38        matches!(self, Self::Off | Self::Prohibited)
39    }
40}
41
42impl Display for OutputDirection {
43    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
44        match self {
45            Self::Off => write!(f, "Off"),
46            Self::Same => write!(f, "Same side"),
47            Self::Opposite => write!(f, "Opposite side"),
48            Self::Prohibited => write!(f, "Prohibited"),
49        }
50    }
51}
52
53#[derive(Debug, Clone, Copy, Encode, Decode)]
54pub struct PwmControl {
55    pub timer_interval: u16,
56    pub dreq1_enabled: bool,
57    pub l_out: OutputDirection,
58    pub r_out: OutputDirection,
59}
60
61impl PwmControl {
62    fn new() -> Self {
63        Self {
64            timer_interval: 0,
65            dreq1_enabled: false,
66            l_out: OutputDirection::default(),
67            r_out: OutputDirection::default(),
68        }
69    }
70
71    fn effective_timer_interval(self) -> u16 {
72        if self.timer_interval == 0 { 16 } else { self.timer_interval }
73    }
74
75    // 68000: $A15130
76    // SH-2: $4030
77    fn read(self) -> u16 {
78        (self.timer_interval << 8)
79            | (u16::from(self.dreq1_enabled) << 7)
80            | ((self.r_out as u16) << 2)
81            | (self.l_out as u16)
82    }
83
84    // 68000: $A15130
85    fn m68k_write(&mut self, value: u16) {
86        self.r_out = OutputDirection::from_value(value >> 2);
87        self.l_out = OutputDirection::from_value(value);
88        // M68K cannot change timer interval or RTP / DREQ1 enable
89
90        log::debug!("PWM control write: {value:04X}");
91        log::debug!("  L channel output direction: {:?}", self.l_out);
92        log::debug!("  R channel output direction: {:?}", self.r_out);
93    }
94
95    // SH-2: $4030
96    fn sh2_write(&mut self, value: u16) {
97        self.timer_interval = (value >> 8) & 0xF;
98        self.dreq1_enabled = value.bit(7);
99        self.r_out = OutputDirection::from_value(value >> 2);
100        self.l_out = OutputDirection::from_value(value);
101
102        log::debug!("PWM control write: {value:04X}");
103        log::debug!("  Effective timer interval: {}", self.effective_timer_interval());
104        log::debug!("  DREQ1 enabled: {}", self.dreq1_enabled);
105        log::debug!("  L channel output direction: {:?}", self.l_out);
106        log::debug!("  R channel output direction: {:?}", self.r_out);
107    }
108}
109
110const FIFO_LEN: usize = 3;
111
112#[derive(Debug, Clone, Encode, Decode)]
113pub struct PwmFifo(VecDeque<u16>);
114
115impl PwmFifo {
116    pub fn new() -> Self {
117        Self(VecDeque::with_capacity(FIFO_LEN))
118    }
119
120    pub fn push(&mut self, sample: u16) {
121        if self.0.len() == FIFO_LEN {
122            self.0.pop_front();
123        }
124        self.0.push_back(sample);
125    }
126
127    fn pop(&mut self) -> Option<u16> {
128        self.0.pop_front()
129    }
130
131    fn is_empty(&self) -> bool {
132        self.0.is_empty()
133    }
134
135    fn is_full(&self) -> bool {
136        self.0.len() == FIFO_LEN
137    }
138}
139
140#[derive(Debug, Clone, Encode, Decode)]
141pub struct PwmChip {
142    pub control: PwmControl,
143    pub cycle_register: u16,
144    l_fifo: PwmFifo,
145    r_fifo: PwmFifo,
146    l_output: u16,
147    r_output: u16,
148    cycle_counter: u64,
149    off_cycle_counter: u64,
150    timer_counter: u16,
151    dreq1: bool,
152    genesis_mclk_frequency: f64,
153}
154
155// Cycle register and pulse width are unsigned 12-bit values
156const U12_MASK: u16 = (1 << 12) - 1;
157
158impl PwmChip {
159    pub fn new(timing_mode: TimingMode) -> Self {
160        Self {
161            control: PwmControl::new(),
162            cycle_register: 0,
163            l_fifo: PwmFifo::new(),
164            r_fifo: PwmFifo::new(),
165            l_output: 0,
166            r_output: 0,
167            cycle_counter: U12_MASK.into(),
168            off_cycle_counter: U12_MASK.into(),
169            timer_counter: 16,
170            dreq1: false,
171            genesis_mclk_frequency: match timing_mode {
172                TimingMode::Ntsc => genesis_components::NTSC_GENESIS_MCLK_FREQUENCY,
173                TimingMode::Pal => genesis_components::PAL_GENESIS_MCLK_FREQUENCY,
174            },
175        }
176    }
177
178    pub fn tick(
179        &mut self,
180        mut sh2_cycles: u64,
181        system_registers: &mut SystemRegisters,
182        audio_output: &mut impl Sega32XAudioOutput,
183    ) {
184        if (self.control.l_out.is_off() && self.control.r_out.is_off()) || self.cycle_register == 1
185        {
186            // PWM counters are stopped when both channels are off
187            // Output 0 samples at ~22 KHz
188            audio_output.update_pwm_source_frequency(compute_sample_rate(
189                self.genesis_mclk_frequency,
190                TWENTY_TWO_KHZ_CYCLE_REGISTER,
191            ));
192
193            while sh2_cycles != 0 {
194                let prev_cycle_counter = self.off_cycle_counter;
195                self.off_cycle_counter = self.off_cycle_counter.saturating_sub(sh2_cycles);
196                sh2_cycles -= prev_cycle_counter - self.off_cycle_counter;
197
198                if self.off_cycle_counter == 0 {
199                    self.off_cycle_counter = (TWENTY_TWO_KHZ_CYCLE_REGISTER - 1).into();
200                    audio_output.collect_pwm((0.0, 0.0));
201                }
202            }
203
204            return;
205        }
206
207        audio_output.update_pwm_source_frequency(compute_sample_rate(
208            self.genesis_mclk_frequency,
209            self.cycle_register,
210        ));
211
212        while sh2_cycles != 0 {
213            let prev_cycle_counter = self.cycle_counter;
214            self.cycle_counter = self.cycle_counter.saturating_sub(sh2_cycles);
215            sh2_cycles -= prev_cycle_counter - self.cycle_counter;
216
217            if self.cycle_counter == 0 {
218                // Cycle counter is always set to (register - 1), wrapping from 0 to 4095
219                let cycle_register = self.cycle_register;
220                self.cycle_counter = (cycle_register.wrapping_sub(1) & U12_MASK).into();
221
222                self.l_output = self.l_fifo.pop().unwrap_or(self.l_output);
223                self.r_output = self.r_fifo.pop().unwrap_or(self.r_output);
224
225                let sample_l = match self.control.l_out {
226                    OutputDirection::Same => pulse_width_to_f64(self.l_output, cycle_register),
227                    OutputDirection::Opposite => pulse_width_to_f64(self.r_output, cycle_register),
228                    _ => 0.0,
229                };
230                let sample_r = match self.control.r_out {
231                    OutputDirection::Same => pulse_width_to_f64(self.r_output, cycle_register),
232                    OutputDirection::Opposite => pulse_width_to_f64(self.l_output, cycle_register),
233                    _ => 0.0,
234                };
235                audio_output.collect_pwm((sample_l, sample_r));
236
237                self.timer_counter -= 1;
238                if self.timer_counter == 0 {
239                    self.timer_counter = self.control.effective_timer_interval();
240
241                    log::trace!("Generating PWM interrupt");
242                    system_registers.notify_pwm_timer();
243
244                    self.dreq1 |= self.control.dreq1_enabled;
245                }
246            }
247        }
248    }
249
250    pub fn read_register(&self, address: u32) -> u16 {
251        log::trace!("PWM register read {address:08X}");
252
253        match address & 0xF {
254            0x0 => self.control.read(),
255            0x2 => self.cycle_register,
256            0x4 => self.read_l_fifo_status(),
257            0x6 => self.read_r_fifo_status(),
258            0x8 => self.read_mono_fifo_status(),
259            _ => {
260                log::warn!("Invalid PWM register read {address:08X}");
261                0
262            }
263        }
264    }
265
266    fn write_register(
267        &mut self,
268        address: u32,
269        value: u16,
270        control_write_fn: impl FnOnce(&mut PwmControl, u16),
271    ) {
272        match address & 0xF {
273            0x0 => {
274                control_write_fn(&mut self.control, value);
275                self.dreq1 &= self.control.dreq1_enabled;
276            }
277            0x2 => self.write_cycle_register(value),
278            0x4 => self.write_l_fifo(value),
279            0x6 => self.write_r_fifo(value),
280            0x8 => self.write_mono_fifo(value),
281            _ => {
282                // BC Racers frequently writes to $403A for some reason
283                log::debug!("Invalid PWM register write: {address:08X} {value:04X}");
284            }
285        }
286    }
287
288    pub fn m68k_write_register(&mut self, address: u32, value: u16) {
289        self.write_register(address, value, PwmControl::m68k_write);
290    }
291
292    pub fn sh2_write_register(&mut self, address: u32, value: u16) {
293        self.write_register(address, value, PwmControl::sh2_write);
294    }
295
296    // 68000: $A15132
297    // SH-2: $4032
298    fn write_cycle_register(&mut self, value: u16) {
299        self.cycle_register = value & U12_MASK;
300
301        log::debug!("Cycle register write: {value:04X}");
302        log::debug!(
303            "  Effective sample rate: {} Hz",
304            53_693_175.0 * 3.0 / 7.0 / f64::from(self.cycle_register.wrapping_sub(1) & U12_MASK)
305        );
306    }
307
308    // 68000: $A15134
309    // SH-2: $4034
310    fn read_l_fifo_status(&self) -> u16 {
311        (u16::from(self.l_fifo.is_full()) << 15) | (u16::from(self.l_fifo.is_empty()) << 14)
312    }
313
314    // 68000: $A15136
315    // SH-2: $4036
316    fn read_r_fifo_status(&self) -> u16 {
317        (u16::from(self.r_fifo.is_full()) << 15) | (u16::from(self.r_fifo.is_empty()) << 14)
318    }
319
320    // 68000: $A15138
321    // SH-2: $4038
322    fn read_mono_fifo_status(&self) -> u16 {
323        // TODO is this right?
324        let full = self.l_fifo.is_full() || self.r_fifo.is_full();
325        let empty = self.l_fifo.is_empty() && self.r_fifo.is_empty();
326        (u16::from(full) << 15) | (u16::from(empty) << 14)
327    }
328
329    // 68000: $A15134
330    // SH-2: $4034
331    fn write_l_fifo(&mut self, value: u16) {
332        let sample = value & U12_MASK;
333        self.l_fifo.push(sample);
334
335        log::trace!("L pulse width FIFO write: {value:04X}");
336        log::trace!("  Effective wave height: {sample}");
337    }
338
339    // 68000: $A15136
340    // SH-2: $4036
341    fn write_r_fifo(&mut self, value: u16) {
342        let sample = value & U12_MASK;
343        self.r_fifo.push(sample);
344
345        log::trace!("R pulse width FIFO write: {value:04X}");
346        log::trace!("  Effective wave height: {sample}");
347    }
348
349    // 68000: $A15138
350    // SH-2: $4038
351    fn write_mono_fifo(&mut self, value: u16) {
352        let sample = value & U12_MASK;
353        self.l_fifo.push(sample);
354        self.r_fifo.push(sample);
355
356        log::trace!("Mono pulse width FIFO write: {value:04X}");
357        log::trace!("  Effective wave height: {sample}");
358    }
359
360    pub fn dma_request_1(&self) -> bool {
361        self.dreq1
362    }
363
364    pub fn acknowledge_dreq_1(&mut self) {
365        self.dreq1 = false;
366    }
367}
368
369fn compute_sample_rate(genesis_mclk_frequency: f64, cycle_register: u16) -> f64 {
370    genesis_mclk_frequency * 3.0 / 7.0 / f64::from(cycle_register.wrapping_sub(1) & U12_MASK)
371}
372
373fn pulse_width_to_f64(sample: u16, cycle_register: u16) -> f64 {
374    if cycle_register == 1 {
375        return 0.0;
376    }
377
378    // Treat the pulse width as a sample on a scale from 0 to (cycle_register - 1) and map that to [0, 1]
379    let max_width = cycle_register.wrapping_sub(1) & U12_MASK;
380    let clamped_width = cmp::min(sample, max_width);
381
382    // TODO this is wrong - should treat PWM output as unsigned and maybe high-pass filter to shift the center to 0
383    let divisor = 0.5 * f64::from(max_width);
384    (f64::from(clamped_width) - divisor) / divisor
385}