mii_emu/test/mii_cpu_test.c

523 lines
12 KiB
C
Raw Normal View History

/*
* mii_cpu_test.c
*
* Copyright (C) 2023 Michel Pollet <buserror+git@gmail.com>
*
* SPDX-License-Identifier: MIT
*
* This was heavily inspired and converted from
* https://github.com/ct6502/apple2ts/blob/main/src/emulator/instructions.test.ts
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "mii_65c02.h"
#include "mii_65c02_asm.h"
#include "mii_65c02_disasm.h"
#include "mii_65c02_ops.h"
static void
_run_one_dump_state(
mii_cpu_t *cpu,
mii_cpu_state_t s,
void *p)
{
printf("PC:%04X A:%02X X:%02X Y:%02X S:%02x #%d %s AD:%04X D:%02x %s ",
cpu->PC, cpu->A, cpu->X, cpu->Y, cpu->S, cpu->cycle,
s.sync ? "I" : " ", s.addr, s.data, s.w ? "W" : "R");
// display the S flags
static const char *s_flags = "CZIDBRVN";
for (int i = 0; i < 8; i++)
printf("%c", cpu->P.P[i] ? s_flags[i] : tolower(s_flags[i]));
if (s.sync) {
mii_op_t d = mii_cpu_op[cpu->ram[cpu->PC]];
printf(" ");
char dis[64];
mii_cpu_disasm_one(cpu->ram + cpu->PC, cpu->PC, dis, sizeof(dis),
MII_DUMP_DIS_PC | MII_DUMP_DIS_DUMP_HEX);
printf("%s", dis);
if (d.desc.branch) {
if (cpu->P.P[d.desc.s_bit] == d.desc.s_bit_value)
printf(" ; taken");
}
printf("\n");
} else
printf("\n");
}
static int
_run_one_test(
const char *prog,
int verbose)
{
uint8_t *ram = calloc(1, 0x10000);
mii_cpu_asm_program_t p = {};
{
p.verbose = verbose;
if (mii_cpu_asm(&p, prog) == 0) {
// uint8_t *o = p.output;
if (verbose || p.verbose)
mii_cpu_disasm(p.output, p.org, p.output_len);
memcpy(ram + p.org, p.output, p.output_len);
} else {
printf("error\n");
exit(1);
}
}
mii_cpu_t cpu = {0};
mii_cpu_state_t s = {0};
verbose += p.verbose;
cpu.ram = ram;
if (verbose) {
// cpu.debug = _run_one_dump_state;
}
cpu.P.P[5] = 1;
cpu.S = 0xFF;
cpu.PC = p.org;
cpu.A = 0x01;
int count = 1000;
int error = 1;
do {
s = mii_cpu_run(&cpu, s);
if (s.w) ram[s.addr] = s.data;
else s.data = ram[s.addr];
if (verbose)
_run_one_dump_state(&cpu, s, NULL);
if (s.sync && !s.w && s.data == 0x00) {
mii_cpu_asm_line_t *l = p.prog;
while (l) {
if (l->addr == s.addr)
break;
l = l->next;
}
if (l && !strcasecmp(l->label, "pass")) {
printf("TEST %-40.40s: PASS\n", p.prog->line + 2);
error = 0;
} else if (l) {
printf("TEST %-40.40s: FAIL\n", p.prog->line + 2);
printf("\t%s\n", l->line);
_run_one_dump_state(&cpu, s, NULL);
} else {
printf("TEST %-40.40s: FAIL (out of program!)\n",
p.prog->line + 2);
_run_one_dump_state(&cpu, s, NULL);
}
break;
}
} while (count--);
if (!count) {
printf("TEST %-40.40s: FAIL (out of _run_this cycles!)\n",
p.prog->line + 2);
_run_one_dump_state(&cpu, s, NULL);
}
free(ram);
return error;
}
int
_run_this_one(
const char *desc,
const char *prog,
uint8_t expexted_A,
uint8_t expected_flags,
int verbose )
{
uint8_t *ram = calloc(1, 0x10000);
mii_cpu_asm_program_t p = {.org = 0x2000};
{
if (mii_cpu_asm(&p, prog) == 0) {
// uint8_t *o = p.output;
if (verbose || p.verbose)
mii_cpu_disasm(p.output, p.org, p.output_len);
memcpy(ram + p.org, p.output, p.output_len);
} else {
printf("error\n");
exit(1);
}
}
mii_cpu_t cpu = {0};
mii_cpu_state_t s = {0};
verbose += p.verbose;
cpu.ram = ram;
cpu.P.P[B_X] = 1;
expected_flags |= (1 << B_X);
cpu.S = 0xFF;
cpu.PC = p.org;
cpu.A = 0x01;
int count = 1000;
do {
s = mii_cpu_run(&cpu, s);
if (s.w) ram[s.addr] = s.data;
else s.data = ram[s.addr];
if (verbose)
_run_one_dump_state(&cpu, s, NULL);
if (s.sync && !s.w && s.data == 0x00) {
if (cpu.A == expexted_A) {
int err = 0;
static const char *s_flags = "CZIDBRVN";
for (int i = 0; i < 8; i++)
if (cpu.P.P[i] != ((expected_flags >> i) & 1)) {
printf("** S bit %c mismatch %d want %d\n",
s_flags[i], cpu.P.P[i], (expected_flags >> i) & 1);
err++;
}
if (err)
goto fail;
printf("TEST %-40.40s: PASS\n", desc);
break;
} else {
printf("** A Mismatch %02x vs %02x\n", cpu.A, expexted_A);
fail:
printf("TEST %-40.40s: FAIL\n", desc);
return -1;
}
}
} while (count--);
free(ram);
return 0;
}
void
_run_this(
const char *desc,
const char *prog,
uint8_t expexted_A,
uint8_t expected_flags,
int verbose )
{
int e = _run_this_one(desc, prog, expexted_A, expected_flags, verbose);
if (e && !verbose) {
if (_run_this_one(desc, prog, expexted_A, expected_flags, 1))
exit(1);
}
}
static char * indirect(char *prefix, uint8_t A, uint8_t mem, char *suffix) {
char *s = calloc(1, 256);
sprintf(s,
" %s\n"
" LDA #$01\n"
" STA $12\n"
" LDA #$30\n"
" STA $13\n"
" LDA #$%02x\n"
" STA $3001\n"
" LDA #$%02x\n"
" %s\n", prefix, mem, A, suffix);
return s;
}
static char * doSED_ADC(uint8_t a, uint8_t b) {
char *s = calloc(1, 256);
sprintf(s,
" SED\n"
" LDA #$%02x\n"
" ADC #$%02x\n",
a, b);
return s;
}
#include <glob.h>
int main()
{
glob_t globbuf;
glob("test/asm/0*.asm", 0, NULL, &globbuf);
for (int i = 0; i < (int)globbuf.gl_pathc; i++) {
printf("Loading %s\n", globbuf.gl_pathv[i]);
FILE *f = fopen(globbuf.gl_pathv[i], "r");
if (f) {
char *prog = calloc(1, 65536);
fread(prog, 1, 65536, f);
fclose(f);
_run_one_test(prog, 0);
free(prog);
}
}
globfree(&globbuf);
enum {
C = (1 << B_C),
Z = (1 << B_Z),
I = (1 << B_I),
D = (1 << B_D),
B = (1 << B_B),
X = (1 << B_X),
V = (1 << B_V),
N = (1 << B_N),
};
_run_this("RMB $12",
" lda #$FF\n"
" sta $12\n"
" rmb1 $12\n"
" lda $12\n",
0xfd, N, 0);
_run_this("RMB1 $ea",
" ldx $EA\n"
" lda #$FF\n"
" sta $EA\n"
" rmb1 $EA\n"
" cmp $EA\n"
" stx $EA\n",
0xff, C, 0);
_run_this("ASL Carry",
" clc\n lda #$80\n asl\n",
0x00, Z|C, 0);
_run_this("ROL Base",
" sec\n lda #$80\n rol\n",
0x01, C, 0);
_run_this("SBC Over+",
" sec\n lda #$d0\n sbc #$70\n",
0x60, V|C, 0);
_run_this("SBC Neg3",
" sec\n lda #$d0\n sbc #$30\n",
0xA0, N|C, 0);
_run_this("LDA ($aa,X)",
" LDX #$E9\n"
" LDY #$81\n"
" STY $3104\n"
" LDY #$04\n"
" STY $003A\n"
" LDY #$31\n"
" STY $003B\n"
" LDA ($51,X)\n",
0x81, N, 0);
_run_this("LDA ($aa),Y",
" LDY #$E9\n"
" LDX #$BB\n"
" STX $403A\n"
" LDX #$51\n"
" STX $00A4\n"
" LDX #$3F\n"
" STX $00A5\n"
" LDA ($A4),Y\n",
0xBB, N, 0);
_run_this("JMP ($3000,X)",
" JMP skip\n"
" LDA #$99 ; $2003\n"
" JMP done\n"
"skip LDA #$03\n"
" STA $3002\n"
" LDA #$20\n"
" STA $3003\n"
" LDX #$02\n"
" JMP ($3000,X) ; JMP -> $2003\n"
" LDA #$01 ; should not reach here\n"
"done NOP ;\n",
0x99, N, 0);
_run_this("DEC",
" LDA #99\n" " DEC\n",
0x98, N, 0);
_run_this("PLX",
" LDX #$97\n" " PHX\n" " PLX\n" " TXA\n",
0x97, N, 0);
_run_this("ASL", " LDA #$80\n ASL\n", 0x00, Z | C, 0);
_run_this("ROR", " LDA #$81\n ROR\n", 0x40, C, 0);
_run_this("ROL Z", " LDA #$80\n ROL\n", 0x00, Z | C, 0);
_run_this("ROL", " LDA #$81\n ROL\n", 0x02, C, 0);
_run_this("LDA ($12)",
indirect("", 0x99, 0xFC, "LDA ($12)"),
0xFC, N, 0);
_run_this("ADC ($12)",
indirect("", 0x7E, 0xAF, "ADC ($12)"),
0x2D, C, 0);
_run_this("ADC ($12) SED",
indirect("SED", 0x75, 0x25, "ADC ($12)"),
0x00, V | C | D, 0);
_run_this("AND ($12)",
indirect("", 0x3F, 0xFC, "AND ($12)"),
0x3C, 0, 0);
_run_this("CMP ($12)",
indirect("", 0xFC, 0x3F, "CMP ($12)"),
0xFC, C | N, 0);
_run_this("EOR ($12)",
indirect("", 0x3F, 0xFC, "EOR ($12)"),
0xC3, N, 0);
_run_this("ORA ($12)",
indirect("", 0x0E, 0xFC, "ORA ($12)"),
0xFE, N, 0);
_run_this("SBC ($12)",
indirect("SEC", 0xFF, 0xC0, "SBC ($12)"),
0x3F, C, 0);
_run_this("SBC ($12) SED",
indirect("SEC\n SED", 0x75, 0x25, "SBC ($12)"),
0x50, C | D, 0);
_run_this("STA ($12)",
indirect("", 0xF1, 0xC0, "STA ($12)\n LDA $3001"),
0xF1, N, 0);
_run_this("BIT #$F0",
" LDA #$0F\n BIT #$F0\n",
0x0F, Z , 0);
_run_this("BIT #$80",
" LDA #$0F\n BIT #$80", 0x0F, Z, 0);
_run_this("BIT #$70",
" LDA #$0F\n BIT #$70",
0x0F, Z, 0);
_run_this("BIT #$FF",
" LDA #$0F\n BIT #$FF",
0x0F, 0, 0);
_run_this("BIT $12,X",
" LDY #$F0\n"
" STY $14\n"
" LDA #$0F\n"
" LDX #$02\n"
" BIT $12,X\n",
0x0F, Z | V | N, 0);
_run_this("BIT $1234,X",
" LDY #$F0\n"
" STY $1236\n"
" LDA #$0F\n"
" LDX #$02\n"
" BIT $1234,X\n",
0x0F, Z | V | N, 0);
_run_this("DEC $xx",
" LDA #$FF\n"
" STA $12\n"
" DEC $12\n"
" LDA $12\n",
0xFE, N, 0);
_run_this("INC $xxxx",
" LDA #$FF\n"
" STA $3000\n"
" INC $3000\n"
" LDA $3000\n",
0x00, Z, 0);
_run_this("LDA ($9d),Y",
" LDX #$90\n"
" STX $90\n"
" LDY #$D\n"
" STZ $91\n"
" LDX #$de\n"
" STX $9D\n"
" LDA ($90),y\n",
0xde, N, 0);
_run_this("LDA ($aa,X)",
" LDX #$E9\n"
" LDY #$81\n"
" STY $3104\n"
" LDY #$04\n"
" STY $003A\n"
" LDY #$31\n"
" STY $003B\n"
" LDA ($51,X)\n",
0x81, N, 0);
_run_this("LDA ($aa),Y",
" LDY #$E9\n"
" LDX #$BB\n"
" STX $403A\n"
" LDX #$51\n"
" STX $00A4\n"
" LDX #$3F\n"
" STX $00A5\n"
" LDA ($A4),Y\n",
0xBB, N, 0);
_run_this("LDA ($aa)",
" LDX #$AA\n"
" STX $3F51\n"
" LDX #$51\n"
" STX $00A4\n"
" LDX #$3F\n"
" STX $00A5\n"
" LDA ($A4)\n",
0xAA, N, 0);
_run_this("SED ADC 0", doSED_ADC(0, 0), 0x0, Z | D, 0);
_run_this("SED ADC 1", doSED_ADC(1, 0), 0x01, D, 0);
_run_this("SED ADC 9", doSED_ADC(9, 0), 0x09, D, 0);
_run_this("SED ADC 10", doSED_ADC(0x10, 0), 0x10, D, 0);
_run_this("SED ADC 1D", doSED_ADC(0x1D, 0), 0x23, D, 0);
_run_this("SED ADC 99", doSED_ADC(0x99, 0), 0x99, N | D, 0);
_run_this("SED ADC 99", doSED_ADC(0x99, 1), 0x00, C | D, 0);
_run_this("SED ADC BD", doSED_ADC(0xBD, 0), 0x23, C | D, 0);
#if 0
// _run_this("SED ADC FF", doSED_ADC(0xFF, 0), 0x65, C | D | N, 0);
_run_this("SED ADC 0,1", doSED_ADC(0, 1), 0x01, D, 0);
_run_this("SED ADC 0,9", doSED_ADC(0, 9), 0x09, D, 0);
_run_this("SED ADC 0,10", doSED_ADC(0, 0x10), 0x10, D, 0);
_run_this("SED ADC 0,1D", doSED_ADC(0, 0x1D), 0x23, D, 0);
_run_this("SED ADC 0,99", doSED_ADC(0, 0x99), 0x99, N | D, 0);
_run_this("SED ADC 0,BD", doSED_ADC(0, 0xBD), 0x23, C | D, 0);
// _run_this("SED ADC 0,FF", doSED_ADC(0, 0xFF), 0x65, C | D, 0);
_run_this("SED ADC 99,1", doSED_ADC(0x99, 1), 0x0, Z | C | D, 0);
_run_this("SED ADC 35,35", doSED_ADC(0x35, 0x35), 0x70, D, 0);
_run_this("SED ADC 45,45", doSED_ADC(0x45, 0x45), 0x90, N | V | D, 0);
_run_this("SED ADC 50,50", doSED_ADC(0x50, 0x50), 0x0, V | Z | C | D, 0);
_run_this("SED ADC 99,99", doSED_ADC(0x99, 0x99), 0x98, N | V | C | D, 0);
// _run_this("SED ADC B1,C1", doSED_ADC(0xB1, 0xC1), 0xD2, N | V | C | D, 0);
#endif
// create an emulator, load the binary file 6502_functional_test.bin at $0000
// and run it until we hit a BRK
const char *bigtest[] = {
// "test/asm/6502_functional_test.bin",
// "test/asm/65C02_extended_opcodes_test.bin",
NULL,
};
for (int ti = 0; bigtest[ti]; ti++) {
FILE *f = fopen(bigtest[ti], "r");
if (!f) {
fprintf(stderr, "Cannot open %s\n", bigtest[ti]);
continue;
}
printf("TEST %s\n", bigtest[ti]);
uint8_t *ram = calloc(1, 0x10000);
fread(ram + 0x000, 1, 0x10000 - 0x400, f);
fclose(f);
mii_cpu_t cpu = {0};
mii_cpu_state_t s = {0};
cpu.ram = ram;
// cpu.debug = _run_one_dump_state;
cpu.P.P[B_X] = 1;
cpu.P.P[B_I] = 1;
cpu.S = 0xFF;
cpu.PC = 0x400;
cpu.A = 0x00;
int count = 100000000;
int same_pc_count = 0;
uint16_t prev_pc = 0;
do {
s = mii_cpu_run(&cpu, s);
if (s.w) ram[s.addr] = s.data;
else s.data = ram[s.addr];
if (s.sync && !s.w) {
if (cpu.PC == prev_pc) {
same_pc_count++;
if (same_pc_count > 3) {
printf("TEST %s: FAIL (stuck at %04X)\n",
bigtest[ti], cpu.PC);
_run_one_dump_state(&cpu, s, NULL);
printf(" Failed instruction is %c%c%c\n",
cpu.A, cpu.X, cpu.Y);
break;
}
} else {
same_pc_count = 0;
prev_pc = cpu.PC;
}
}
// if (s.sync)
_run_one_dump_state(&cpu, s, NULL);
} while (count--);
printf("TEST run with %d spare\n", count);
_run_one_dump_state(&cpu, s, NULL);
if (!count) {
printf("TEST %s: FAIL (out of _run_this cycles!)\n",
bigtest[ti]);
_run_one_dump_state(&cpu, s, NULL);
}
free(ram);
}
}