schip: Improve architecture & compatibility somewhat
This commit is contained in:
		
							
								
								
									
										17
									
								
								src/bus.rs
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								src/bus.rs
									
									
									
									
									
								
							| @@ -166,6 +166,23 @@ impl Bus { | |||||||
|         } |         } | ||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |     /// Updates an existing named range (Region) | ||||||
|  |     /// # Examples | ||||||
|  |     /// ```rust | ||||||
|  |     ///# use chirp::*; | ||||||
|  |     ///# fn main() -> Result<()> { | ||||||
|  |     ///     let bus = Bus::new().add_region(Program, 0..1234); | ||||||
|  |     ///     assert_eq!(1234, bus.len()); | ||||||
|  |     ///#    Ok(()) | ||||||
|  |     ///# } | ||||||
|  |     /// ``` | ||||||
|  |     pub fn set_region(&mut self, name: Region, range: Range<usize>) -> &mut Self { | ||||||
|  |         self.with_size(range.end); | ||||||
|  |         if let Some(region) = self.region.get_mut(name as usize) { | ||||||
|  |             *region = Some(range); | ||||||
|  |         } | ||||||
|  |         self | ||||||
|  |     } | ||||||
|     /// Loads data into a named region |     /// Loads data into a named region | ||||||
|     /// # Examples |     /// # Examples | ||||||
|     /// ```rust |     /// ```rust | ||||||
|   | |||||||
							
								
								
									
										246
									
								
								src/cpu.rs
									
									
									
									
									
								
							
							
						
						
									
										246
									
								
								src/cpu.rs
									
									
									
									
									
								
							| @@ -788,11 +788,11 @@ impl CPU { | |||||||
|             Insn::dmai  { x       } => self.load_dma(x, bus), |             Insn::dmai  { x       } => self.load_dma(x, bus), | ||||||
|             // Super-Chip extensions |             // Super-Chip extensions | ||||||
|             Insn::scd   {       n } => self.scroll_down(n, bus), |             Insn::scd   {       n } => self.scroll_down(n, bus), | ||||||
|             Insn::scr               => self.scr(bus), |             Insn::scr               => self.scroll_right(bus), | ||||||
|             Insn::scl               => self.scl(bus), |             Insn::scl               => self.scroll_left(bus), | ||||||
|             Insn::halt              => self.flags.pause(), |             Insn::halt              => self.flags.pause(), | ||||||
|             Insn::lores             => self.flags.draw_mode = false, |             Insn::lores             => self.init_lores(bus), | ||||||
|             Insn::hires             => self.flags.draw_mode = true, |             Insn::hires             => self.init_hires(bus), | ||||||
|             Insn::hfont { x       } => self.load_big_sprite(x), |             Insn::hfont { x       } => self.load_big_sprite(x), | ||||||
|             Insn::flgo  { x       } => self.store_flags(x, bus), |             Insn::flgo  { x       } => self.store_flags(x, bus), | ||||||
|             Insn::flgi  { x       } => self.load_flags(x, bus), |             Insn::flgi  { x       } => self.load_flags(x, bus), | ||||||
| @@ -821,37 +821,6 @@ impl CPU { | |||||||
|         self.sp = self.sp.wrapping_add(2); |         self.sp = self.sp.wrapping_add(2); | ||||||
|         self.pc = bus.read(self.sp); |         self.pc = bus.read(self.sp); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// |`00cN`| Scroll the screen down N lines |  | ||||||
|     #[inline(always)] |  | ||||||
|     fn scroll_down(&mut self, n: Nib, bus: &mut Bus) { |  | ||||||
|         // Get a line from the bus |  | ||||||
|         for i in (16 * n as usize..16 * 15).step_by(16).rev() { |  | ||||||
|             let i = i + self.screen as usize; |  | ||||||
|             let line: u128 = bus.read(i); |  | ||||||
|             bus.write(i - (n as usize * 16), line); |  | ||||||
|             bus.write(i, 0u128); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// |`00fb`| Scroll the screen right |  | ||||||
|     #[inline(always)] |  | ||||||
|     fn scr(&mut self, bus: &mut (impl Read<u128> + Write<u128>)) { |  | ||||||
|         // Get a line from the bus |  | ||||||
|         for i in (0..16 * 64).step_by(16) { |  | ||||||
|             //let line: u128 = bus.read(self.screen + i) >> 4; |  | ||||||
|             bus.write(self.screen + i, bus.read(self.screen + i) >> 4); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     /// |`00fc`| Scroll the screen right |  | ||||||
|     #[inline(always)] |  | ||||||
|     fn scl(&mut self, bus: &mut (impl Read<u128> + Write<u128>)) { |  | ||||||
|         // Get a line from the bus |  | ||||||
|         for i in (0..16 * 64).step_by(16) { |  | ||||||
|             let line: u128 = (bus.read(self.screen + i) & !(0xf << 124)) << 4; |  | ||||||
|             bus.write(self.screen + i, line); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // |`1aaa`| Sets pc to an absolute address | // |`1aaa`| Sets pc to an absolute address | ||||||
| @@ -1081,15 +1050,6 @@ impl CPU { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| /// TODO: Do this more idiomatically, using some iterator chain? |  | ||||||
| fn doublewide(value: u16) -> u32 { |  | ||||||
|     let mut out: u32 = 0; |  | ||||||
|     for i in 0..16 { |  | ||||||
|         out |= ((value as u32 & 0x1 << i) * 3) << i; |  | ||||||
|     } |  | ||||||
|     out |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // |`Dxyn`| Draws n-byte sprite to the screen at coordinates (vX, vY) | // |`Dxyn`| Draws n-byte sprite to the screen at coordinates (vX, vY) | ||||||
| impl CPU { | impl CPU { | ||||||
|     /// |`Dxyn`| Draws n-byte sprite to the screen at coordinates (vX, vY) |     /// |`Dxyn`| Draws n-byte sprite to the screen at coordinates (vX, vY) | ||||||
| @@ -1098,38 +1058,38 @@ impl CPU { | |||||||
|     /// On the original chip-8 interpreter, this will wait for a VBI |     /// On the original chip-8 interpreter, this will wait for a VBI | ||||||
|     #[inline(always)] |     #[inline(always)] | ||||||
|     fn draw(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut Bus) { |     fn draw(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut Bus) { | ||||||
|         // lmaotch |  | ||||||
|         match self.flags.draw_mode { |  | ||||||
|             true => self.draw_hires(x, y, n, bus), |  | ||||||
|             false => self.draw_lores(x, y, n, bus), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     #[inline(always)] |  | ||||||
|     fn draw_lores(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut Bus) { |  | ||||||
|         if !self.flags.quirks.draw_wait { |         if !self.flags.quirks.draw_wait { | ||||||
|             self.flags.draw_wait = true; |             self.flags.draw_wait = true; | ||||||
|         } |         } | ||||||
|         let (w, h) = (64, 32); |         // self.draw_hires handles both hi-res mode and drawing 16x16 sprites | ||||||
|         let (x, y) = (self.v[x] as u16 % w, self.v[y] as u16 % h); |         if self.flags.draw_mode || n == 0 { | ||||||
|  |             self.draw_hires(x, y, n, bus); | ||||||
|  |         } else { | ||||||
|  |             self.draw_lores(x, y, n, bus); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[inline(always)] | ||||||
|  |     fn draw_lores(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut Bus) { | ||||||
|  |         self.draw_sprite(self.v[x] as u16 % 64, self.v[y] as u16 % 32, n, 64, 32, bus); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[inline(always)] | ||||||
|  |     fn draw_sprite(&mut self, x: u16, y: u16, n: Nib, w: u16, h: u16, bus: &mut Bus) { | ||||||
|  |         let w_bytes = w / 8; | ||||||
|         self.v[0xf] = 0; |         self.v[0xf] = 0; | ||||||
|         if let Some(sprite) = bus.get(self.i as usize..(self.i + n as u16) as usize) { |         if let Some(sprite) = bus.get(self.i as usize..(self.i + n as u16) as usize) { | ||||||
|             let sprite = sprite.to_vec(); |             let sprite = sprite.to_vec(); | ||||||
|             for (line, sprite) in sprite.iter().enumerate() { |             for (line, &sprite) in sprite.iter().enumerate() { | ||||||
|                 let (line, sprite) = (line as u16, *sprite); |                 let line = line as u16; | ||||||
|                 // clip |  | ||||||
|                 if y + line >= h { |                 if y + line >= h { | ||||||
|                     break; |                     break; | ||||||
|                 } |                 } | ||||||
|                 // clip |  | ||||||
|                 let sprite = (sprite as u16) << (8 - (x % 8)) |                 let sprite = (sprite as u16) << (8 - (x % 8)) | ||||||
|                     & if (x % w) >= (w - 8) { 0xff00 } else { 0xffff }; |                     & if (x % w) >= (w - 8) { 0xff00 } else { 0xffff }; | ||||||
|                 // scale |                 let addr = |x, y| -> u16 { (y + line) * w_bytes + (x / 8) + self.screen }; | ||||||
|                 let addr = |x, y| -> u16 { ((y + (2 * line)) * 8 + (x / 8)) * 2 + self.screen }; |                 let screen: u16 = bus.read(addr(x, y)); | ||||||
|                 let y = y << 1; |                 bus.write(addr(x, y), screen ^ sprite); | ||||||
|                 let sprite = doublewide(sprite); |  | ||||||
|                 for scale in 0..2 { |  | ||||||
|                     let screen: u32 = bus.read(addr(x, y + scale)); |  | ||||||
|                     bus.write(addr(x, y + scale), screen ^ sprite); |  | ||||||
|                 if screen & sprite != 0 { |                 if screen & sprite != 0 { | ||||||
|                     self.v[0xf] = 1; |                     self.v[0xf] = 1; | ||||||
|                 } |                 } | ||||||
| @@ -1137,50 +1097,6 @@ impl CPU { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|     // Super-Chip extension high-resolution graphics mode |  | ||||||
|     #[inline(always)] |  | ||||||
|     fn draw_hires(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut Bus) { |  | ||||||
|         if !self.flags.quirks.draw_wait { |  | ||||||
|             self.flags.draw_wait = true; |  | ||||||
|         } |  | ||||||
|         let (w, h) = (128, 64); |  | ||||||
|         let (x, y) = (self.v[x] as u16 % w, self.v[y] as u16 % h); |  | ||||||
|         let w_bytes = w / 8; |  | ||||||
|         self.v[0xf] = 0; |  | ||||||
|         if n == 0 { |  | ||||||
|             if let Some(sprite) = bus.get(self.i as usize..(self.i + 32) as usize) { |  | ||||||
|                 let sprite = sprite.to_owned(); |  | ||||||
|                 for (line, sprite) in sprite.chunks(2).enumerate() { |  | ||||||
|                     let sprite = u16::from_be_bytes( |  | ||||||
|                         sprite |  | ||||||
|                             .try_into() |  | ||||||
|                             .expect("Chunks should only return 2 bytes"), |  | ||||||
|                     ); |  | ||||||
|                     let addr = (y + line as u16) * w_bytes + x / 8 + self.screen; |  | ||||||
|                     let sprite = (sprite as u32) << (16 - (x % 8)); |  | ||||||
|                     let screen: u32 = bus.read(addr); |  | ||||||
|                     bus.write(addr, screen ^ sprite); |  | ||||||
|                     if screen & sprite != 0 { |  | ||||||
|                         self.v[0xf] += 1; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             if let Some(sprite) = bus.get(self.i as usize..(self.i + n as u16) as usize) { |  | ||||||
|                 let sprite = sprite.to_vec(); |  | ||||||
|                 for (line, sprite) in sprite.iter().enumerate() { |  | ||||||
|                     let addr = (y + line as u16) * w_bytes + x / 8 + self.screen; |  | ||||||
|                     let sprite = (*sprite as u16) << (8 - (x % 8)); |  | ||||||
|                     let screen: u16 = bus.read(addr); |  | ||||||
|                     bus.write(addr, screen ^ sprite); |  | ||||||
|                     if screen & sprite != 0 { |  | ||||||
|                         self.v[0xf] += 1; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // |`Exbb`| Skips instruction on value of keypress | // |`Exbb`| Skips instruction on value of keypress | ||||||
| // | // | ||||||
| @@ -1192,16 +1108,14 @@ impl CPU { | |||||||
|     /// |`Ex9E`| Skip next instruction if key == vX |     /// |`Ex9E`| Skip next instruction if key == vX | ||||||
|     #[inline(always)] |     #[inline(always)] | ||||||
|     fn skip_key_equals(&mut self, x: Reg) { |     fn skip_key_equals(&mut self, x: Reg) { | ||||||
|         let x = self.v[x] as usize; |         if self.keys[self.v[x] as usize & 0xf] { | ||||||
|         if self.keys[x] { |  | ||||||
|             self.pc += 2; |             self.pc += 2; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     /// |`ExaE`| Skip next instruction if key != vX |     /// |`ExaE`| Skip next instruction if key != vX | ||||||
|     #[inline(always)] |     #[inline(always)] | ||||||
|     fn skip_key_not_equals(&mut self, x: Reg) { |     fn skip_key_not_equals(&mut self, x: Reg) { | ||||||
|         let x = self.v[x] as usize; |         if !self.keys[self.v[x] as usize & 0xf] { | ||||||
|         if !self.keys[x] { |  | ||||||
|             self.pc += 2; |             self.pc += 2; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -1315,8 +1229,99 @@ impl CPU { | |||||||
|             self.i += x as Adr + 1; |             self.i += x as Adr + 1; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | //////////////// SUPER CHIP //////////////// | ||||||
|  |  | ||||||
|  | impl CPU { | ||||||
|  |     /// |`00cN`| Scroll the screen down N lines | ||||||
|  |     #[inline(always)] | ||||||
|  |     fn scroll_down(&mut self, n: Nib, bus: &mut Bus) { | ||||||
|  |         match self.flags.draw_mode { | ||||||
|  |             true => { | ||||||
|  |                 // Get a line from the bus | ||||||
|  |                 for i in (0..16 * (64 - n as usize)).step_by(16).rev() { | ||||||
|  |                     let i = i + self.screen as usize; | ||||||
|  |                     let line: u128 = bus.read(i); | ||||||
|  |                     bus.write(i - (n as usize * 16), 0u128); | ||||||
|  |                     bus.write(i, line); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             false => { | ||||||
|  |                 // Get a line from the bus | ||||||
|  |                 for i in (0..8 * (32 - n as usize)).step_by(8).rev() { | ||||||
|  |                     let i = i + self.screen as usize; | ||||||
|  |                     let line: u64 = bus.read(i); | ||||||
|  |                     bus.write(i, 0u64); | ||||||
|  |                     bus.write(i + (n as usize * 8), line); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// |`00fb`| Scroll the screen right | ||||||
|  |     #[inline(always)] | ||||||
|  |     fn scroll_right(&mut self, bus: &mut (impl Read<u128> + Write<u128>)) { | ||||||
|  |         // Get a line from the bus | ||||||
|  |         for i in (0..16 * 64).step_by(16) { | ||||||
|  |             //let line: u128 = bus.read(self.screen + i) >> 4; | ||||||
|  |             bus.write(self.screen + i, bus.read(self.screen + i) >> 4); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     /// |`00fc`| Scroll the screen right | ||||||
|  |     #[inline(always)] | ||||||
|  |     fn scroll_left(&mut self, bus: &mut (impl Read<u128> + Write<u128>)) { | ||||||
|  |         // Get a line from the bus | ||||||
|  |         for i in (0..16 * 64).step_by(16) { | ||||||
|  |             let line: u128 = (bus.read(self.screen + i) & !(0xf << 124)) << 4; | ||||||
|  |             bus.write(self.screen + i, line); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// |`Dxyn`| | ||||||
|  |     /// Super-Chip extension high-resolution graphics mode | ||||||
|  |     #[inline(always)] | ||||||
|  |     fn draw_hires(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut Bus) { | ||||||
|  |         if !self.flags.quirks.draw_wait { | ||||||
|  |             self.flags.draw_wait = true; | ||||||
|  |         } | ||||||
|  |         let (w, h) = match self.flags.draw_mode { | ||||||
|  |             true => (128, 64), | ||||||
|  |             false => (64, 32), | ||||||
|  |         }; | ||||||
|  |         let (x, y) = (self.v[x] as u16 % w, self.v[y] as u16 % h); | ||||||
|  |         match n { | ||||||
|  |             0 => self.draw_schip_sprite(x, y, w, bus), | ||||||
|  |             _ => self.draw_sprite(x, y, n, w, h, bus), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     /// Draws a 16x16 Super Chip sprite | ||||||
|  |     #[inline(always)] | ||||||
|  |     fn draw_schip_sprite(&mut self, x: u16, y: u16, w: u16, bus: &mut Bus) { | ||||||
|  |         self.v[0xf] = 0; | ||||||
|  |         let w_bytes = w / 8; | ||||||
|  |         if let Some(sprite) = bus.get(self.i as usize..(self.i + 32) as usize) { | ||||||
|  |             let sprite = sprite.to_owned(); | ||||||
|  |             for (line, sprite) in sprite.chunks(2).enumerate() { | ||||||
|  |                 let sprite = u16::from_be_bytes( | ||||||
|  |                     sprite | ||||||
|  |                         .try_into() | ||||||
|  |                         .expect("Chunks should only return 2 bytes"), | ||||||
|  |                 ); | ||||||
|  |                 let addr = (y + line as u16) * w_bytes + x / 8 + self.screen; | ||||||
|  |                 let sprite = (sprite as u32) << (16 - (x % 8)); | ||||||
|  |                 let screen: u32 = bus.read(addr); | ||||||
|  |                 bus.write(addr, screen ^ sprite); | ||||||
|  |                 if screen & sprite != 0 { | ||||||
|  |                     self.v[0xf] += 1; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// |`Fx30`| (Super-Chip) 16x16 equivalent of Fx29 |     /// |`Fx30`| (Super-Chip) 16x16 equivalent of Fx29 | ||||||
|  |     /// | ||||||
|  |     /// TODO: Actually make and import the 16x font | ||||||
|     #[inline(always)] |     #[inline(always)] | ||||||
|     fn load_big_sprite(&mut self, x: Reg) { |     fn load_big_sprite(&mut self, x: Reg) { | ||||||
|         self.i = self.font + (5 * 8) + (16 * (self.v[x] as Adr % 0x10)); |         self.i = self.font + (5 * 8) + (16 * (self.v[x] as Adr % 0x10)); | ||||||
| @@ -1345,4 +1350,19 @@ impl CPU { | |||||||
|             self.v[reg] = *value; |             self.v[reg] = *value; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Initialize lores mode | ||||||
|  |     fn init_lores(&mut self, bus: &mut Bus) { | ||||||
|  |         self.flags.draw_mode = false; | ||||||
|  |         let scraddr = self.screen as usize; | ||||||
|  |         bus.set_region(Region::Screen, scraddr..scraddr + 256); | ||||||
|  |         self.clear_screen(bus); | ||||||
|  |     } | ||||||
|  |     /// Initialize hires mode | ||||||
|  |     fn init_hires(&mut self, bus: &mut Bus) { | ||||||
|  |         self.flags.draw_mode = true; | ||||||
|  |         let scraddr = self.screen as usize; | ||||||
|  |         bus.set_region(Region::Screen, scraddr..scraddr + 1024); | ||||||
|  |         self.clear_screen(bus); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -78,7 +78,7 @@ pub enum Insn { | |||||||
|     rand { B: u8, x: usize }, |     rand { B: u8, x: usize }, | ||||||
|     /// | Dxyn | Draws n-byte sprite to the screen at coordinates (vX, vY) |     /// | Dxyn | Draws n-byte sprite to the screen at coordinates (vX, vY) | ||||||
|     #[opcode = "0xdxyn"] |     #[opcode = "0xdxyn"] | ||||||
|     draw { x: usize, y: usize, n: u8 }, |     draw { y: usize, x: usize, n: u8 }, | ||||||
|     /// | eX9e | Skip next instruction if key == vX |     /// | eX9e | Skip next instruction if key == vX | ||||||
|     #[opcode = "0xex9e"] |     #[opcode = "0xex9e"] | ||||||
|     sek { x: usize }, |     sek { x: usize }, | ||||||
| @@ -170,7 +170,7 @@ impl Display for Insn { | |||||||
|             Insn::movI { A }       => write!(f, "mov    ${A:03x}, I"), |             Insn::movI { A }       => write!(f, "mov    ${A:03x}, I"), | ||||||
|             Insn::jmpr { A }       => write!(f, "jmp    ${A:03x}+v0"), |             Insn::jmpr { A }       => write!(f, "jmp    ${A:03x}+v0"), | ||||||
|             Insn::rand { B, x }    => write!(f, "rand   #{B:02x}, v{x:X}"), |             Insn::rand { B, x }    => write!(f, "rand   #{B:02x}, v{x:X}"), | ||||||
|             Insn::draw { x, y, n } => write!(f, "draw   #{n:x}, v{x:X}, v{y:X}"), |             Insn::draw { y, x, n } => write!(f, "draw   #{n:x}, v{x:X}, v{y:X}"), | ||||||
|             Insn::sek { x }        => write!(f, "sek    v{x:X}"), |             Insn::sek { x }        => write!(f, "sek    v{x:X}"), | ||||||
|             Insn::snek { x }       => write!(f, "snek   v{x:X}"), |             Insn::snek { x }       => write!(f, "snek   v{x:X}"), | ||||||
|             Insn::getdt { x }      => write!(f, "mov    DT, v{x:X}"), |             Insn::getdt { x }      => write!(f, "mov    DT, v{x:X}"), | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user