Added raddad772/jsmoo's Z80 test runner

Special thanks to raddad772 https://github.com/raddad772

Also added some fixes to the Z80 for panicking math operations, but
it still won't complete due to an unimplemented instruction
This commit is contained in:
transistor 2023-05-09 21:50:42 -07:00
parent 5e228c377e
commit f8083db181
22 changed files with 1609 additions and 27 deletions

2
.gitignore vendored
View File

@ -13,3 +13,5 @@ binaries/*/*.bin
binaries/*/*.smd
emulator/frontends/pixels/dist/
tests/ProcessorTests/
tests/jsmoo/

13
Cargo.lock generated
View File

@ -1031,6 +1031,19 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rad_tests"
version = "0.1.0"
dependencies = [
"clap 3.2.22",
"flate2",
"moa_core",
"moa_z80",
"serde",
"serde_derive",
"serde_json",
]
[[package]]
name = "raw-window-handle"
version = "0.3.4"

View File

@ -4,7 +4,8 @@ members = [
"emulator/frontends/common",
"emulator/frontends/console",
"emulator/frontends/minifb",
"tests/harte_tests"
"tests/harte_tests",
"tests/rad_tests"
]
exclude = [
"emulator/frontends/pixels",

View File

@ -18,6 +18,12 @@ For more details on how it works, check out this post about how I started the pr
For more about the Sega Genesis support, check out this series I wrote about implementing it:
[Emulating the Sega Genesis](http://jabberwocky.ca/posts/2022-01-emulating_the_sega_genesis_part1.html)
I've also generated rustdocs of the workspace. All the various crates within moa
are listed in the crates section in the sidebar on the left. There's not a lot
of doc comments in the code yet but I plan to eventually write more:
[rustdocs for moa_core](http://jabberwocky.ca/moa/doc/moa_core/)
[rustdocs for ym2612](http://jabberwocky.ca/moa/doc/moa_peripherals_yamaha/ym2612/index.html)
Sega Genesis/MegaDrive
----------------------
@ -67,6 +73,7 @@ though that doesn't happen during normal play
![alt text](images/sega-genesis-sonic2-demo.gif)
Computie
--------
@ -84,6 +91,7 @@ host, and set up host routing. The exact commands in
`src/machines/computie.rs` might need to be adjusted to work on different
hosts.
TRS-80
------

View File

@ -416,3 +416,15 @@ General Work
making the fps drop down to 47-50 instead of 60. I'm not sure if that's because the older js
version does some funny clock stuff or if the concept itself adds a lot of overhead for some as
yet unknown reason
2023-05-08
- audio works in Sonic1. Mortal Kombat 2 has a bit of audio, but periods of silence where it's
clearly turning notes on and off and changing frequencies, but nothing is coming out. Then every
other has silence but they seems to write to the ym2612 registers with note on/off (reg 0x28), and
total level or frequency changes which make sense for audio to be playing but nothing. I had sort
of suspected the coprocessor communication until I printed the register set values which clearly
shows audio is being requested but nothing is produced, so still some big issues with the ym2612
- looking at earthworm jim, it's sending a lot of commands to the 0x40s on bank 0 and 1, which
doesn't make a heck of a lot of sense to be changing the volume level but nothing else all the
time. It doesn't seem to set the initial frequencies. So it's possible that a bug in the Z80
code is making it run incorrectly and thus not producing correct register commands to the ym2612

View File

@ -211,3 +211,53 @@ impl Device {
}
/*
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct DeviceId(usize);
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Interrupt {
Number(usize),
}
pub enum InterruptPriority {
NonMaskable,
Number(usize),
}
struct InterruptPort {
id: usize,
controller: TransmutableBox,
}
impl InterruptPort {
fn check_pending(&self) -> Option<Interrupt> {
self.controller.borrow_mut().as_interrupt_controller().check_pending(self.id)
}
fn acknowledge(&self, interrupt: Interrupt) -> Result<(), Error> {
self.controller.borrow_mut().as_interrupt_controller().acknowledge(self.id, interrupt)
}
}
//pub trait InterruptPort {
// fn check_pending(&mut self, id: DeviceId) -> Option<Interrupt>;
// fn acknowledge(&mut self, id: DeviceId, interrupt: Interrupt) -> Result<(), Error>;
//}
//pub trait Interrupter {
// fn trigger(&mut self, id: DeviceId, interrupt: Interrupt) -> Result<(), Error>;
//}
struct Interrupter {
input_id: usize,
interrupt: Interrupt,
controller: Rc<RefCell<TransmutableBox>>,
}
pub trait InterruptController {
fn connect(&mut self, priority: InterruptPriority) -> Result<InterruptPort, Error>;
fn check_pending(&mut self, id: usize) -> Option<Interrupt>;
fn acknowledge(&mut self, id: usize, interrupt: Interrupt) -> Result<(), Error>;
}
*/

View File

@ -314,11 +314,11 @@ impl M68k {
let src_parts = get_nibbles_from_byte(src_val);
let dest_parts = get_nibbles_from_byte(dest_val);
let binary_result = src_val + dest_val + extend_flag;
let mut result = src_parts.1 + dest_parts.1 + extend_flag;
if result > 0x09 { result += 0x06 };
let binary_result = src_val.wrapping_add(dest_val).wrapping_add(extend_flag);
let mut result = src_parts.1.wrapping_add(dest_parts.1).wrapping_add(extend_flag);
if result > 0x09 { result = result.wrapping_add(0x06) };
result += src_parts.0 + dest_parts.0;
if result > 0x99 { result += 0x60 };
if result > 0x99 { result = result.wrapping_add(0x60) };
let carry = (result & 0xFFFFFF00) != 0;
self.set_target_value(dest, result, Size::Byte, Used::Twice)?;
@ -434,7 +434,7 @@ impl M68k {
fn execute_bcc(&mut self, cond: Condition, offset: i32) -> Result<(), Error> {
let should_branch = self.get_current_condition(cond);
if should_branch {
if let Err(err) = self.set_pc((self.decoder.start + 2).wrapping_add(offset as u32)) {
if let Err(err) = self.set_pc(self.decoder.start.wrapping_add(2).wrapping_add(offset as u32)) {
self.state.pc -= 2;
return Err(err);
}
@ -444,7 +444,7 @@ impl M68k {
#[inline]
fn execute_bra(&mut self, offset: i32) -> Result<(), Error> {
if let Err(err) = self.set_pc((self.decoder.start + 2).wrapping_add(offset as u32)) {
if let Err(err) = self.set_pc(self.decoder.start.wrapping_add(2).wrapping_add(offset as u32)) {
self.state.pc -= 2;
return Err(err);
}
@ -456,7 +456,7 @@ impl M68k {
self.push_long(self.state.pc)?;
let sp = *self.get_stack_pointer_mut();
self.debugger.stack_tracer.push_return(sp);
if let Err(err) = self.set_pc((self.decoder.start + 2).wrapping_add(offset as u32)) {
if let Err(err) = self.set_pc(self.decoder.start.wrapping_add(2).wrapping_add(offset as u32)) {
self.state.pc -= 2;
return Err(err);
}
@ -629,7 +629,7 @@ impl M68k {
let next = ((get_value_sized(self.state.d_reg[reg as usize], Size::Word) as u16) as i16).wrapping_sub(1);
set_value_sized(&mut self.state.d_reg[reg as usize], next as u32, Size::Word);
if next != -1 {
if let Err(err) = self.set_pc((self.decoder.start + 2).wrapping_add(offset as u32)) {
if let Err(err) = self.set_pc(self.decoder.start.wrapping_add(2).wrapping_add(offset as u32)) {
self.state.pc -= 2;
return Err(err);
}
@ -1000,7 +1000,7 @@ impl M68k {
match dir {
Direction::ToTarget => {
let mut shift = (size.in_bits() as i32) - 8;
let mut addr = ((*self.get_a_reg_mut(areg) as i32) + (offset as i32)) as Address;
let mut addr = (*self.get_a_reg_mut(areg)).wrapping_add_signed(offset as i32) as Address;
while shift >= 0 {
let byte = (self.state.d_reg[dreg as usize] >> shift) as u8;
self.port.write_u8(self.current_clock, addr, byte)?;
@ -1010,7 +1010,7 @@ impl M68k {
},
Direction::FromTarget => {
let mut shift = (size.in_bits() as i32) - 8;
let mut addr = ((*self.get_a_reg_mut(areg) as i32) + (offset as i32)) as Address;
let mut addr = (*self.get_a_reg_mut(areg)).wrapping_add_signed(offset as i32) as Address;
while shift >= 0 {
let byte = self.port.read_u8(self.current_clock, addr)?;
self.state.d_reg[dreg as usize] |= (byte as u32) << shift;

View File

@ -5,7 +5,7 @@ use crate::state::Z80;
use crate::decode::Z80Decoder;
#[derive(Default)]
#[derive(Clone, Default)]
pub struct Z80Debugger {
pub enabled: bool,
pub breakpoints: Vec<u16>,

View File

@ -172,6 +172,7 @@ pub enum Instruction {
XOR(Target),
}
#[derive(Clone)]
pub struct Z80Decoder {
pub clock: ClockTime,
pub start: u16,
@ -678,14 +679,14 @@ impl Z80Decoder {
fn read_instruction_byte(&mut self, device: &mut dyn Addressable) -> Result<u8, Error> {
let byte = device.read_u8(self.clock, self.end as Address)?;
self.end += 1;
self.end = self.end.wrapping_add(1);
self.execution_time += 4;
Ok(byte)
}
fn read_instruction_word(&mut self, device: &mut dyn Addressable) -> Result<u16, Error> {
let word = device.read_leu16(self.clock, self.end as Address)?;
self.end += 2;
self.end = self.end.wrapping_add(2);
self.execution_time += 8;
Ok(word)
}

View File

@ -217,7 +217,7 @@ impl Z80 {
self.set_register_value(Register::B, result);
if result != 0 {
self.state.pc = ((self.state.pc as i16) + (offset as i16)) as u16;
self.state.pc = self.state.pc.wrapping_add_signed(offset as i16);
}
},
Instruction::EI => {
@ -251,6 +251,7 @@ impl Z80 {
},
Instruction::HALT => {
self.state.status = Status::Halted;
self.state.pc -= 1;
},
Instruction::IM(mode) => {
self.state.interrupt_mode = mode;
@ -297,11 +298,11 @@ impl Z80 {
}
},
Instruction::JR(offset) => {
self.state.pc = ((self.state.pc as i16) + (offset as i16)) as u16;
self.state.pc = self.state.pc.wrapping_add_signed(offset as i16);
},
Instruction::JRcc(cond, offset) => {
if self.get_current_condition(cond) {
self.state.pc = ((self.state.pc as i16) + (offset as i16)) as u16;
self.state.pc = self.state.pc.wrapping_add_signed(offset as i16);
}
},
Instruction::LD(dest, src) => {
@ -367,9 +368,9 @@ impl Z80 {
//},
//Instruction::OUTic(reg) => {
//},
Instruction::OUTx(port) => {
Instruction::OUTx(_port) => {
// TODO this needs to be fixed
println!("OUT ({:x}), {:x} {}", port, self.state.reg[Register::A as usize], self.state.reg[Register::A as usize] as char);
//println!("OUT ({:x}), {:x} {}", port, self.state.reg[Register::A as usize], self.state.reg[Register::A as usize] as char);
},
Instruction::POP(regpair) => {
let value = self.pop_word()?;
@ -699,7 +700,7 @@ impl Z80 {
Ok(self.port.read_u8(self.current_clock, addr as Address)?)
},
Target::IndirectOffset(reg, offset) => {
let addr = (self.get_index_register_value(reg) as i16) + (offset as i16);
let addr = self.get_index_register_value(reg).wrapping_add_signed(offset as i16);
Ok(self.port.read_u8(self.current_clock, addr as Address)?)
},
Target::Immediate(data) => Ok(data),
@ -715,7 +716,7 @@ impl Z80 {
self.port.write_u8(self.current_clock, addr as Address, value)?;
},
Target::IndirectOffset(reg, offset) => {
let addr = (self.get_index_register_value(reg) as i16) + (offset as i16);
let addr = self.get_index_register_value(reg).wrapping_add_signed(offset as i16);
self.port.write_u8(self.current_clock, addr as Address, value)?;
},
_ => panic!("Unsupported LoadTarget for set"),

View File

@ -104,6 +104,7 @@ impl Z80State {
}
}
#[derive(Clone)]
pub struct Z80 {
pub cputype: Z80Type,
pub frequency: Frequency,

46
tests/README.md Normal file
View File

@ -0,0 +1,46 @@
Tests
=====
This directory contains CPU tests for the 68k and Z80. The test cases themselves are provided by
Tom Harte and raddad772, and must be cloned from their respective repositories before running the
tests.
Downloading
-----------
To download the 68k tests, from the `tests/` directory, run:
```sh
git clone git@github.com:TomHarte/ProcessorTests.git
```
To download the Z80 tests, from the `tests/` directory, run:
```sh
git clone --no-checkout git@github.com:raddad772/jsmoo.git
cd jsmoo
git checkout origin/HEAD -- misc/tests/GeneratedTests
```
Running
-------
The 68k tests can be run from the moa root with:
```sh
tests/harte_tests/run_all.sh
```
By default, the script will use the compressed versions of the tests which are slower to run because
they must be unzipped every time the tests are run. To speed it up for repeat runs, the tests can be
gunzip'ed to their own directory and the test suite location can be change on the command line or in
the script to point to the uncompressed versions
The Z80 tests can be run with:
```sh
tests/rad_tests/run_all.sh
```
Thanks to [Tom Harte](https://github.com/TomHarte) and [raddad772](https://github.com/raddad772) for
providing these incredibly valuable tests

View File

@ -1,7 +1,7 @@
[package]
name = "harte_tests"
version = "0.1.0"
edition = "2018"
edition = "2021"
[dependencies]
moa_core = { path = "../../emulator/core" }

View File

@ -7,5 +7,5 @@ RESULTS=latest.txt
cd $LOCATION
echo "Last run on $DATE at commit $COMMIT" | tee $RESULTS
echo "" | tee -a $RESULTS
cargo run -- -q --testsuite "../ProcessorTests/680x0/68000/uncompressed/" | tee -a $RESULTS
cargo run -- -q --testsuite "../ProcessorTests/680x0/68000/v1/" | tee -a $RESULTS
}

View File

@ -7,5 +7,5 @@ RESULTS=latest-excluding-addr-error.txt
cd $LOCATION
echo "Last run on $DATE at commit $COMMIT" | tee $RESULTS
echo "" | tee -a $RESULTS
cargo run -- -q --testsuite "../ProcessorTests/680x0/68000/uncompressed/" -e exclude-addr | tee -a $RESULTS
cargo run -- -q --testsuite "../ProcessorTests/680x0/68000/v1/" -e exclude-addr | tee -a $RESULTS
}

View File

@ -1,5 +1,5 @@
const DEFAULT_HART_TESTS: &str = "tests/ProcessorTests/680x0/68000/v1/";
const DEFAULT_HARTE_TESTS: &str = "tests/ProcessorTests/680x0/68000/v1/";
use std::io::prelude::*;
use std::fmt::Debug;
@ -41,7 +41,7 @@ struct Args {
#[clap(short, long)]
timing: bool,
/// Directory to the test suite to run
#[clap(long, default_value = DEFAULT_HART_TESTS)]
#[clap(long, default_value = DEFAULT_HARTE_TESTS)]
testsuite: String,
#[clap(long, short, arg_enum, default_value_t = Selection::Include)]
exceptions: Selection,

View File

@ -0,0 +1,13 @@
[package]
name = "rad_tests"
version = "0.1.0"
edition = "2021"
[dependencies]
moa_core = { path = "../../emulator/core" }
moa_z80 = { path = "../../emulator/cpus/z80" }
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
flate2 = "1.0"
clap = { version = "3.2.20", features = ["derive"] }

19
tests/rad_tests/README.md Normal file
View File

@ -0,0 +1,19 @@
Rad Test Suite
==============
This is a test running for moa that uses the [raddad772/jsmoo tests](https://github.com/raddad772/jsmoo).
To run, the jsmoo repository must be cloned into tests/ and then from the moa project root:
```shell
cargo run -p rad_tests -- [FILTER]
```
An optional filter can be specified, which will only run test files who's file name starts with the
filter text. Timing tests are not done by default, but can be run with `-t` or `--timing`. The output
can be increased or decreased with the `--debug` or `--quiet` flags, respectively.
Special thanks to [raddad772](https://github.com/raddad772) for the incredibly
exhaustive and thorough set of testcases. Emulators everywhere will be better
for your efforts!

1053
tests/rad_tests/latest.txt Normal file

File diff suppressed because it is too large Load Diff

11
tests/rad_tests/run_all.sh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/bash
COMMIT=$(git rev-parse HEAD)
DATE=$(date --iso)
LOCATION=$(dirname ${BASH_SOURCE[0]})
RESULTS=latest.txt
{
cd $LOCATION
echo "Last run on $DATE at commit $COMMIT" | tee $RESULTS
echo "" | tee -a $RESULTS
cargo run -- -q --testsuite "../jsmoo/misc/tests/GeneratedTests/z80/v1/" | tee -a $RESULTS
}

351
tests/rad_tests/src/main.rs Normal file
View File

@ -0,0 +1,351 @@
const DEFAULT_RAD_TESTS: &str = "tests/jsmoo/misc/tests/GeneratedTests/z80/v1/";
use std::io::prelude::*;
use std::fmt::Debug;
use std::path::PathBuf;
use std::time::SystemTime;
use std::fs::{self, File};
use clap::{Parser, ArgEnum};
use flate2::read::GzDecoder;
use serde_derive::Deserialize;
use moa_core::{System, Error, MemoryBlock, BusPort, Frequency, Address, Addressable, Steppable, wrap_transmutable};
use moa_z80::{Z80, Z80Type};
use moa_z80::state::Status;
#[derive(Copy, Clone, PartialEq, Eq, ArgEnum)]
enum Selection {
Include,
Exclude,
ExcludeAddr,
Only,
}
#[derive(Parser)]
struct Args {
/// Filter the tests by gzip file name
filter: Option<String>,
/// Only run the one test with the given number
#[clap(short, long)]
only: Option<String>,
/// Dump the CPU state when a test fails
#[clap(short, long)]
debug: bool,
/// Only print a summary for each test file
#[clap(short, long)]
quiet: bool,
/// Directory to the test suite to run
#[clap(long, default_value = DEFAULT_RAD_TESTS)]
testsuite: String,
#[clap(long, short, arg_enum, default_value_t = Selection::Include)]
exceptions: Selection,
}
fn main() {
let args = Args::parse();
run_all_tests(&args);
}
#[derive(Debug, Deserialize)]
struct TestState {
pc: u16,
sp: u16,
a: u8,
b: u8,
c: u8,
d: u8,
e: u8,
f: u8,
h: u8,
l: u8,
//i: u8,
//r: u8,
//ei: u8,
//wz: u8,
ix: u16,
iy: u16,
af_: u16,
bc_: u16,
de_: u16,
hl_: u16,
//im: u8,
//p: u8,
//q: u8,
//iff1: u8,
//iff2: u8,
ram: Vec<(u16, u8)>,
}
#[derive(Debug, Deserialize)]
struct TestCase {
name: String,
#[serde(rename(deserialize = "initial"))]
initial_state: TestState,
#[serde(rename(deserialize = "final"))]
final_state: TestState,
}
impl TestState {
pub fn dump(&self) {
println!(" a: {:02x} a': {:02x}", self.a, self.af_ >> 8);
println!(" b: {:02x} b': {:02x}", self.b, self.bc_ & 0xff);
println!(" c: {:02x} c': {:02x}", self.c, self.bc_ >> 8);
println!(" d: {:02x} d': {:02x}", self.d, self.de_ & 0xff);
println!(" e: {:02x} e': {:02x}", self.e, self.de_ >> 8);
println!(" f: {:02x} f': {:02x}", self.f, self.af_ & 0xff);
println!(" h: {:02x} h': {:02x}", self.h, self.hl_ >> 8);
println!(" l: {:02x} l': {:02x}", self.l, self.hl_ & 0xff);
println!("pc: {:04x} sp: {:04x}", self.pc, self.sp);
println!("ix: {:04x} iy: {:04x}", self.ix, self.iy);
println!("ram: ");
for (addr, byte) in self.ram.iter() {
println!("{:04x} {:02x} ", *addr, *byte);
}
}
}
impl TestCase {
pub fn dump(&self) {
println!("{}", self.name);
println!("initial:");
self.initial_state.dump();
println!("final:");
self.final_state.dump();
}
}
fn init_execute_test(cputype: Z80Type, state: &TestState) -> Result<(Z80, System), Error> {
let mut system = System::default();
// Insert basic initialization
let data = vec![0; 0x01000000];
let mem = MemoryBlock::new(data);
system.add_addressable_device(0x00000000, wrap_transmutable(mem)).unwrap();
let port = BusPort::new(0, 16, 8, system.bus.clone());
let mut cpu = Z80::new(cputype, Frequency::from_mhz(10), port);
cpu.state.status = Status::Running;
load_state(&mut cpu, &mut system, state)?;
Ok((cpu, system))
}
fn assert_value<T: PartialEq + Debug>(actual: T, expected: T, message: &str) -> Result<(), Error> {
if actual == expected {
Ok(())
} else {
Err(Error::assertion(&format!("{:?} != {:?}, {}", actual, expected, message)))
}
}
fn load_state(cpu: &mut Z80, system: &mut System, initial: &TestState) -> Result<(), Error> {
cpu.state.reg[0] = initial.b;
cpu.state.reg[1] = initial.c;
cpu.state.reg[2] = initial.d;
cpu.state.reg[3] = initial.e;
cpu.state.reg[4] = initial.h;
cpu.state.reg[5] = initial.l;
cpu.state.reg[6] = initial.a;
cpu.state.reg[7] = initial.f;
cpu.state.shadow_reg[0] = (initial.bc_ >> 8) as u8;
cpu.state.shadow_reg[1] = (initial.bc_ & 0xff) as u8;
cpu.state.shadow_reg[2] = (initial.de_ >> 8) as u8;
cpu.state.shadow_reg[3] = (initial.de_ & 0xff) as u8;
cpu.state.shadow_reg[4] = (initial.hl_ >> 8) as u8;
cpu.state.shadow_reg[5] = (initial.hl_ & 0xff) as u8;
cpu.state.shadow_reg[6] = (initial.af_ >> 8) as u8;
cpu.state.shadow_reg[7] = (initial.af_ & 0xff) as u8;
cpu.state.ix = initial.ix;
cpu.state.iy = initial.iy;
cpu.state.sp = initial.sp;
cpu.state.pc = initial.pc;
// Load data bytes into memory
for (addr, byte) in initial.ram.iter() {
system.get_bus().write_u8(system.clock, *addr as u64, *byte)?;
}
Ok(())
}
fn assert_state(cpu: &Z80, system: &System, expected: &TestState) -> Result<(), Error> {
assert_value(cpu.state.reg[0], expected.b, "b")?;
assert_value(cpu.state.reg[1], expected.c, "c")?;
assert_value(cpu.state.reg[2], expected.d, "d")?;
assert_value(cpu.state.reg[3], expected.e, "e")?;
assert_value(cpu.state.reg[4], expected.h, "h")?;
assert_value(cpu.state.reg[5], expected.l, "l")?;
assert_value(cpu.state.reg[6], expected.a, "a")?;
assert_value(cpu.state.reg[7], expected.f, "f")?;
assert_value(cpu.state.shadow_reg[0], (expected.bc_ >> 8) as u8, "b'")?;
assert_value(cpu.state.shadow_reg[1], (expected.bc_ & 0xff) as u8, "c'")?;
assert_value(cpu.state.shadow_reg[2], (expected.de_ >> 8) as u8, "d'")?;
assert_value(cpu.state.shadow_reg[3], (expected.de_ & 0xff) as u8, "e'")?;
assert_value(cpu.state.shadow_reg[4], (expected.hl_ >> 8) as u8, "h'")?;
assert_value(cpu.state.shadow_reg[5], (expected.hl_ & 0xff) as u8, "l'")?;
assert_value(cpu.state.shadow_reg[6], (expected.af_ >> 8) as u8, "a'")?;
assert_value(cpu.state.shadow_reg[7], (expected.af_ & 0xff) as u8, "f'")?;
assert_value(cpu.state.ix, expected.ix, "ix")?;
assert_value(cpu.state.iy, expected.iy, "iy")?;
assert_value(cpu.state.sp, expected.sp, "sp")?;
assert_value(cpu.state.pc, expected.pc, "pc")?;
let addr_mask = cpu.port.address_mask();
// Load data bytes into memory
for (addr, byte) in expected.ram.iter() {
let actual = system.get_bus().read_u8(system.clock, *addr as Address & addr_mask)?;
assert_value(actual, *byte, &format!("ram at {:x}", addr))?;
}
Ok(())
}
fn step_cpu_and_assert(cpu: &mut Z80, system: &System, case: &TestCase) -> Result<(), Error> {
let _clock_elapsed = cpu.step(&system)?;
assert_state(&cpu, &system, &case.final_state)?;
Ok(())
}
fn run_test(case: &TestCase, args: &Args) -> Result<(), Error> {
let (mut cpu, system) = init_execute_test(Z80Type::Z80, &case.initial_state).unwrap();
let mut initial_cpu = cpu.clone();
let result = step_cpu_and_assert(&mut cpu, &system, case);
match result {
Ok(()) => Ok(()),
Err(err) => {
if !args.quiet {
if args.debug {
case.dump();
println!("");
initial_cpu.dump_state(system.clock);
cpu.dump_state(system.clock);
}
println!("FAILED: {}", err.msg);
}
Err(err)
},
}
}
fn test_json_file(path: PathBuf, args: &Args) -> (usize, usize, String) {
let extension = path.extension().unwrap();
let cases: Vec<TestCase> = if extension == "gz" {
let file = File::open(&path).unwrap();
let mut decoder = GzDecoder::new(file);
let mut data = String::new();
decoder.read_to_string(&mut data).unwrap();
serde_json::from_str(&data).unwrap()
} else {
let data = fs::read(&path).unwrap();
serde_json::from_slice(&data).unwrap()
};
let mut passed = 0;
let mut failed = 0;
for mut case in cases {
if let Some(only) = args.only.as_ref() {
if !case.name.ends_with(only) {
continue;
}
}
// Sort the ram memory for debugging help
if args.debug {
case.initial_state.ram.sort_by_key(|(addr, _)| *addr);
case.final_state.ram.sort_by_key(|(addr, _)| *addr);
}
if !args.quiet {
println!("Running test {}", case.name);
}
let result = run_test(&case, args);
if let Err(err) = result {
failed += 1;
if !args.quiet {
println!("FAILED: {:?}", err);
}
} else {
passed += 1
}
}
let name = path.file_name().unwrap().to_str().unwrap();
let message = if failed == 0 {
format!("{} completed, all passed!", name)
} else {
format!("{} completed: {} passed, {} FAILED", name, passed, failed)
};
(passed, failed, message)
}
fn run_all_tests(args: &Args) {
let mut passed = 0;
let mut failed = 0;
let mut messages = vec![];
let mut tests: Vec<PathBuf> = fs::read_dir(&args.testsuite)
.unwrap()
.map(|dirent| dirent.unwrap().path())
.collect();
tests.sort();
let start = SystemTime::now();
for path in tests {
// Only test gzip files (the repo has .md files as well)
let extension = path.extension().unwrap();
if extension != "json" && extension != "gz" {
continue;
}
// If specified, only test files that start with a given string
if let Some(filter) = &args.filter {
if !path.file_name().unwrap().to_str().unwrap().starts_with(filter) {
continue;
}
}
// Run every test in the file
let (test_passed, test_failed, message) = test_json_file(path, args);
// In quiet mode, print each summary as it's received to give a progress update
if args.quiet {
println!("{}", message);
}
passed += test_passed;
failed += test_failed;
messages.push(message);
}
let elapsed_secs = start.elapsed().unwrap().as_secs();
// Print the stored summary if not in quite mode
if !args.quiet {
for message in messages {
println!("{}", message);
}
}
println!("");
println!("passed: {}, failed: {}, total {:.0}%", passed, failed, ((passed as f32) / (passed as f32 + failed as f32)) * 100.0);
println!("completed in {}m {}s", elapsed_secs / 60, elapsed_secs % 60);
}

View File

@ -38,7 +38,7 @@
* I think the overflowing add and subs return the original number and not the overflowed result. I might have already checked that
in the m68k impl but I should check again
* double check the functioning of the banked areas and register settings for Z80 coprocessor
* test the Z80 more, add tests
* test the Z80 more, add tests like jsmoo's
* add opentelemetry if it can be wasm compatible, or some kind of timing for giving an average framerate
* improve performance