schip: Improve architecture & compatibility somewhat
This commit is contained in:
parent
9fcae555ce
commit
8bb34f2593
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}"),
|
||||||
|
Loading…
Reference in New Issue
Block a user