920 lines
28 KiB
C
920 lines
28 KiB
C
/* 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 <http://www.gnu.org/licenses/>. |
|
|
| |
|
|
'=============================================================================*/
|
|
|
|
#ifdef TEST_Z80_WITH_ARCHIVE_EXTRACTION
|
|
# include <zip.h>
|
|
# include <zlib.h>
|
|
# include <Z/formats/archive/TAR.h>
|
|
#endif
|
|
|
|
#include <Z/constants/pointer.h>
|
|
#include <Z/macros/aggregate.h>
|
|
#include <Z80.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
|
|
/* 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 | <test>...)\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 <model> Specify the CPU model to emulate.\n"
|
|
" -p, --path <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 <manuel@zxe.io>\n"
|
|
"Open issues at <https://github.com/redcode/Z80>");
|
|
|
|
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 */
|