mirror of
https://github.com/transistorfet/moa.git
synced 2025-02-16 11:30:33 +00:00
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:
parent
5e228c377e
commit
f8083db181
2
.gitignore
vendored
2
.gitignore
vendored
@ -13,3 +13,5 @@ binaries/*/*.bin
|
||||
binaries/*/*.smd
|
||||
|
||||
emulator/frontends/pixels/dist/
|
||||
tests/ProcessorTests/
|
||||
tests/jsmoo/
|
||||
|
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -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"
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
------
|
||||
|
||||
|
12
docs/log.txt
12
docs/log.txt
@ -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
|
||||
|
@ -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>;
|
||||
}
|
||||
*/
|
||||
|
@ -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;
|
||||
|
@ -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>,
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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"),
|
||||
|
@ -104,6 +104,7 @@ impl Z80State {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Z80 {
|
||||
pub cputype: Z80Type,
|
||||
pub frequency: Frequency,
|
||||
|
46
tests/README.md
Normal file
46
tests/README.md
Normal 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
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "harte_tests"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
moa_core = { path = "../../emulator/core" }
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
13
tests/rad_tests/Cargo.toml
Normal file
13
tests/rad_tests/Cargo.toml
Normal 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
19
tests/rad_tests/README.md
Normal 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
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
11
tests/rad_tests/run_all.sh
Executable 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
351
tests/rad_tests/src/main.rs
Normal 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);
|
||||
}
|
||||
|
2
todo.txt
2
todo.txt
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user