mos_hardware/
vic2.rs

1// copyright 2022 mikael lund aka wombat
2//
3// licensed under the apache license, version 2.0 (the "license");
4// you may not use this file except in compliance with the license.
5// you may obtain a copy of the license at
6//
7//     http://www.apache.org/licenses/license-2.0
8//
9// unless required by applicable law or agreed to in writing, software
10// distributed under the license is distributed on an "as is" basis,
11// without warranties or conditions of any kind, either express or implied.
12// see the license for the specific language governing permissions and
13// limitations under the license.
14
15//! Registers for the MOS 6566/6567 (VIC-II) Chip
16//!
17//! The VIC-II (Video Interface Chip II), specifically known as the MOS Technology
18//! 6567/8562/8564 (NTSC versions), 6569/8565/8566 (PAL), is the microchip tasked
19//! with generating Y/C video signals (combined to composite video in the RF modulator)
20//! and DRAM refresh signals in the Commodore 64 and C128 home computers.
21
22use bitflags::bitflags;
23use core::mem::size_of;
24use static_assertions::const_assert;
25use volatile_register::{RO, RW};
26
27pub const BLACK: u8 = 0;
28pub const WHITE: u8 = 1;
29pub const RED: u8 = 2;
30pub const CYAN: u8 = 3;
31pub const PURPLE: u8 = 4;
32pub const GREEN: u8 = 5;
33pub const BLUE: u8 = 6;
34pub const YELLOW: u8 = 7;
35pub const ORANGE: u8 = 8;
36pub const BROWN: u8 = 9;
37pub const LIGHT_RED: u8 = 10;
38pub const GRAY1: u8 = 11;
39pub const GRAY2: u8 = 12;
40pub const LIGHT_GREEN: u8 = 13;
41pub const LIGHT_BLUE: u8 = 14;
42pub const GRAY3: u8 = 15;
43
44bitflags! {
45    /// Bitmask for sprites 0 to 7.
46    pub struct Sprites: u8 {
47        const SPRITE0 = 0b00000001;
48        const SPRITE1 = 0b00000010;
49        const SPRITE2 = 0b00000100;
50        const SPRITE3 = 0b00001000;
51        const SPRITE4 = 0b00010000;
52        const SPRITE5 = 0b00100000;
53        const SPRITE6 = 0b01000000;
54        const SPRITE7 = 0b10000000;
55    }
56}
57
58impl Sprites {
59    /// Construct from sprite index of bits
60    ///
61    /// # Examples
62    /// ~~~
63    /// const SPRITE2: Sprite = Sprites::new(2);
64    /// ~~~
65    pub const fn new(index: u8) -> Self {
66        assert!(index < 8);
67        match index {
68            0 => Self::SPRITE0,
69            1 => Self::SPRITE1,
70            2 => Self::SPRITE2,
71            3 => Self::SPRITE3,
72            4 => Self::SPRITE4,
73            5 => Self::SPRITE5,
74            6 => Self::SPRITE6,
75            7 => Self::SPRITE7,
76            _ => panic!(),
77        }
78    }
79}
80
81bitflags! {
82    /// Y-Scroll Register Mask (0xd011)
83    pub struct ControlYFlags: u8 {
84        /// Control smooth y scrolling
85        const YSCROLL = 0b0000_0111;
86        /// Switch between 25 (on) and 24 (off) row text mode
87        const ROW_SELECT = 0b0000_1000;
88        /// Blank screen to border color (0 = blank)
89        const BLANK_SCREEN = 0b0001_0000;
90        /// Enable bitmap mode (enable = 1)
91        const BITMAP_MODE = 0b0010_0000;
92        /// Extended color text mode (enable = 1)
93        const EXTENDED_COLOR_MODE = 0b0100_0000;
94        const RASTER_COMPARE = 0b1000_0000;
95    }
96}
97
98bitflags! {
99    /// X-Scroll Register Flags (0xD016)
100    pub struct ControlXFlags: u8 {
101        /// Control smooth x scrolling
102        const XSCROLL = 0b0000_0111;
103        /// Switch between 38 (off) or 40 (on) column text mode
104        const COLUMN_SELECT = 0b0000_1000;
105        /// Enable (on) multi color for text and bitmap modes
106        const MULTICOLOR = 0b0001_0000;
107        /// Should always be set to zero!
108        const ALWAYS_ZERO = 0b0010_0000;
109        /// Unused
110        const UNUSED = 0b1100_0000;
111    }
112}
113
114bitflags! {
115    /// Interrupt Register Flags (0xD019).
116    /// Bits are set to 1 when an IRQ is detected.
117    pub struct InterruptFlags: u8 {
118        /// Set when raster counter equals stored raster count
119        const RASTER_COMPARE_IRQ = 0b00000001;
120        /// Set for first collision of sprite with background
121        const SPRITE_BACKGROUND_COLLISION = 0b00000010;
122        /// Set for first collision of sprite with another sprite
123        const SPRITE_SPRITE_COLLISION = 0b00000100;
124        const LIGHPEN_TRIGGERED = 0b00001000;
125        const ANY_IRQ = 0b10000000;
126    }
127}
128
129bitflags! {
130    /// Flags to trigger IRQ request when VIC-II interrupt
131    /// conditions are met (0xD01A)
132    pub struct IRQEnableFlags: u8 {
133        const RASTER_COMPARE              = 0b0000_0001; // bit 0
134        const SPRITE_BACKGROUND_COLLISION = 0b0000_0010; // bit 1
135        const ENSPRITE_SPRITE_COLLISION   = 0b0000_0100; // bit 2
136        const LIGHT_PEN                   = 0b0000_1000; // bit 3
137    }
138}
139
140bitflags! {
141    /// All possible charset memory locations
142    ///
143    /// # Examples
144    /// ~~~
145    /// let bank = vic2::ScreenBank::AT_2C00.bits() | vic2::CharsetBank::AT_2000.bits();
146    /// (*c64::VIC).screen_and_charset_bank.write(bank);
147    /// ~~~
148    pub struct CharsetBank: u8 {
149        const AT_0000 = 0b0000_0000;
150        const AT_0800 = 0b0000_0010;
151        const AT_1000 = 0b0000_0100;
152        const AT_1800 = 0b0000_0110;
153        const AT_2000 = 0b0000_1000;
154        const AT_2800 = 0b0000_1010;
155        const AT_3000 = 0b0000_1100;
156        const AT_3800 = 0b0000_1110;
157        const DEFAULT = Self::AT_1000.bits;
158    }
159}
160
161impl CharsetBank {
162    /// Generate bank from charset memory address. Will check if it is valid.
163    ///
164    /// # Examples
165    /// ~~~
166    /// const SCREEN: u16 = 0x2800;
167    /// const CHARSET: u16 = 0x2000;
168    /// const BANK: u8 = vic2::ScreenBank::from(SCREEN).bits() | vic2::CharsetBank::from(CHARSET).bits();
169    /// ~~~
170    pub const fn from(charset: u16) -> Self {
171        let bank = ((charset >> 10) & 0x0e) as u8;
172        Self::from_bits(bank).unwrap()
173    }
174}
175
176bitflags! {
177    /// All possible screen memory locations
178    pub struct ScreenBank: u8 {
179        const AT_0000 = 0b0000_0000;
180        const AT_0400 = 0b0001_0000;
181        const AT_0800 = 0b0010_0000;
182        const AT_0C00 = 0b0011_0000;
183        const AT_1000 = 0b0100_0000;
184        const AT_1400 = 0b0101_0000;
185        const AT_1800 = 0b0110_0000;
186        const AT_1C00 = 0b0111_0000;
187        const AT_2000 = 0b1000_0000;
188        const AT_2400 = 0b1001_0000;
189        const AT_2800 = 0b1010_0000;
190        const AT_2C00 = 0b1011_0000;
191        const AT_3000 = 0b1100_0000;
192        const AT_3400 = 0b1101_0000;
193        const AT_3800 = 0b1110_0000;
194        const AT_3C00 = 0b1111_0000;
195        const DEFAULT = Self::AT_0800.bits;
196    }
197}
198
199impl ScreenBank {
200    /// Generate bank from screen memory address. Will check if it is valid.
201    ///
202    /// # Examples
203    /// ~~~
204    /// const SCREEN: u16 = 0x2800;
205    /// const CHARSET: u16 = 0x2000;
206    /// const BANK: u8 = ScreenBank::from_address(SCREEN).bits() | ScreenBank::from_address(CHARSET).bits();
207    /// ~~~
208    pub const fn from_address(screen: u16) -> Self {
209        let bank = (screen >> 6) as u8;
210        Self::from_bits(bank).unwrap()
211    }
212}
213
214/// X and Y positions for e.g. sprites
215pub struct XYcoordinate {
216    /// X position
217    pub x: RW<u8>,
218    /// Y position
219    pub y: RW<u8>,
220}
221
222#[repr(C, packed)]
223/// Hardware registers for the MOS Technologies Video Interface Controller II
224pub struct MOSVideoInterfaceControllerII {
225    /// Sprite positions (x0, y0, x1, ...)
226    pub sprite_positions: [XYcoordinate; 8],
227    /// `MSIGX` Most Significant Bits of Sprites 0-7 Horizontal Positions (0x10)
228    ///
229    /// Setting one of these bits to 1 adds 256 to the horizontal
230    /// position of the corresponding sprite.
231    /// Resetting one to 0 restricts the horizontal position of the
232    /// corresponding sprite to a value of 255 or less.
233    pub sprite_positions_most_significant_bit_of_x: RW<Sprites>,
234    /// `SCROLY` Vertical Fine Scrolling and Control Register, 0x11
235    pub control_y: RW<ControlYFlags>,
236    /// `RASTER` Raster counter (0x12)
237    ///
238    /// This has two different functions, depending on whether reading or writing:
239    /// 1. When _read_, it tells which screen line the electron beam is currently scanning.
240    /// 2. _Writing_ to this register designates the comparison value for the Raster IRQ.
241    pub raster_counter: RW<u8>,
242    /// `LPENX` (0x13)
243    pub lightpen_x: RW<u8>,
244    /// `LPENY` (0x14)
245    pub lightpen_y: RW<u8>,
246    /// `SPENA` (0x15)
247    pub sprite_enable: RW<Sprites>,
248    /// Offset 0x16
249    pub control_x: RW<ControlXFlags>,
250    /// `YXPAND` (0x17)
251    pub sprite_expand_y: RW<Sprites>,
252    /// `VMCSB` Memory Control Register (0x18)
253    pub screen_and_charset_bank: RW<u8>,
254    /// `VICIRQ` Interrupt flag (0x19)
255    ///
256    /// The VIC-II chip is capable of generating a maskable request (IRQ)
257    /// when certain conditions relating to the video display are fulfilled.
258    pub irq_status: RW<InterruptFlags>,
259    /// `IRQMSK` IRQ Mask Register (0x1a)
260    pub irq_enable: RW<IRQEnableFlags>,
261    /// `SPBGPR` Place sprites behind (0) in infront of bitmaps (0x1b)
262    pub sprite_background_priority: RW<Sprites>,
263    /// `SPMC` (0x1c)
264    pub sprite_multicolor_mode: RW<Sprites>,
265    /// Offset 0x1d
266    pub sprite_expand_x: RW<Sprites>,
267    /// Offset 0x1e
268    pub sprite_sprite_collision: RO<Sprites>,
269    /// Offset 0x1f
270    pub sprite_background_collision: RO<Sprites>,
271    /// Offset 0x20
272    pub border_color: RW<u8>,
273    /// Offset 0x21
274    pub background_color0: RW<u8>,
275    /// Offset 0x22
276    pub background_color1: RW<u8>,
277    /// Offset 0x23
278    pub background_color2: RW<u8>,
279    /// Offset 0x24
280    pub background_color3: RW<u8>,
281    /// Offset 0x25
282    pub sprite_multicolor0: RW<u8>,
283    /// Offset 0x26
284    pub sprite_multicolor1: RW<u8>,
285    /// `SPxCOL` Sprite Colors (0x2e)
286    pub sprite_colors: [RW<u8>; 8],
287}
288
289const_assert!(size_of::<MOSVideoInterfaceControllerII>() == 0x2f);
290
291impl MOSVideoInterfaceControllerII {
292    /// Sets position of sprite identified by it's index
293    pub fn set_sprite_pos(&self, index: u8, xpos: u8, ypos: u8) {
294        unsafe {
295            self.sprite_positions[index as usize].x.write(xpos);
296            self.sprite_positions[index as usize].y.write(ypos);
297        }
298    }
299
300    /// Sets color of sprite identified by it's index
301    pub fn set_sprite_color(&self, index: u8, color: u8) {
302        unsafe {
303            self.sprite_colors[index as usize].write(color);
304        }
305    }
306}
307
308/// Calculate sprite pointer from pattern address
309///
310/// To make a given sprite show the pattern that's stored in RAM at `address`
311/// (which must be divisible with 64), set the contents of the corresponding
312/// sprite pointer address to `address` divided by 64. For instance, if the sprite pattern
313/// begins at address 704, the pointer value will be 704 / 64 = 11.
314///
315/// # Examples
316///
317/// ~~~
318/// const SPRITE_ADDRESS: u16 = 0x2000;
319/// const SPRITE_PTR: u8 = to_sprite_pointer(SPRITE_ADDRESS);
320/// ~~~
321///
322/// When called as `const`, assertions are done at compile time:
323///
324/// ~~~should_panic
325/// const INVALID_SPRITE_ADDRESS: u16 = 0x2001; // compile time error
326/// ~~~
327///
328/// # Image conversion
329///
330/// Image files can be converted using ImageMagick:
331///
332/// ~~~bash
333/// convert image.png -alpha off -resize 24x21! -monochrome sprite.png
334/// ~~~
335///
336/// and then converted into a byte array using Python:
337///
338/// ~~~python
339/// import numpy as np
340/// from PIL import Image
341/// image = Image.open('sprite.png')
342/// bits = (~np.asarray(image).reshape(int(24*21/8), 8))
343/// for bits_in_byte in bits.astype(int):
344///     print(int(''.join('01'[i] for i in bits_in_byte), 2), end=',')
345/// ~~~
346pub const fn to_sprite_pointer(address: u16) -> u8 {
347    assert!(address % 64 == 0);
348    assert!(address / 64 < 256);
349    (address / 64) as u8
350}