Skip to content

Commit

Permalink
[WIP] restructure handling of CA1, CA2, CB1, CB2 lines on MOS 652x chips
Browse files Browse the repository at this point in the history
  • Loading branch information
breqdev committed Apr 14, 2024
1 parent e8dcd25 commit 83adeb4
Show file tree
Hide file tree
Showing 9 changed files with 331 additions and 169 deletions.
13 changes: 5 additions & 8 deletions src/memory/mos6510.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ impl Memory for Mos6510Port {
fn read(&mut self, address: u16) -> u8 {
match address % 2 {
0 => self.ddr,
1 => (self.port.read() & !self.ddr) | (self.writes & self.ddr),
1 => (self.port.read_data() & !self.ddr) | (self.writes & self.ddr),
_ => unreachable!(),
}
}
Expand All @@ -36,11 +36,11 @@ impl Memory for Mos6510Port {
match address % 2 {
0 => {
self.ddr = value;
self.port.write(self.writes & self.ddr);
self.port.write_data(self.writes & self.ddr);
}
1 => {
self.writes = value;
self.port.write(value & self.ddr);
self.port.write_data(value & self.ddr);
}
_ => unreachable!(),
}
Expand All @@ -50,10 +50,7 @@ impl Memory for Mos6510Port {
self.port.reset();
}

fn poll(&mut self, cycles_since_poll: u64, total_cycle_count: u64) -> ActiveInterrupt {
match self.port.poll(cycles_since_poll, total_cycle_count) {
true => ActiveInterrupt::IRQ,
false => ActiveInterrupt::None,
}
fn poll(&mut self, _cycles_since_poll: u64, _total_cycle_count: u64) -> ActiveInterrupt {
ActiveInterrupt::None
}
}
43 changes: 22 additions & 21 deletions src/memory/mos652x/cia.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,10 @@ impl Memory for Cia {
}
0x0C => self.shift_register.data,
0x0D => {
// TODO: alarm and shift register flags
let value = self
.interrupts
.read_flags((self.timer_a.interrupt as u8) | (self.timer_b.interrupt as u8) << 1);

self.timer_a.interrupt = false;
self.timer_b.interrupt = false;
let value = self.interrupts.read_flags();

// clear the interrupt flags
self.interrupts.clear_flag(value);
value
}
0x0E => {
Expand Down Expand Up @@ -209,25 +205,30 @@ impl Memory for Cia {
}

fn poll(&mut self, cycles_since_poll: u64, total_cycle_count: u64) -> ActiveInterrupt {
if self.timer_a.poll(cycles_since_poll, total_cycle_count)
&& (self.interrupts.interrupt_enable & interrupt_bits::TIMER_A) != 0
{
return ActiveInterrupt::IRQ;
}
let mut active_interrupt = ActiveInterrupt::None;

if self.timer_b.poll(cycles_since_poll, total_cycle_count)
&& (self.interrupts.interrupt_enable & interrupt_bits::TIMER_B) != 0
{
return ActiveInterrupt::IRQ;
if self.timer_a.poll(cycles_since_poll, total_cycle_count) {
self.interrupts.set_flag(interrupt_bits::TIMER_A);

if (self.interrupts.interrupt_enable & interrupt_bits::TIMER_A) != 0 {
active_interrupt = ActiveInterrupt::IRQ;
}
}

if self.a.poll(cycles_since_poll, total_cycle_count)
|| self.b.poll(cycles_since_poll, total_cycle_count)
{
return ActiveInterrupt::IRQ;
if self.timer_b.poll(cycles_since_poll, total_cycle_count) {
self.interrupts.set_flag(interrupt_bits::TIMER_B);

if (self.interrupts.interrupt_enable & interrupt_bits::TIMER_B) != 0 {
active_interrupt = ActiveInterrupt::IRQ;
}
}

ActiveInterrupt::None
let (_ca1, _ca2) = self.a.poll(cycles_since_poll, total_cycle_count);
let (_cb1, _cb2) = self.b.poll(cycles_since_poll, total_cycle_count);

// TODO: stuff based on ca1, ca2, cb1, cb2

active_interrupt
}
}

Expand Down
80 changes: 64 additions & 16 deletions src/memory/mos652x/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ pub use via::Via;

use crate::memory::Port;

#[derive(PartialEq)]
pub enum ActiveTransition {
Rising,
Falling,
None,
}

/// A port and its associated registers on the MOS 6522 VIA or MOS 6526 CIA.
pub struct PortRegisters {
/// The Port implementation that this instance delegates to.
Expand All @@ -21,6 +28,9 @@ pub struct PortRegisters {

/// Latch enable: Present on the MOS 6522 VIA.
latch_enabled: bool,

/// Previous state of the CX1 and CX2 lines (if any). Used to detect rising and falling edges.
cx_lines: Option<(bool, bool)>,
}

impl PortRegisters {
Expand All @@ -30,23 +40,51 @@ impl PortRegisters {
writes: 0,
ddr: 0,
latch_enabled: false,
cx_lines: None,
}
}

/// Read from the port, respecting the DDR.
pub fn read(&mut self) -> u8 {
(self.port.read() & !self.ddr) | (self.writes & self.ddr)
(self.port.read_data() & !self.ddr) | (self.writes & self.ddr)
}

/// Write to the port, respecting the DDR.
pub fn write(&mut self, value: u8) {
self.writes = value;
self.port.write(value & self.ddr);
self.port.write_data(value & self.ddr);
}

/// Poll the underlying port for interrupts.
pub fn poll(&mut self, cycles_since_poll: u64, total_cycle_count: u64) -> bool {
self.port.poll(cycles_since_poll, total_cycle_count)
/// Returns if an edge is detected on either CX1 or CX2.
pub fn poll(
&mut self,
cycles_since_poll: u64,
total_cycle_count: u64,
) -> (ActiveTransition, ActiveTransition) {
let (cx1, cx2) = self.port.read_control(cycles_since_poll, total_cycle_count);

if let Some((prev_cx1, prev_cx2)) = self.cx_lines {
let cx1_transition = match (prev_cx1, cx1) {
(false, true) => ActiveTransition::Rising,
(true, false) => ActiveTransition::Falling,
_ => ActiveTransition::None,
};

let cx2_transition = match (prev_cx2, cx2) {
(false, true) => ActiveTransition::Rising,
(true, false) => ActiveTransition::Falling,
_ => ActiveTransition::None,
};

self.cx_lines = Some((cx1, cx2));

(cx1_transition, cx2_transition)
} else {
self.cx_lines = Some((cx1, cx2));

(ActiveTransition::None, ActiveTransition::None)
}
}

/// Reset the port to its initial state.
Expand Down Expand Up @@ -97,9 +135,6 @@ pub struct Timer {
/// signed integer since polling the timer does not happen at every cycle.
counter: i32,

/// Whether the timer's interrupt flag is set.
interrupt: bool,

/// If false, the timer will fire once; if true, it will load the latch into the counter and keep going
continuous: bool,

Expand All @@ -118,7 +153,6 @@ impl Timer {
Self {
latch: 0,
counter: 0,
interrupt: false,
continuous: false,
running: true,
output: TimerOutput::None,
Expand All @@ -142,7 +176,6 @@ impl Timer {

if self.counter <= 0 {
// The counter underflowed
self.interrupt = true;
true
} else {
false
Expand Down Expand Up @@ -197,7 +230,6 @@ impl Timer {
fn reset(&mut self) {
self.latch = 0;
self.counter = 0;
self.interrupt = false;
self.continuous = false;
self.running = true;
self.output = TimerOutput::None;
Expand Down Expand Up @@ -238,19 +270,23 @@ impl ShiftRegister {
/// Registers for interrupt flags and interrupt enable bits.
/// Each bit from 0 to 6 corresponds to an interrupt source.
pub struct InterruptRegister {
/// The current state of which interrupts are enabled.
/// The current state of which interrupts are enabled (to trigger IRQs).
/// If a bit is set, the corresponding interrupt is enabled.
pub interrupt_enable: u8,

/// The current state of which interrupts are flagged.
/// Flagged interrupts are cleared on read.
pub interrupt_flags: u8,
}

impl InterruptRegister {
/// Read the apparent value of the interrupt register, based on the provided interrupt enable bits.
pub fn read_flags(&self, mut value: u8) -> u8 {
if (value & self.interrupt_enable) != 0 {
value |= 0x80;
pub fn read_flags(&self) -> u8 {
if (self.interrupt_flags & self.interrupt_enable) != 0 {
self.interrupt_flags | 0x80
} else {
self.interrupt_flags
}

value
}

/// Read the value of the interrupt enable register.
Expand All @@ -273,16 +309,28 @@ impl InterruptRegister {
pub fn is_enabled(&self, interrupt: u8) -> bool {
(self.interrupt_enable & interrupt) != 0
}

/// Set the specified interrupt flag (after an interrupt occurred).
pub fn set_flag(&mut self, interrupt: u8) {
self.interrupt_flags |= interrupt;
}

/// Clear the specified interrupt flag (after it has been serviced).
pub fn clear_flag(&mut self, interrupt: u8) {
self.interrupt_flags &= !interrupt;
}
}

impl InterruptRegister {
fn new() -> Self {
Self {
interrupt_enable: 0,
interrupt_flags: 0,
}
}

fn reset(&mut self) {
self.interrupt_enable = 0;
self.interrupt_flags = 0;
}
}
58 changes: 51 additions & 7 deletions src/memory/mos652x/pia.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ struct PiaPortRegisters {
/// Data direction register. Each bit controls whether the line is an input (0) or output (1)
ddr: u8,

// Control register. Each bit has a specific function.
/// Control register. Each bit has a specific function.
pub control: u8,

/// Previous state of the CX1 and CX2 lines (if any). Used to detect rising and falling edges.
cx_lines: Option<(bool, bool)>,
}

impl PiaPortRegisters {
Expand All @@ -25,6 +28,7 @@ impl PiaPortRegisters {
writes: 0,
ddr: 0,
control: 0,
cx_lines: None,
}
}

Expand All @@ -35,7 +39,7 @@ impl PiaPortRegisters {
/// the written value.
pub fn read(&mut self) -> u8 {
if self.control & pia_control_bits::DDR_SELECT != 0 {
(self.port.read() & !self.ddr) | (self.writes & self.ddr)
(self.port.read_data() & !self.ddr) | (self.writes & self.ddr)
} else {
self.ddr
}
Expand All @@ -48,15 +52,51 @@ impl PiaPortRegisters {
pub fn write(&mut self, value: u8) {
if self.control & pia_control_bits::DDR_SELECT != 0 {
self.writes = value;
self.port.write(value & self.ddr);
self.port.write_data(value & self.ddr);
} else {
self.ddr = value;
}
}

/// Poll the underlying port for interrupts.
pub fn poll(&mut self, cycles_since_poll: u64, total_cycle_count: u64) -> bool {
self.port.poll(cycles_since_poll, total_cycle_count)
let (cx1, cx2) = self.port.read_control(cycles_since_poll, total_cycle_count);

let mut interrupt = false;

if let Some((prev_cx1, prev_cx2)) = self.cx_lines {
// CX1
if cx1 != prev_cx1 {
let direction = self.control & pia_control_bits::C1_ACTIVE_TRANSITION;
if (direction == 0 && cx1 == false) || (direction != 0 && cx1 == true) {

Check failure on line 71 in src/memory/mos652x/pia.rs

View workflow job for this annotation

GitHub Actions / Desktop Build and Style Check

equality checks against false can be replaced by a negation

Check failure on line 71 in src/memory/mos652x/pia.rs

View workflow job for this annotation

GitHub Actions / Desktop Build and Style Check

equality checks against true are unnecessary
// matching edge detected
self.control |= pia_control_bits::C1_ACTIVE_TRANSITION_FLAG;

if self.control & pia_control_bits::C1_INTERRUPT_ENABLE != 0 {
interrupt = true;
}
}
}

// CX2
if (pia_control_bits::C2_DIRECTION & self.control) != 0 {
// input
if cx2 != prev_cx2 {
let direction = self.control & pia_control_bits::C2_INPUT_ACTIVE_TRANSITION;
if (direction == 0 && cx2 == false) || (direction != 0 && cx2 == true) {

Check failure on line 86 in src/memory/mos652x/pia.rs

View workflow job for this annotation

GitHub Actions / Desktop Build and Style Check

equality checks against false can be replaced by a negation

Check failure on line 86 in src/memory/mos652x/pia.rs

View workflow job for this annotation

GitHub Actions / Desktop Build and Style Check

equality checks against true are unnecessary
// matching edge detected
self.control |= pia_control_bits::C2_ACTIVE_TRANSITION_FLAG;

if self.control & pia_control_bits::C2_INPUT_INTERRUPT_ENABLE != 0 {
interrupt = true;
}
}
}
}
}

self.cx_lines = Some((cx1, cx2));
interrupt
}

/// Reset the DDR, control register, and underlying port.
Expand All @@ -71,12 +111,16 @@ impl PiaPortRegisters {
#[allow(dead_code)]
/// The meanings of each bit in the control register.
pub mod pia_control_bits {
pub const C1_ACTIVE_TRANSITION_FLAG: u8 = 0b10000000; // 1 = 0->1, 0 = 1->0
pub const C1_ACTIVE_TRANSITION_FLAG: u8 = 0b10000000;
pub const C2_ACTIVE_TRANSITION_FLAG: u8 = 0b01000000;
pub const C2_DIRECTION: u8 = 0b00100000; // 1 = output, 0 = input
pub const C2_CONTROL: u8 = 0b00011000; // ???
pub const C2_OUTPUT_MANUAL: u8 = 0b00010000; // 0 = pulse, 1 = manual
pub const C2_OUTPUT_TYPE: u8 = 0b00001000; // just read the datasheet for this one
pub const C2_INPUT_ACTIVE_TRANSITION: u8 = 0b00010000; // 0 = negative (falling), 1 = positive (rising)
pub const C2_INPUT_INTERRUPT_ENABLE: u8 = 0b00001000; // 0 = disable IRQs, 1 = enable IRQs
pub const DDR_SELECT: u8 = 0b00000100; // enable accessing DDR
pub const C1_CONTROL: u8 = 0b00000011; // interrupt status control
pub const C1_ACTIVE_TRANSITION: u8 = 0b0000010; // 0 = negative (falling), 1 = positive (rising)
pub const C1_INTERRUPT_ENABLE: u8 = 0b00000001; // 0 = disable IRQs, 1 = enable IRQs
}

/// The MOS 6520 Peripheral Interface Adapter (PIA), containing two ports and
Expand Down
Loading

0 comments on commit 83adeb4

Please sign in to comment.