c64 0.1.0-alpha.1

Driver for the Commodore 64 platform
Documentation
//! HAL for the Commodore 64 Kernal ROM.

use ufmt::{uDisplay, uwrite};

use crate::{
    hal::{AddressSpace, Io, Kernal},
    pac::kernal,
};

/// Type-safe interface to the Kernal ROM.
///
/// This can only be constructed when the memory configuration has the Kernal ROM overlay
/// enabled, making it safe to call Kernal functions.
pub struct KernalRom<'a, LORAM, CHAREN> {
    _asp: &'a AddressSpace<Kernal, LORAM, CHAREN>,
}

impl<'a, LORAM, CHAREN> KernalRom<'a, LORAM, CHAREN> {
    /// Constructs an access API for the Kernal ROM overlay.
    pub fn new(asp: &'a AddressSpace<Kernal, LORAM, CHAREN>) -> Self {
        Self { _asp: asp }
    }

    /// Returns the trigonometry constants in the Kernel ROM.
    pub fn trig_constants(&self) -> &kernal::TrigConstants {
        unsafe { &*kernal::TRIG_CONSTANTS }
    }

    /// Writes a single PETSCII character to the current default output channel.
    ///
    /// If `Self` is not `KernalRom<'_, _, Io>` (i.e. if the character ROM is mapped into
    /// memory instead of I/O devices) then this function partially degrades: the glyph
    /// still appears on the screen, but the cell colour will not be updated and will keep
    /// its previous colour.
    pub fn write_petscii(&self, byte: u8) {
        // SAFETY: We don't require `CHAREN` to be `Io` here because we won't trample
        // arbitrary memory:
        // - We know the Kernal ROM is loaded, so `CHAREN` cannot be `Ram`.
        // - Writes to the I/O address space (for setting the cell colour) will be ignored
        //   if `CHAREN` is `CharacterRom`.
        unsafe { kernal::chrout(byte) }
    }

    /// Toggles reverse-video output mode for subsequent CHROUT output.
    ///
    /// While reverse mode is on, every printable PETSCII byte routed through CHROUT
    /// has bit 7 of its screen code set, so the glyph renders as its inverse — most
    /// usefully turning a space (`$20`) into a solid filled cell (screen code `$A0`).
    /// PETSCII `$A0` itself, despite being commonly described as "shifted space",
    /// maps to screen code `$60`, which is the *blank* glyph in the upper-case
    /// character ROM, so this toggle is the only way to reach the filled block via
    /// CHROUT.
    ///
    /// CHROUT honours this state via the control codes `$12` (on) and `$92` (off),
    /// and the state persists across as many characters as you like — calling once
    /// before a multi-character run is equivalent to calling per character, just
    /// cheaper. A `RETURN` or carriage-return write resets it.
    pub fn set_reverse_mode(&self, on: bool) {
        self.write_petscii(if on { 0x12 } else { 0x92 });
    }

    /// Sets the system clock to the given number of jiffies (60th of seconds) since
    /// midnight.
    pub fn set_system_clock(&self, jiffies: Jiffies) {
        unsafe { kernal::settim(jiffies.as_u32()) }
    }

    /// Returns the time in jiffies (60ths of a second) since the system was turned on or
    /// reset, or since midnight if [`Self::set_system_clock`] has been called.
    pub fn read_system_clock(&self) -> Jiffies {
        // SAFETY: The Kernal stores the jiffy counter in three bytes, so the value
        // returned by `rdtim` always fits in 24 bits.
        Jiffies(unsafe { kernal::rdtim() })
    }

    /// Returns the dimensions of the screen.
    pub fn screen_size(&self) -> ScreenSize {
        let (cols, rows) = unsafe { kernal::screen() };
        ScreenSize { cols, rows }
    }

    /// Returns the current position of the cursor.
    pub fn cursor_position(&self) -> CursorPos {
        let (row, col) = unsafe { kernal::plot_get() };
        CursorPos { row, col }
    }

    /// Sets the cursor position.
    pub fn set_cursor_position(&self, row: u8, col: u8) {
        unsafe { kernal::plot_set(row, col) };
    }
}

impl<'a, LORAM> KernalRom<'a, LORAM, Io> {
    /// Switches the screen to the requested character set.
    ///
    /// CHROUT treats `$0E` and `$8E` as control codes that flip the VIC-II's character
    /// ROM pointer (bit 1 of `$D018`) rather than printing a glyph, so this only
    /// affects future output and the rendering of any text already on screen — it
    /// does not move the cursor or rewrite the screen RAM.
    pub fn set_character_set(&self, set: CharacterSet) {
        self.write_petscii(match set {
            CharacterSet::UppercaseGraphics => 0x8E,
            CharacterSet::LowercaseUppercase => 0x0E,
        });
    }
}

/// Which of the C64's two text-mode character sets the VIC-II is currently displaying.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CharacterSet {
    /// Uppercase letters in `$41..=$5A`, with graphics glyphs in `$61..=$7A`. This is
    /// the default at boot.
    UppercaseGraphics,
    /// Lowercase letters in `$41..=$5A` and uppercase letters in `$61..=$7A`. Most of
    /// the PETSCII graphics glyphs are unavailable in this set.
    LowercaseUppercase,
}

/// Time since midnight, in 60ths of a second.
///
/// The Kernal jiffy counter is stored in three bytes, so any value in `[0, 2^24)` is a
/// valid encoding. When normal IRQs are running, the Kernal increments the counter every
/// 60th of a second and wraps it to 0 the moment it reaches exactly 24 hours, so the
/// counter naturally stays within `[0, 24h)`. [`KernalRom::set_system_clock`] does not
/// enforce that range, however, so values in `[24h, 2^24)` are still representable.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Jiffies(u32);

impl uDisplay for Jiffies {
    fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
    where
        W: ufmt::uWrite + ?Sized,
    {
        const JIFFIES_PER_SECOND: u32 = 60;
        const JIFFIES_PER_MINUTE: u32 = JIFFIES_PER_SECOND * 60;
        const JIFFIES_PER_HOUR: u32 = JIFFIES_PER_MINUTE * 60;
        let hours = self.0 / JIFFIES_PER_HOUR;
        let rest = self.0 - (hours * JIFFIES_PER_HOUR);
        let mins = rest / JIFFIES_PER_MINUTE;
        let rest = rest - (mins * JIFFIES_PER_MINUTE);
        let secs = rest / JIFFIES_PER_SECOND;
        let jiffies = rest - (secs * JIFFIES_PER_SECOND);
        uwrite!(f, "{}:{}:{}.{}", hours, mins, secs, jiffies)
    }
}

impl Jiffies {
    /// The modulus of the Kernal's three-byte jiffy register.
    const STORE_MODULUS: u32 = 0x0100_0000;

    /// The number of jiffies in 24 hours, at which the Kernal IRQ wraps the counter.
    const CLOCK_MODULUS: u32 = 24 * 60 * 60 * 60;

    /// Constructs a `Jiffies` from a raw 24-bit register value.
    ///
    /// Returns `None` if `value` does not fit in the Kernal's three-byte storage.
    pub const fn new(value: u32) -> Option<Self> {
        if value < Self::STORE_MODULUS {
            Some(Self(value))
        } else {
            None
        }
    }

    /// Constructs a canonical time-of-day `Jiffies` by wrapping `value` modulo 24 hours.
    ///
    /// The result is guaranteed to lie in `[0, 24h)`, matching the range that the Kernal
    /// IRQ handler maintains.
    pub const fn wrapping_new(value: u32) -> Self {
        Self(value % Self::CLOCK_MODULUS)
    }

    /// Returns the underlying jiffy count.
    pub const fn as_u32(self) -> u32 {
        self.0
    }
}

/// Logical screen size in characters.
pub struct ScreenSize {
    pub cols: u8,
    pub rows: u8,
}

/// The zero-indexed cursor position.
pub struct CursorPos {
    pub row: u8,
    pub col: u8,
}