diff options
| -rw-r--r-- | cpu.c | 62 | ||||
| -rw-r--r-- | rom.c | 56 | ||||
| -rw-r--r-- | rom.h | 17 |
3 files changed, 122 insertions, 13 deletions
| @@ -1,8 +1,11 @@ | |||
| 1 | #include <libgen.h> | ||
| 1 | #include <stdint.h> | 2 | #include <stdint.h> |
| 2 | #include <stdio.h> | 3 | #include <stdio.h> |
| 3 | #include <stdlib.h> | 4 | #include <stdlib.h> |
| 4 | #include <string.h> | 5 | #include <string.h> |
| 5 | 6 | ||
| 7 | #include "rom.h" | ||
| 8 | |||
| 6 | #define MAX(a, b) ((a > b) ? a : b) | 9 | #define MAX(a, b) ((a > b) ? a : b) |
| 7 | 10 | ||
| 8 | #define STATUS_UPDATE_ZERO(r) \ | 11 | #define STATUS_UPDATE_ZERO(r) \ |
| @@ -37,7 +40,9 @@ struct registers { | |||
| 37 | struct cpu_flags status; | 40 | struct cpu_flags status; |
| 38 | uint16_t pc; | 41 | uint16_t pc; |
| 39 | }; | 42 | }; |
| 40 | struct registers regs; | 43 | struct registers regs = {0}; |
| 44 | |||
| 45 | struct Rom rom = {0}; | ||
| 41 | 46 | ||
| 42 | enum addressing_mode { | 47 | enum addressing_mode { |
| 43 | AM_IMM, | 48 | AM_IMM, |
| @@ -56,9 +61,6 @@ enum addressing_mode { | |||
| 56 | /* 64K address space, 16bit words */ | 61 | /* 64K address space, 16bit words */ |
| 57 | uint8_t memory[0x16000]; | 62 | uint8_t memory[0x16000]; |
| 58 | 63 | ||
| 59 | /* example program taken from https://bugzmanov.github.io/nes_ebook/chapter_3_1.html */ | ||
| 60 | uint8_t program[] = { 0xa9, 0xc0, 0xaa, 0xe8, 0x00 }; | ||
| 61 | |||
| 62 | uint32_t cycles = 0; | 64 | uint32_t cycles = 0; |
| 63 | 65 | ||
| 64 | static uint8_t | 66 | static uint8_t |
| @@ -66,7 +68,14 @@ peek(uint16_t addr) | |||
| 66 | { | 68 | { |
| 67 | MEMORY_MIRROR(addr); | 69 | MEMORY_MIRROR(addr); |
| 68 | 70 | ||
| 69 | return memory[addr]; | 71 | if (addr > 0x7FFF) { |
| 72 | if (rom.prg_rom_size == 0x4000) | ||
| 73 | return rom.prg_rom[(addr - 0x8000) % 0x4000]; | ||
| 74 | else if (rom.prg_rom_size == 0x8000) | ||
| 75 | return rom.prg_rom[addr - 0x8000]; | ||
| 76 | } else { | ||
| 77 | return memory[addr]; | ||
| 78 | } | ||
| 70 | } | 79 | } |
| 71 | 80 | ||
| 72 | static uint16_t | 81 | static uint16_t |
| @@ -738,7 +747,7 @@ interpret(void) | |||
| 738 | for (;;) { | 747 | for (;;) { |
| 739 | opcode = peek(regs.pc++); | 748 | opcode = peek(regs.pc++); |
| 740 | 749 | ||
| 741 | printf("opcode: $%02X\n", opcode); | 750 | printf("opcode: $%02X @ $%04X\n", opcode, regs.pc - 1); |
| 742 | 751 | ||
| 743 | switch (opcode) { | 752 | switch (opcode) { |
| 744 | case 0x69: | 753 | case 0x69: |
| @@ -1403,7 +1412,7 @@ interpret(void) | |||
| 1403 | regs.status.decimal_mode, regs.status.brk, | 1412 | regs.status.decimal_mode, regs.status.brk, |
| 1404 | regs.status.unused, regs.status.overflow, | 1413 | regs.status.unused, regs.status.overflow, |
| 1405 | regs.status.negative); | 1414 | regs.status.negative); |
| 1406 | printf("A: $%02X, X: $%02X, Y: $%02X, SP: $%02X, PC: $%02X\n", | 1415 | printf("A: $%02X, X: $%02X, Y: $%02X, SP: $%02X, PC: $%04X\n", |
| 1407 | regs.a, regs.x, regs.y, regs.sp, regs.pc); | 1416 | regs.a, regs.x, regs.y, regs.sp, regs.pc); |
| 1408 | } | 1417 | } |
| 1409 | } | 1418 | } |
| @@ -1416,26 +1425,51 @@ cpu_init(void) | |||
| 1416 | regs.pc = 0xFFFC; | 1425 | regs.pc = 0xFFFC; |
| 1417 | regs.sp = 0xFD; | 1426 | regs.sp = 0xFD; |
| 1418 | 1427 | ||
| 1419 | memset(®s.status, 0, sizeof(regs.status)); | 1428 | //memset(®s.status, 0, sizeof(regs.status)); |
| 1420 | regs.status.unused = 1; | 1429 | regs.status.unused = 1; |
| 1421 | 1430 | ||
| 1422 | cycles += 7; | 1431 | cycles += 7; |
| 1423 | } | 1432 | } |
| 1424 | 1433 | ||
| 1425 | int | 1434 | int |
| 1426 | main(void) | 1435 | main(int argc, char *argv[]) |
| 1427 | { | 1436 | { |
| 1437 | FILE *fp; | ||
| 1438 | uint8_t *buf; | ||
| 1439 | size_t buflen; | ||
| 1440 | |||
| 1441 | if (argc != 2) { | ||
| 1442 | fprintf(stderr, "Usage: %s rom.nes\n", basename(argv[0])); | ||
| 1443 | return 1; | ||
| 1444 | } | ||
| 1445 | |||
| 1446 | fp = fopen(argv[1], "r"); | ||
| 1447 | fseek(fp, 0, SEEK_END); | ||
| 1448 | buflen = ftell(fp); | ||
| 1449 | buf = calloc(1, buflen); | ||
| 1450 | fseek(fp, 0, SEEK_SET); | ||
| 1451 | if (fread(buf, 1, buflen, fp) != buflen && ferror(fp)) { | ||
| 1452 | fprintf(stderr, "file %s was not read properly\n", argv[1]); | ||
| 1453 | clearerr(fp); | ||
| 1454 | fclose(fp); | ||
| 1455 | return 1; | ||
| 1456 | } | ||
| 1457 | fclose(fp); | ||
| 1458 | |||
| 1459 | parse_rom(buf, buflen, &rom); | ||
| 1460 | free(buf); | ||
| 1461 | |||
| 1428 | cpu_init(); | 1462 | cpu_init(); |
| 1429 | 1463 | ||
| 1430 | if (sizeof(program) > (0x10000 - 0x8000)) { | 1464 | /* TODO: move to separate file? */ |
| 1431 | fprintf(stderr, "program is too big for memory\n"); | 1465 | if (rom.mapper != 0) { |
| 1466 | fprintf(stderr, "Only iNES ROMs using Mapper 0 are supported for now.\n"); | ||
| 1432 | return 1; | 1467 | return 1; |
| 1433 | } | 1468 | } |
| 1434 | 1469 | ||
| 1435 | memcpy(memory + 0x8000, program, sizeof(program)); | 1470 | memwrite16(0xFFFC, 0x8000); |
| 1436 | regs.pc = 0x8000; | 1471 | regs.pc = 0x8000; |
| 1437 | 1472 | ||
| 1438 | printf("Initial State:\n"); | ||
| 1439 | printf("status: %i%i%i%i%i%i%i%i\n", regs.status.carry, | 1473 | printf("status: %i%i%i%i%i%i%i%i\n", regs.status.carry, |
| 1440 | regs.status.zero, regs.status.interrupt_disable, | 1474 | regs.status.zero, regs.status.interrupt_disable, |
| 1441 | regs.status.decimal_mode, regs.status.brk, | 1475 | regs.status.decimal_mode, regs.status.brk, |
| @@ -1448,5 +1482,7 @@ main(void) | |||
| 1448 | 1482 | ||
| 1449 | interpret(); | 1483 | interpret(); |
| 1450 | 1484 | ||
| 1485 | free_rom(&rom); | ||
| 1486 | |||
| 1451 | return 0; | 1487 | return 0; |
| 1452 | } | 1488 | } |
| @@ -0,0 +1,56 @@ | |||
| 1 | #include <stdint.h> | ||
| 2 | #include <stdio.h> | ||
| 3 | #include <stdlib.h> | ||
| 4 | #include <string.h> | ||
| 5 | |||
| 6 | #include "rom.h" | ||
| 7 | |||
| 8 | void | ||
| 9 | parse_rom(const uint8_t *data, size_t data_len, struct Rom *rom) | ||
| 10 | { | ||
| 11 | size_t prg_rom_offset = 16; | ||
| 12 | size_t chr_rom_offset = 0; | ||
| 13 | |||
| 14 | if (data_len < 16) { | ||
| 15 | fprintf(stderr, "Invalid ROM: size less than 16 bytes\n"); | ||
| 16 | exit(1); | ||
| 17 | } | ||
| 18 | |||
| 19 | if (data[0] != 'N' || data[1] != 'E' || data[2] != 'S' || data[3] != 0x1A) { | ||
| 20 | fprintf(stderr, "Invalid ROM: not in iNES format\n"); | ||
| 21 | exit(1); | ||
| 22 | } | ||
| 23 | |||
| 24 | /* trainer data present */ | ||
| 25 | if (data[6] & (1 << 2)) | ||
| 26 | prg_rom_offset += 512; | ||
| 27 | |||
| 28 | rom->prg_rom_size = data[4] * 0x4000; | ||
| 29 | rom->prg_rom = calloc(rom->prg_rom_size, 1); | ||
| 30 | memcpy(rom->prg_rom, data + prg_rom_offset, rom->prg_rom_size); | ||
| 31 | |||
| 32 | rom->chr_rom_size = data[5] * 0x2000; | ||
| 33 | if (rom->chr_rom_size > 0) { | ||
| 34 | rom->chr_rom = calloc(rom->chr_rom_size, 1); | ||
| 35 | chr_rom_offset = prg_rom_offset + rom->prg_rom_size; | ||
| 36 | memcpy(rom->chr_rom, data + chr_rom_offset, rom->chr_rom_size); | ||
| 37 | } | ||
| 38 | |||
| 39 | if ((data[6] & (1 << 3)) != 0) | ||
| 40 | rom->mirror = M_FOUR; | ||
| 41 | else if ((data[6] & 1) == 1) | ||
| 42 | rom->mirror = M_VERTICAL; | ||
| 43 | else if ((data[6] & 1) == 0) | ||
| 44 | rom->mirror = M_HORIZONTAL; | ||
| 45 | |||
| 46 | rom->mapper = (data[6] & (0xF << 4)) >> 4; | ||
| 47 | rom->mapper |= data[7] & (0xF << 4); | ||
| 48 | } | ||
| 49 | |||
| 50 | void | ||
| 51 | free_rom(struct Rom *rom) | ||
| 52 | { | ||
| 53 | free(rom->prg_rom); | ||
| 54 | if (rom->chr_rom_size > 0) | ||
| 55 | free(rom->chr_rom); | ||
| 56 | } | ||
| @@ -0,0 +1,17 @@ | |||
| 1 | enum screen_mirroring { | ||
| 2 | M_HORIZONTAL, | ||
| 3 | M_VERTICAL, | ||
| 4 | M_FOUR, | ||
| 5 | }; | ||
| 6 | |||
| 7 | struct Rom { | ||
| 8 | char *prg_rom; | ||
| 9 | char *chr_rom; | ||
| 10 | size_t prg_rom_size; | ||
| 11 | size_t chr_rom_size; | ||
| 12 | uint8_t mapper; | ||
| 13 | enum screen_mirroring mirror; | ||
| 14 | }; | ||
| 15 | |||
| 16 | void parse_rom(const uint8_t *data, size_t data_len, struct Rom *rom); | ||
| 17 | void free_rom(struct Rom *rom); | ||
