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}