Skip to content

Instantly share code, notes, and snippets.

@binji
Created January 25, 2024 17:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save binji/3dba4717c6e6a1c4ff77ed2bf1d109db to your computer and use it in GitHub Desktop.
Save binji/3dba4717c6e6a1c4ff77ed2bf1d109db to your computer and use it in GitHub Desktop.
Parsing/executing tom harte tests for NES in C
#include "common.h"
#include "options.h"
#include "vec.h"
#include "third_party/json.h"
#define HOOK(name, ...)
#define DEBUG(...) (void)0
// See s_opcode_bits below
#define STEP_CPU_DECODE 781
#define STEP_CALLVEC (STEP_CPU_DECODE + 1)
#define STEP_RESET (STEP_CALLVEC + 7)
#define STEP_OAMDMA (STEP_RESET + 7)
#define STEP_DMC (STEP_OAMDMA + 514)
#define CPU_ONLY 1
static const char* s_json_filename;
typedef struct { u16 PC; u8 S, A, X, Y, P; } cpu_state;
typedef struct { u16 addr; u8 val; } ram_state;
typedef struct { u16 addr; u8 val; bool write; } cycle_state;
typedef union {
struct { u8 lo, hi; }; // TODO endian
u16 val;
} u16pair;
typedef struct {
u64 bits;
u16 step, next_step, dmc_step;
u16pair PC, T, bus, oam;
u8 fixhi, veclo;
u8 A, X, Y, S;
u8 ram[0x10000];
u8 opcode, open_bus, irq;
bool C, Z, I, D, V, N; // Flags.
bool req_nmi, req_reset, has_nmi, has_irq, has_reset, reset_active;
u64 set_vec_cy;
//
cycle_state log[8];
} C;
typedef struct {
struct {
C c;
u64 cy;
} s;
} E;
static const u8 s_opcode_bits[781+1+7+7+514+4];
static const u16 s_opcode_loc[256];
static u8 cpu_read(E *e, u16 addr) {
u8 val = e->s.c.ram[addr];
e->s.c.log[e->s.cy] = (cycle_state){.addr = addr, .val = val, 0};
return val;
}
static void cpu_write(E *e, u16 addr, u8 val) {
e->s.c.log[e->s.cy] = (cycle_state){.addr = addr, .val = val, 1};
e->s.c.ram[addr] = val;
}
void check_irq(E* e) {}
static inline u16 get_u16(u8 hi, u8 lo) { return (hi << 8) | lo; }
#include "cpu.c"
static void usage(int argc, char** argv) {
PRINT_ERROR(
"usage: %s [options] <file.json>\n"
" -h,--help help\n",
argv[0]);
}
static void parse_arguments(int argc, char** argv) {
static const Option options[] = {
{'h', "help", 0},
};
struct OptionParser* parser = option_parser_new(
options, sizeof(options) / sizeof(options[0]), argc, argv);
int errors = 0;
int done = 0;
while (!done) {
OptionResult result = option_parser_next(parser);
switch (result.kind) {
case OPTION_RESULT_KIND_UNKNOWN:
PRINT_ERROR("ERROR: Unknown option: %s.\n\n", result.arg);
goto error;
case OPTION_RESULT_KIND_EXPECTED_VALUE:
PRINT_ERROR("ERROR: Option --%s requires a value.\n\n",
result.option->long_name);
goto error;
case OPTION_RESULT_KIND_BAD_SHORT_OPTION:
PRINT_ERROR("ERROR: Short option -%c is too long: %s.\n\n",
result.option->short_name, result.arg);
goto error;
case OPTION_RESULT_KIND_OPTION:
switch (result.option->short_name) {
case 'h':
goto error;
default:
abort();
break;
}
break;
case OPTION_RESULT_KIND_ARG:
s_json_filename = result.value;
break;
case OPTION_RESULT_KIND_DONE:
done = 1;
break;
}
}
if (!s_json_filename) {
PRINT_ERROR("ERROR: expected input .json\n\n");
goto error;
}
option_parser_delete(parser);
return;
error:
usage(argc, argv);
option_parser_delete(parser);
exit(1);
}
bool json_string_matches(json_string_t* js, const char* s) {
size_t i;
for (i = 0; i < js->string_size && s[i]; ++i) {
if (js->string[i] != s[i]) {
return false;
}
}
return s[i] == 0;
}
Result parse_cpu_state(json_object_t* jo, cpu_state *result) {
for (json_object_element_t* joe = jo->start; joe; joe = joe->next) {
if (json_string_matches(joe->name, "pc")) {
json_number_t* n = json_value_as_number(joe->value);
CHECK(n);
result->PC = strtoul(n->number, NULL, 10);
} else if (json_string_matches(joe->name, "s")) {
json_number_t* n = json_value_as_number(joe->value);
CHECK(n);
result->S = strtoul(n->number, NULL, 10);
} else if (json_string_matches(joe->name, "a")) {
json_number_t* n = json_value_as_number(joe->value);
CHECK(n);
result->A = strtoul(n->number, NULL, 10);
} else if (json_string_matches(joe->name, "x")) {
json_number_t* n = json_value_as_number(joe->value);
CHECK(n);
result->X = strtoul(n->number, NULL, 10);
} else if (json_string_matches(joe->name, "y")) {
json_number_t* n = json_value_as_number(joe->value);
CHECK(n);
result->Y = strtoul(n->number, NULL, 10);
} else if (json_string_matches(joe->name, "p")) {
json_number_t* n = json_value_as_number(joe->value);
CHECK(n);
result->P = strtoul(n->number, NULL, 10);
}
}
return OK;
ON_ERROR_RETURN;
}
Result parse_ram_state(json_object_t* jo, ram_state* result,
int* ram_state_count) {
int index = 0;
for (json_object_element_t* joe = jo->start; joe; joe = joe->next) {
if (json_string_matches(joe->name, "ram")) {
json_array_t* ja = json_value_as_array(joe->value);
CHECK(ja);
for (json_array_element_t* jae = ja->start; jae;
jae = jae->next, ++index) {
CHECK_MSG(index < *ram_state_count, "too many ram states");
json_array_t* pair = json_value_as_array(jae->value);
CHECK_MSG(pair, "ram element is not array");
CHECK_MSG(pair->length == 2, "ram element length != 2");
json_number_t* naddr = json_value_as_number(pair->start->value);
CHECK_MSG(naddr, "pair first is not number");
json_number_t* nval = json_value_as_number(pair->start->next->value);
CHECK_MSG(nval, "pair second is not number");
result[index].addr = strtoul(naddr->number, NULL, 10);
result[index].val = strtoul(nval->number, NULL, 10);
}
}
}
*ram_state_count = index;
return OK;
ON_ERROR_RETURN;
}
Result parse_cycle_state(json_array_t* ja, cycle_state* result,
int* cycle_state_count) {
int index = 0;
for (json_array_element_t* jae = ja->start; jae; jae = jae->next, ++index) {
CHECK_MSG(index < *cycle_state_count, "too many cycle states");
json_array_t* triple = json_value_as_array(jae->value);
CHECK_MSG(triple, "ram element is not array");
CHECK_MSG(triple->length == 3, "ram element length != 3");
json_number_t* naddr = json_value_as_number(triple->start->value);
CHECK_MSG(naddr, "triple first is not number");
json_number_t* nval = json_value_as_number(triple->start->next->value);
CHECK_MSG(nval, "triple second is not number");
json_string_t* swrite =
json_value_as_string(triple->start->next->next->value);
CHECK_MSG(swrite, "triple third is not string");
result[index].addr = strtoul(naddr->number, NULL, 10);
result[index].val = strtoul(nval->number, NULL, 10);
result[index].write = json_string_matches(swrite, "write");
}
*cycle_state_count = index;
return OK;
ON_ERROR_RETURN;
}
int main(int argc, char** argv) {
parse_arguments(argc, argv);
E e;
C* c = &e.s.c;
FileData file_data;
CHECK(SUCCESS(file_read(s_json_filename, &file_data)));
json_value_t* jv = json_parse(file_data.data, file_data.size);
CHECK(jv);
json_array_t* ja = json_value_as_array(jv);
CHECK(ja);
size_t test_count = ja->length;
size_t failed = 0;
for (json_array_element_t* jae = ja->start; jae; jae = jae->next) {
char name[16];
cpu_state init_cpu;
ram_state init_ram[20];
int init_ram_count = ARRAY_SIZE(init_ram);
cpu_state final_cpu;
ram_state final_ram[20];
int final_ram_count = ARRAY_SIZE(final_ram);
cycle_state cycles[8];
int cycle_count = ARRAY_SIZE(cycles);
json_object_t* jo = json_value_as_object(jae->value);
CHECK(jo);
for (json_object_element_t* joe = jo->start; joe; joe = joe->next) {
if (json_string_matches(joe->name, "name")) {
json_string_t* js = json_value_as_string(joe->value);
CHECK(js);
strncpy(name, js->string, js->string_size);
name[MIN(js->string_size, sizeof(name) - 1)] = 0;
} else if (json_string_matches(joe->name, "initial")) {
json_object_t* jo2 = json_value_as_object(joe->value);
CHECK(jo2);
CHECK(SUCCESS(parse_cpu_state(jo2, &init_cpu)));
CHECK(SUCCESS(parse_ram_state(jo2, init_ram, &init_ram_count)));
} else if (json_string_matches(joe->name, "final")) {
json_object_t* jo2 = json_value_as_object(joe->value);
CHECK(jo2);
CHECK(SUCCESS(parse_cpu_state(jo2, &final_cpu)));
CHECK(SUCCESS(parse_ram_state(jo2, final_ram, &final_ram_count)));
} else if (json_string_matches(joe->name, "cycles")) {
json_array_t* ja2 = json_value_as_array(joe->value);
CHECK(ja2);
CHECK(SUCCESS(parse_cycle_state(ja2, cycles, &cycle_count)));
}
}
// Init CPU
ZERO_MEMORY(e);
c->PC.val = init_cpu.PC;
c->A = init_cpu.A;
c->X = init_cpu.X;
c->Y = init_cpu.Y;
c->S = init_cpu.S;
set_P(&e, init_cpu.P);
// Init RAM
for (int i = 0; i < init_ram_count; ++i) {
c->ram[init_ram[i].addr] = init_ram[i].val;
}
// Step
for (int i = 0; i < cycle_count; ++i) {
cpu_step(&e);
e.s.cy++;
}
// Check CPU state
bool ok = true;
ok &= c->PC.val == final_cpu.PC;
ok &= c->A == final_cpu.A;
ok &= c->X == final_cpu.X;
ok &= c->Y == final_cpu.Y;
ok &= c->S == final_cpu.S;
u8 P = get_P(&e, false);
ok &= P == final_cpu.P;
// Check RAM state
for (int i = 0; i < final_ram_count; ++i) {
ok &= c->ram[final_ram[i].addr] == final_ram[i].val;
}
// Check cycle state
for (int i = 0; i < cycle_count; ++i) {
ok &= c->log[i].addr == cycles[i].addr;
ok &= c->log[i].val == cycles[i].val;
ok &= c->log[i].write == cycles[i].write;
}
// Print any failures
if (!ok) {
printf("X %s:", name);
if (c->PC.val != final_cpu.PC) {
printf(" PC:$%04x != $%04x", c->PC.val, final_cpu.PC);
}
if (c->A != final_cpu.A) {
printf(" A:$%02x != $%02x", c->A, final_cpu.A);
}
if (c->X != final_cpu.X) {
printf(" X:$%02x != $%02x", c->X, final_cpu.X);
}
if (c->Y != final_cpu.Y) {
printf(" Y:$%02x != $%02x", c->Y, final_cpu.Y);
}
if (P != final_cpu.P) {
printf(" P:$%02x != $%02x", P, final_cpu.P);
}
if (c->S != final_cpu.S) {
printf(" S:$%02x != $%02x", c->S, final_cpu.S);
}
for (int i = 0; i < final_ram_count; ++i) {
u16 addr = final_ram[i].addr;
u8 actual = c->ram[addr];
u8 expected = final_ram[i].val;
if (actual != expected) {
printf(" R[$%04x]:$%02x != $%02x", addr, actual, expected);
}
}
for (int i = 0; i < cycle_count; ++i) {
if (c->log[i].addr != cycles[i].addr ||
c->log[i].val != cycles[i].val ||
c->log[i].write != cycles[i].write) {
printf(" C[%u]:{$%04x,$%02x,%u} != {$%04x,$%02x,%u}", i,
c->log[i].addr, c->log[i].val, c->log[i].write, cycles[i].addr,
cycles[i].val, cycles[i].write);
}
}
printf("\n");
failed++;
}
}
printf("Passed %zu/%zu tests.\n", test_count - failed, test_count);
return 0;
error:
return 1;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment