/* test-Z80
______ ______ ______
/\___ \/\ __ \\ __ \
____ \/__/ /\_\ __ \\ \/\ \ ________________________________________________
| /\_____\\_____\\_____\ |
| Zilog \/_____//_____//_____/ CPU Emulator Test Tool |
| Copyright (C) 2021-2022 Manuel Sainz de Baranda y Goñi. |
| |
| This program is free software: you can redistribute it and/or modify it |
| under the terms of the GNU General Public License as published by the Free |
| Software Foundation, either version 3 of the License, or (at your option) |
| any later version. |
| |
| This program is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
| or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| for more details. |
| |
| You should have received a copy of the GNU General Public License along |
| with this program. If not, see . |
| |
'=============================================================================*/
#ifdef TEST_Z80_WITH_ARCHIVE_EXTRACTION
# include
# include
# include
#endif
#include
#include
#include
#include
#include
#include
/* MARK: - Constants: Opcodes */
#define OPCODE_NOP 0x00
#define OPCODE_RET 0xC9
#define OPCODE_HALT 0x76
#define OPCODE_CALL_WORD 0xCD
#define OPCODE_JP_WORD 0xC3
/* MARK: - Constants: Test Formats */
/*----------------------------.
| CP/M program in COM format. |
'============================*/
#define TEST_FORMAT_CPM 0
/*----------------------------------------------------------------------.
| ZX Spectrum TAP image. Different versions of the Z80 Instruction Set |
| Exerciser adapted and improved by Jonathan Graham Harston and others. |
'======================================================================*/
#define TEST_FORMAT_HARSTON 1
/*--------------------------------------------------------------.
| ZX Spectrum TAP image. Tapes of the Zilog Z80 CPU Test Suite, |
| written by Patrik Rak. |
'==============================================================*/
#define TEST_FORMAT_RAK 2
/*-----------------------------------------------------------------.
| ZX Spectrum TAP image. Z80 Test Suite, written by Mark Woodmass. |
'=================================================================*/
#define TEST_FORMAT_WOODMASS 3
/* MARK: - Types */
typedef struct {
/* Name of the archive if the test is compressed; Z_NULL otherwise. */
char const* archive_name;
/* Name of the test program file, or the path to the file inside the
archive if the file is compressed. */
char const* file_path;
/* Size of the program file. */
zuint16 file_size;
/* Offset of the executable code inside the program file. */
zuint16 code_offset;
/* Size of the executable code. */
zuint16 code_size;
/* Memory address where to jump to start executing the program. */
zuint16 start_address; /* */
/* Value of the PC register once the test completes. */
zuint16 exit_address;
/* Format of the program file. */
zuint8 format;
/* Total number of lines printed by the test when it passes. */
zuint8 lines_expected;
} Test;
/* MARK: - Global Variables */
static Test const tests[22] = {
{"Yaze v1.14 (2004-04-23)(Cringle, Frank D.)(Sources)[!].tar.gz", "yaze-1.14/test/zexdoc.com", 8704, 0, 8704, 0x0100, 0, TEST_FORMAT_CPM, 68},
{Z_NULL, "Z80 Documented Instruction Set Exerciser for Spectrum (2018)(Harston, Jonathan Graham)[!].tap", 8716, 91, 8624, 0x8000, 0x803D, TEST_FORMAT_HARSTON, 69},
{"Yaze v1.14 (2004-04-23)(Cringle, Frank D.)(Sources)[!].tar.gz", "yaze-1.14/test/zexall.com", 8704, 0, 8704, 0x0100, 0, TEST_FORMAT_CPM, 68},
{Z_NULL, "Z80 Full Instruction Set Exerciser for Spectrum (2009)(Bobrowski, Jan)[!].tap", 8656, 108, 8547, 0x8000, 0x803D, TEST_FORMAT_HARSTON, 69},
{Z_NULL, "Z80 Full Instruction Set Exerciser for Spectrum (2011)(Bobrowski, Jan)(Narrowed to BIT Instructions)[!].tap", 8656, 108, 8547, 0x8000, 0x803D, TEST_FORMAT_HARSTON, 4},
{Z_NULL, "Z80 Full Instruction Set Exerciser for Spectrum (2017-0x)(Harston, Jonathan Graham)[!].tap", 8704, 91, 8612, 0x8000, 0x803D, TEST_FORMAT_HARSTON, 69},
{Z_NULL, "Z80 Full Instruction Set Exerciser for Spectrum (2018)(Harston, Jonathan Graham)[!].tap", 8716, 91, 8624, 0x8000, 0x803D, TEST_FORMAT_HARSTON, 69},
{"Z80 Instruction Set Exerciser for Spectrum 2 v0.1 (2012-11-27)(Rak, Patrik)[!].zip", "zexall2-0.1/zexall2.tap", 9316, 87, 9228, 0x8000, 0x8040, TEST_FORMAT_HARSTON, 76},
{Z_NULL, "Z80 Test Suite (2008)(Woodmass, Mark)[!].tap", 5573, 120, 5452, 0x8057, 0x80E6, TEST_FORMAT_WOODMASS, 50},
{Z_NULL, "Z80 Test Suite (2008)(Woodmass, Mark)[!].tap", 5573, 120, 5452, 0x8049, 0x80E6, TEST_FORMAT_WOODMASS, 61},
{"Zilog Z80 CPU Test Suite v1.0 (2012-12-08)(Rak, Patrik)[!].zip", "z80test-1.0/z80full.tap", 13758, 91, 13666, 0x8000, 0x7003, TEST_FORMAT_RAK, 156},
{"Zilog Z80 CPU Test Suite v1.0 (2012-12-08)(Rak, Patrik)[!].zip", "z80test-1.0/z80doc.tap", 13758, 91, 13666, 0x8000, 0x7003, TEST_FORMAT_RAK, 156},
{"Zilog Z80 CPU Test Suite v1.0 (2012-12-08)(Rak, Patrik)[!].zip", "z80test-1.0/z80flags.tap", 13758, 91, 13666, 0x8000, 0x7003, TEST_FORMAT_RAK, 156},
{"Zilog Z80 CPU Test Suite v1.0 (2012-12-08)(Rak, Patrik)[!].zip", "z80test-1.0/z80docflags.tap", 13758, 91, 13666, 0x8000, 0x7003, TEST_FORMAT_RAK, 156},
{"Zilog Z80 CPU Test Suite v1.0 (2012-12-08)(Rak, Patrik)[!].zip", "z80test-1.0/z80ccf.tap", 14219, 91, 14127, 0x8000, 0x7003, TEST_FORMAT_RAK, 156},
{"Zilog Z80 CPU Test Suite v1.0 (2012-12-08)(Rak, Patrik)[!].zip", "z80test-1.0/z80memptr.tap", 13758, 91, 13666, 0x8000, 0x7003, TEST_FORMAT_RAK, 156},
{"Zilog Z80 CPU Test Suite v1.2 (2022-01-26)(Rak, Patrik)[!].zip", "z80test-1.2/z80full.tap", 14390, 91, 14298, 0x8000, 0x7003, TEST_FORMAT_RAK, 164},
{"Zilog Z80 CPU Test Suite v1.2 (2022-01-26)(Rak, Patrik)[!].zip", "z80test-1.2/z80doc.tap", 14390, 91, 14298, 0x8000, 0x7003, TEST_FORMAT_RAK, 164},
{"Zilog Z80 CPU Test Suite v1.2 (2022-01-26)(Rak, Patrik)[!].zip", "z80test-1.2/z80flags.tap", 14390, 91, 14298, 0x8000, 0x7003, TEST_FORMAT_RAK, 164},
{"Zilog Z80 CPU Test Suite v1.2 (2022-01-26)(Rak, Patrik)[!].zip", "z80test-1.2/z80docflags.tap", 14390, 91, 14298, 0x8000, 0x7003, TEST_FORMAT_RAK, 164},
{"Zilog Z80 CPU Test Suite v1.2 (2022-01-26)(Rak, Patrik)[!].zip", "z80test-1.2/z80ccf.tap", 14875, 91, 14783, 0x8000, 0x7003, TEST_FORMAT_RAK, 164},
{"Zilog Z80 CPU Test Suite v1.2 (2022-01-26)(Rak, Patrik)[!].zip", "z80test-1.2/z80memptr.tap", 14390, 91, 14298, 0x8000, 0x7003, TEST_FORMAT_RAK, 164}
};
static struct {char const *key; zuint8 options;} const cpu_models[4] = {
{"zilog-nmos", Z80_MODEL_ZILOG_NMOS},
{"zilog-cmos", Z80_MODEL_ZILOG_CMOS},
{"nec-nmos", Z80_MODEL_NEC_NMOS },
{"st-cmos", Z80_MODEL_ST_CMOS }
};
/*------------------------------------------------------.
| Instance of the Z80 CPU emulator and 64 KB of memory. |
'======================================================*/
static Z80 cpu;
static zuint8 memory[65536];
/*----------------------------------------------------.
| Whether or not the current test has been completed. |
'====================================================*/
static zboolean test_completed;
/*-------------------------------------------------------.
| Whether or not the previous character printed was TAB. |
| It is only used in ZX Spectrum tests. |
'=======================================================*/
static zboolean zx_spectrum_tab;
/*-----------------------------------------------------------.
| X position of the cursor inside the paper (in characters). |
| It is only used in ZX Spectrum tests. |
'===========================================================*/
static zuint zx_spectrum_column;
/*----------------------------------------------------------.
| Number of text lines printed by the current test program. |
| It is used to know whether the test produces errors. |
'==========================================================*/
static zuint lines;
/*-------------------------------------------------------------.
| Address where to place a trap to intercept the PRINT routine |
| used by the test program. |
'=============================================================*/
static zuint16 print_hook_address;
/*---------------------------------------------------------------.
| [0] = Value read from even I/O ports. |
| [1] = Value read from odd I/O ports. |
| The default values are those of a Sinclair ZX Spectrum 16K/48K |
| with no devives attached. |
'===============================================================*/
static zuint8 in_values[2] = {191, 255};
/*-----------------.
| Verbosity level. |
'=================*/
static zuint8 verbosity = 4;
/*-------------------------------------------------------------.
| Wheter or not to print the output of the test programs. |
| TRUE if the verbosity level is 4. It is used for simplicity. |
'=============================================================*/
static zboolean show_test_output;
static char* path_buffer = Z_NULL;
static char** search_paths = Z_NULL;
static zuint search_path_count = 0;
/* MARK: - CPU Callbacks: Common */
static zuint8 cpu_read(void *context, zuint16 address)
{
Z_UNUSED(context)
return memory[address];
}
static zuint8 cpu_in(void *context, zuint16 port)
{
Z_UNUSED(context)
return in_values[port & 1];
}
static void cpu_out(void *context, zuint16 port, zuint8 value)
{Z_UNUSED(context) Z_UNUSED(port) Z_UNUSED(value)}
static void cpu_halt(void *context, zboolean state)
{
Z_UNUSED(context) Z_UNUSED(state)
cpu.cycles = Z80_CYCLE_LIMIT;
test_completed = TRUE;
}
/* MARK: - CPU Callbacks: CP/M */
static void cpm_cpu_write(void *context, zuint16 address, zuint8 value)
{
Z_UNUSED(context)
memory[address] = value;
}
static zuint8 cpm_cpu_hook(void *context, zuint16 address)
{
zuint8 character;
Z_UNUSED(context)
if (address != 5) return OPCODE_NOP;
if (Z80_C(cpu) == 2) switch ((character = Z80_E(cpu)))
{
case 0x0D: break;
case 0x0A: character = '\n';
case 0x3A: lines++;
default:
if (show_test_output) putchar(character);
}
else if (Z80_C(cpu) == 9)
{
zuint16 i = Z80_DE(cpu);
zuint c = 0;
while (memory[i] != '$')
{
if (c++ > 100)
{
putchar('\n');
fputs("FATAL ERROR: String to print is too long!\n", stderr);
exit(EXIT_FAILURE);
}
switch ((character = memory[i++]))
{
case 0x0D: break;
case 0x0A: character = '\n';
case 0x3A: lines++;
default:
if (show_test_output) putchar(character);
}
}
}
return OPCODE_RET;
}
/* MARK: - CPU Callbacks: ZX Spectrum */
static void zx_spectrum_cpu_write(void *context, zuint16 address, zuint8 value)
{
Z_UNUSED(context)
if (address > 0x3FFF) memory[address] = value;
}
static zuint8 zx_spectrum_cpu_hook(void *context, zuint16 address)
{
Z_UNUSED(context)
if (address != print_hook_address) return OPCODE_NOP;
if (zx_spectrum_tab)
{
if (show_test_output)
{
zuint c = (Z80_A(cpu) % 32) - (zx_spectrum_column % 32);
while (c--) putchar(' ');
}
zx_spectrum_tab = FALSE;
}
else switch (Z80_A(cpu))
{
case 0x0D: /* CR */
if (show_test_output) putchar('\n');
lines++;
zx_spectrum_column = 0;
break;
case 0x7F: /* © */
if (show_test_output) printf("©");
zx_spectrum_column++;
break;
case 0x17: /* TAB */
zx_spectrum_tab = TRUE;
break;
default:
if (Z80_A(cpu) >= 32 && Z80_A(cpu) < 127)
{
if (show_test_output) putchar(Z80_A(cpu));
lines += ++zx_spectrum_column > 32;
}
}
return OPCODE_RET;
}
/* Only needed for Woody's Z80 Test Suite. */
static zuint8 zx_spectrum_cpu_fetch_opcode(void *context, zuint16 address)
{
Z_UNUSED(context)
return (address == 0x0D6B /* 0D6B: THE 'CLS' COMMAND ROUTINE */ ||
address == 0x1601 /* 1601: THE 'CHAN_OPEN' SUBROUTINE */
)
? OPCODE_RET : memory[address];
}
static char const *compose_path(char const *base_path, char const *file_path)
{
zusize base_path_size;
if (base_path == Z_NULL) return file_path;
base_path_size = strlen(base_path);
memcpy(path_buffer, base_path, base_path_size);
path_buffer[base_path_size] = '/';
strcpy(path_buffer + base_path_size + 1, file_path);
return path_buffer;
}
static zboolean load_file(
char const* search_path,
char const* file_path,
zuint32 file_size,
zuint16 offset,
zuint16 size,
void* buffer
)
{
zboolean status = FALSE;
FILE *file = fopen(compose_path(search_path, file_path), "rb");
if (file != Z_NULL)
{
fseek(file, 0, SEEK_END);
if (ftell(file) == file_size)
{
fseek(file, offset, SEEK_SET);
status = fread(buffer, size, 1, file) == 1;
}
fclose(file);
}
return status;
}
static zboolean load_test(char const *search_path, Test const *test, void *buffer)
{
# ifdef TEST_Z80_WITH_ARCHIVE_EXTRACTION
zboolean status = load_file(
search_path, test->file_path, test->file_size,
test->code_offset, test->code_size, buffer);
if (!status && test->archive_name != Z_NULL)
{
search_path = compose_path(search_path, test->archive_name);
/* .tar.gz */
if (strrchr(test->archive_name, '.')[1] == 'g')
{
union {zuint8 data[Z_TAR_BLOCK_SIZE]; Z_TARHeader fields;} header;
gzFile gz = gzopen(search_path, "rb");
if (gz != Z_NULL)
{
while (!gzeof(gz))
{
char *end;
zulong file_size, block_tail_size;
if (gzread(gz, header.data, Z_TAR_BLOCK_SIZE) != Z_TAR_BLOCK_SIZE) break;
file_size = strtoul((char *)header.fields.size, &end, 8);
if (!strcmp(test->file_path, (char *)header.fields.name))
{
status =
file_size == test->file_size &&
gzseek(gz, test->code_offset, SEEK_CUR) != -1 &&
gzread(gz, buffer, test->code_size) == test->code_size;
break;
}
if ( (zuint8 *)end == header.fields.size || *end ||
-1 == gzseek(gz, (block_tail_size = (file_size % Z_TAR_BLOCK_SIZE))
? file_size + (Z_TAR_BLOCK_SIZE - block_tail_size)
: file_size, SEEK_CUR)
)
break;
}
gzclose(gz);
}
}
/* .zip */
else {
int error;
zip_t *zip = zip_open(search_path, ZIP_RDONLY | ZIP_CHECKCONS, &error);
if (zip != Z_NULL)
{
zip_file_t *file;
zip_stat_t stat;
if ( !zip_stat(zip, test->file_path, ZIP_FL_ENC_STRICT, &stat) &&
(stat.valid & ZIP_STAT_SIZE) && stat.size == test->file_size &&
(file = zip_fopen(zip, test->file_path, 0)) != Z_NULL
)
{
if ( zip_fread(file, buffer, test->code_offset) == test->code_offset &&
zip_fread(file, buffer, test->code_size) == test->code_size
)
status = TRUE;
zip_fclose(file);
}
zip_close(zip);
}
}
}
return status;
# else
return load_file(
search_path, test->file_path, test->file_size,
test->code_offset, test->code_size, buffer);
# endif
}
static zuint8 run_test(int test_index)
{
Test const *test = &tests[test_index];
zuint16 start_address = test->start_address;
zuint i = 0;
if (verbosity)
{
printf(verbosity == 1 ? "%02d" : "[%02d] ", test_index);
if (verbosity >= 2)
{
if (test->archive_name == Z_NULL) printf("%s", test->file_path);
else printf("%s/%s", test->archive_name, test->file_path);
if (verbosity >= 3) printf("\n* Loading program");
}
printf("...");
}
memset(memory, 0, 65536);
for (; i < search_path_count &&
!load_test(search_paths[i], test, memory + (start_address & 0xFF00));
i++
);
if ( i == search_path_count &&
!load_test(Z_NULL, test, memory + (start_address & 0xFF00))
)
{
error_loading_file:
if (verbosity) puts(show_test_output
? " error! (test skipped)\n"
: " error! (test skipped)");
return FALSE;
}
if (verbosity >= 3) puts(" OK");
z80_power(&cpu, TRUE);
if (test->format == TEST_FORMAT_CPM)
{
cpu.fetch_opcode = cpu_read;
cpu.write = cpm_cpu_write;
cpu.hook = cpm_cpu_hook;
memory[0] = OPCODE_HALT;
memory[5] = Z80_HOOK; /* PRINT */
}
else {
cpu.write = zx_spectrum_cpu_write;
cpu.hook = zx_spectrum_cpu_hook;
cpu.im = 1;
cpu.i = 0x3F;
if (test->format == TEST_FORMAT_WOODMASS)
{
if (verbosity >= 3) printf("* Loading firmware...");
for ( i = 0;
i < search_path_count &&
!load_file(search_paths[i], "ZX Spectrum.rom", 16384, 0, 16384, memory);
i++
);
if ( i == search_path_count &&
!load_file(Z_NULL, "ZX Spectrum.rom", 16384, 0, 16384, memory)
)
goto error_loading_file;
if (verbosity >= 3) puts(" OK");
Z80_SP(cpu) = 0x7FE8;
Z80_AF(cpu) = 0x3222;
cpu.fetch_opcode = zx_spectrum_cpu_fetch_opcode;
/* 0010: THE 'PRINT A CHARACTER' RESTART */
memory[0x0010] = OPCODE_JP_WORD; /* jp PRINT */
memory[0x0011] = 0xF2;
memory[0x0012] = 0x70;
/* 70F2: PRINT */
memory[print_hook_address = 0x70F2] = Z80_HOOK;
}
else {
cpu.fetch_opcode = cpu_read;
/* 0010: THE 'PRINT A CHARACTER' RESTART */
memory[print_hook_address = 0x0010] = Z80_HOOK;
/* 0D6B: THE 'CLS' COMMAND ROUTINE */
memory[0x0D6B] = OPCODE_RET;
/* 1601: THE 'CHAN_OPEN' SUBROUTINE */
memory[0x1601] = OPCODE_RET;
if (test->format == TEST_FORMAT_RAK)
{
/* 7000: START */
memory[0x7000] = OPCODE_CALL_WORD;
memory[0x7001] = (zuint8)start_address;
memory[0x7002] = (zuint8)(start_address >> 8);
/*memory[0x7003] = HALT;*/
start_address = 0x7000;
}
}
}
memory[test->exit_address] = OPCODE_HALT;
Z80_PC(cpu) = start_address;
lines =
zx_spectrum_column = 0;
zx_spectrum_tab =
test_completed = FALSE;
if (verbosity >= 3) printf("* Running program...%s", show_test_output ? "\n\n" : "");
do
# ifdef TEST_Z80_WITH_EXECUTE
z80_execute(&cpu, Z80_CYCLE_LIMIT);
# else
z80_run(&cpu, Z80_CYCLE_LIMIT);
# endif
while (!test_completed);
if (verbosity) puts(show_test_output
? (test->format == TEST_FORMAT_RAK ? "" : "\n")
: (lines == test->lines_expected ? " passed" : " failed"));
return lines == test->lines_expected;
}
static zboolean is_option(char const* string, char const* short_option, char const* long_option)
{return !strcmp(string, short_option) || !strcmp(string, long_option);}
static zboolean to_uint8(char const* string, zuint8 maximum_value, zuint8 *byte)
{
char *end;
zulong value = strtoul(string, &end, 0);
if (end == string || *end || value > maximum_value) return FALSE;
if (byte != Z_NULL) *byte = (zuint8)value;
return TRUE;
}
int main(int argc, char **argv)
{
zboolean all = FALSE;
zuint32 tests_run = 0;
zusize maximum_search_path_size = 0;
int j, i = 0;
/*--------------------------------------------.
| String containing what has been detected as |
| invalid when parsing the command line. |
'============================================*/
char const *invalid;
/*------------------------------.
| [0] = Number of tests failed. |
| [1] = Number of tests passed. |
'==============================*/
zuint results[2] = {0, 0};
/*------------------------------------------.
| The emulator will behave as a Zilog NMOS |
| if the user does not specify a CPU model. |
'==========================================*/
cpu.options = Z80_MODEL_ZILOG_NMOS;
while (++i < argc && *argv[i] == '-')
{
if (is_option(argv[i], "-h", "--help"))
{
puts( "Usage: test-Z80 [options] (--all | ...)\n"
"\n"
"Options:\n"
" -V, --verbosity (0..4) Set the verbosity level (default: 4).\n"
" -0, --in-even (0..255) Set the byte read from even I/O ports (default: 191).\n"
" -1, --in-odd (0..255) Set the byte read from odd I/O ports (default: 255).\n"
" -a, --all Run all tests.\n"
" -h, --help Show this help message.\n"
" -m, --model Specify the CPU model to emulate.\n"
" -p, --path Add a path where to look for the required files.\n"
" -v, --version Show version and copyright.\n"
"\n"
"CPU models:\n"
" zilog-nmos Zilog NMOS (default)\n"
" zilog-cmos Zilog CMOS\n"
" nec-nmos NEC NMOS\n"
" st-cmos SGS-Thomson CMOS\n"
"\n"
"Tests:\n"
" Versions of the Z80 Documented Instruction Set Exerciser:\n"
" 00 CP/M ~ Cringle, Frank D. (2004-04-23).\n"
" 01 ZX Spectrum ~ Harston, Jonathan Graham (2018).\n"
" Versions of the Z80 Full Instruction Set Exerciser:\n"
" 02 CP/M ~ Cringle, Frank D. (2004-04-23).\n"
" 03 ZX Spectrum ~ Bobrowski, Jan (2009).\n"
" 04 ZX Spectrum ~ Bobrowski, Jan (2011). Narrowed to BIT instructions.\n"
" 05 ZX Spectrum ~ Harston, Jonathan Graham (2017).\n"
" 06 ZX Spectrum ~ Harston, Jonathan Graham (2018).\n"
" 07 ZX Spectrum ~ Rak, Patrik (2012-11-27).\n"
" Z80 Test Suite ~ ZX Spectrum ~ Woodmass, Mark (2008):\n"
" 08 Tests flags.\n"
" 09 Tests MEMPTR.\n"
" Zilog Z80 CPU Test Suite v1.0 ~ ZX Spectrum ~ Rak, Patrik (2012-12-08):\n"
" 10 Tests all flags and registers.\n"
" 11 Tests all registers, but only officially documented flags.\n"
" 12 Tests all flags, ignores registers.\n"
" 13 Tests documented flags only, ignores registers.\n"
" 14 Tests all flags after executing CCF after each instruction.\n"
" 15 Tests all flags after executing BIT N,(HL) after each instruction.\n"
" Zilog Z80 CPU Test Suite v1.2 ~ ZX Spectrum ~ Rak, Patrik (2022-01-26):\n"
" 16 Tests all flags and registers.\n"
" 17 Tests all registers, but only officially documented flags.\n"
" 18 Tests all flags, ignores registers.\n"
" 19 Tests documented flags only, ignores registers.\n"
" 20 Tests all flags after executing CCF after each instruction.\n"
" 21 Tests all flags after executing BIT N,(HL) after each instruction.\n"
"\n"
"Email bug reports and questions to \n"
"Open issues at ");
goto exit_without_error;
}
else if (is_option(argv[i], "-v", "--version"))
{
puts( "test-Z80 v" Z80_LIBRARY_VERSION_STRING "\n"
"Copyright (C) 2021-2022 Manuel Sainz de Baranda y Goñi.\n"
"Released under the terms of the GNU General Public License v3.");
goto exit_without_error;
}
else if (is_option(argv[i], "-V", "--verbosity"))
{
if (++i == argc) goto incomplete_option;
if (!to_uint8(argv[i], 4, &verbosity))
{
invalid = "verbosity level";
goto invalid_argument;
}
}
else if (is_option(argv[i], "-0", "--in-even"))
{
if (++i == argc) goto incomplete_option;
if (!to_uint8(argv[i], 255, &in_values[0])) goto invalid_io_value;
}
else if (is_option(argv[i], "-1", "--in-odd"))
{
if (++i == argc) goto incomplete_option;
if (!to_uint8(argv[i], 255, &in_values[1])) goto invalid_io_value;
}
else if (is_option(argv[i], "-m", "--model"))
{
if (++i == argc) goto incomplete_option;
for (j = 0; j < 4; j++) if (!strcmp(argv[i], cpu_models[j].key)) break;
if (j == 4)
{
invalid = "CPU model";
goto invalid_argument;
}
cpu.options = cpu_models[j].options;
}
else if (is_option(argv[i], "-p", "--path"))
{
zusize s;
char **p;
if (++i == argc || !(s = strlen(argv[i]))) goto incomplete_option;
if (s > maximum_search_path_size) maximum_search_path_size = s;
if ((p = realloc(search_paths, (search_path_count + 1) * sizeof(char *))) == Z_NULL)
goto not_enough_memory_available;
search_paths = p;
search_paths[search_path_count++] = argv[i];
}
else if (is_option(argv[i], "-a", "--all"))
all = TRUE;
else {
invalid = "option";
goto invalid_argument;
}
}
/*-------------------------------------------------------.
| It is mandatory to specify at least one test number in |
| the command line, or the `-a` option in its absence. |
'=======================================================*/
if (i == argc && !all)
{
fputs("No test specified.\n", stderr);
goto bad_syntax;
}
/*--------------------------------------------------------------.
| All test numbers specified in the command line must be valid. |
'==============================================================*/
for (j = i; i < argc; i++)
{
char const *string = argv[i];
char *end;
if (strtoul(string, &end, 10) >= Z_ARRAY_SIZE(tests) || end == string || *end)
{
invalid = "test number";
goto invalid_argument;
}
}
if ( search_path_count &&
(path_buffer = malloc(maximum_search_path_size + 110)) == Z_NULL
)
{
not_enough_memory_available:
fputs("Error: Not enough memory available.", stderr);
goto exit_with_error;
}
/*-------------------------------------------------------------------.
| The output of the test programs is only printed when using the |
| verbosity level 4. In this case, the standard output is configured |
| as unbuffered so that the characters printed by these programs are |
| visible immediately, rather than after each ENTER. |
'===================================================================*/
if (verbosity)
{
setvbuf(stdout, Z_NULL, _IONBF, 0);
show_test_output = verbosity == 4;
}
/* Configure the Z80 CPU emulator. */
/*---------------------------------------------------------.
| No CPU context is needed; we are using global variables. |
'=========================================================*/
cpu.context = Z_NULL;
/*----------------------------------------------------------.
| It is not necessary to distinguish between memory read on |
| instruction data, memory read on non-instruction data and |
| internal NOP (the three are memory read M-cycles), as the |
| tests do not require precise timing or memory contention. |
'==========================================================*/
cpu.fetch =
cpu.read =
cpu.nop = cpu_read;
cpu.in = cpu_in;
cpu.out = cpu_out;
/*-------------------------------------------------------------------.
| Entering the HALT state means that the test program has completed. |
'===================================================================*/
cpu.halt = cpu_halt;
/*------------------------------------------------.
| The following callbacks of the Z80 CPU emulator |
| are not required by the test programs. |
'================================================*/
cpu.nmia =
cpu.inta =
cpu.int_fetch = Z_NULL;
cpu.ld_i_a =
cpu.ld_r_a =
cpu.reti =
cpu.retn = Z_NULL;
cpu.illegal = Z_NULL;
/*-------------------------------------------------.
| Run the tests whose numbers have been explicitly |
| specified in the command line. |
'=================================================*/
while (j < argc)
{
tests_run |= Z_UINT32(1) << (i = atoi(argv[j++]));
results[run_test(i)]++;
}
/*-----------------------------------------------------.
| If all tests must be run, do so, but avoid repeating |
| those already run. |
'=====================================================*/
if (all) for (i = 0; i < (int)Z_ARRAY_SIZE(tests); i++)
if (!(tests_run & (Z_UINT32(1) << i))) results[run_test(i)]++;
/*----------------------------------.
| Print the summary of the results. |
'==================================*/
printf( "%sResults: %u test%s passed, %u failed\n",
verbosity && !show_test_output ? "\n" : "",
results[1],
results[1] == 1 ? "" : "s",
results[0]);
exit_without_error:
free(search_paths);
free(path_buffer);
return results[0] ? -1 : 0;
incomplete_option:
fprintf(stderr, "Incomplete option: '%s'\n", argv[i - 1]);
goto bad_syntax;
invalid_io_value:
invalid = "I/O value";
invalid_argument:
fprintf(stderr, "Invalid %s: '%s'\n", invalid, argv[i]);
bad_syntax:
fputs("Type 'test-Z80 -h' for help.\n", stderr);
exit_with_error:
free(search_paths);
free(path_buffer);
return -1;
}
/* test-Z80.c EOF */