diff --git a/a2p.c b/a2p.c index e798adc..c3e83b2 100644 --- a/a2p.c +++ b/a2p.c @@ -131,6 +131,184 @@ void a2p_screen_txt_write(struct cpu_t *cpu, struct mem_t *mem, uint16_t addr, u //printf("[A2P] $%.4X = $%.2X\n", addr, b); } +// Apple Language Card +struct ewm_alc_t { + struct mem_t *ram1; // $D000 - $DFFF RAM Bank #1 + struct mem_t *ram2; // $D000 - $DFFF RAM Bank #2 + struct mem_t *ram3; // $E000 - $FFFF RAM Bank #3 + struct mem_t *rom; // $F800 - $FFFF Autostart ROM + struct mem_t *iom; // $C080 - $C08F + int wrtcount; +}; + +uint8_t alc_iom_read(struct cpu_t *cpu, struct mem_t *mem, uint16_t addr) { + struct ewm_alc_t *alc = (struct ewm_alc_t*) mem->obj; + + // Always enable the right banks + if (addr & 0b00001000) { + alc->ram1->enabled = true; + alc->ram2->enabled = false; + alc->ram3->enabled = true; + } else { + alc->ram1->enabled = false; + alc->ram2->enabled = true; + alc->ram3->enabled = true; + } + + switch (addr) { + // WRTCOUNT = 0, WRITE DISABLE, READ ENABLE + case 0xc080: + case 0xc084: + alc->wrtcount = 0; + alc->ram1->flags = MEM_FLAGS_READ; + alc->ram2->flags = MEM_FLAGS_READ; + alc->ram3->flags = MEM_FLAGS_READ; + break; + + // WRTCOUNT++, READ DISABLE, WRITE ENABLE IF WRTCOUNT >= 2 + case 0xc081: + case 0xc085: + alc->wrtcount = alc->wrtcount + 1; + alc->ram1->flags &= ~MEM_FLAGS_READ; + alc->ram2->flags &= ~MEM_FLAGS_READ; + alc->ram3->flags &= ~MEM_FLAGS_READ; + if (alc->wrtcount >= 2) { + alc->ram1->flags |= MEM_FLAGS_WRITE; + alc->ram2->flags |= MEM_FLAGS_WRITE; + alc->ram3->flags |= MEM_FLAGS_WRITE; + } + break; + + // WRTCOUNT = 0, WRITE DISABLE, READ DISABLE + case 0xc082: + case 0xc086: + alc->wrtcount = 0; + alc->ram1->flags &= ~MEM_FLAGS_WRITE; + alc->ram2->flags &= ~MEM_FLAGS_WRITE; + alc->ram3->flags &= ~MEM_FLAGS_WRITE; + alc->ram1->flags &= MEM_FLAGS_WRITE; + alc->ram2->flags &= MEM_FLAGS_WRITE; + alc->ram3->flags &= MEM_FLAGS_WRITE; + break; + + // WRTCOUNT++, READ ENABLE, WRITE ENABLE IF WRTCOUNT >= 2 + case 0xc083: + case 0xc08b: + alc->wrtcount = alc->wrtcount + 1; + alc->ram1->flags |= MEM_FLAGS_READ; + alc->ram2->flags |= MEM_FLAGS_READ; + alc->ram3->flags |= MEM_FLAGS_READ; + if (alc->wrtcount >= 2) { + alc->ram1->flags |= MEM_FLAGS_WRITE; + alc->ram2->flags |= MEM_FLAGS_WRITE; + alc->ram3->flags |= MEM_FLAGS_WRITE; + } + break; + + default: + fprintf(stderr, "[ALC] Unexpected read at $%.4X\n", addr); + break; + } + + return 0; +} + +static void alc_iom_write(struct cpu_t *cpu, struct mem_t *mem, uint16_t addr, uint8_t b) { + struct ewm_alc_t *alc = (struct ewm_alc_t*) mem->obj; + + // Always enable the right banks + if (addr & 0b00001000) { + alc->ram1->enabled = true; + alc->ram2->enabled = false; + alc->ram3->enabled = true; + } else { + alc->ram1->enabled = false; + alc->ram2->enabled = true; + alc->ram3->enabled = true; + } + + switch (addr) { + // WRTCOUNT = 0, WRITE DISABLE, READ ENABLE + case 0xc080: + case 0xc084: + alc->wrtcount = 0; + alc->ram1->flags = MEM_FLAGS_READ; + alc->ram2->flags = MEM_FLAGS_READ; + alc->ram3->flags = MEM_FLAGS_READ; + break; + + // WRTCOUNT = 0, READ DISABLE + case 0xc081: + case 0xc085: + alc->wrtcount = 0; + alc->ram1->flags &= ~MEM_FLAGS_READ; + alc->ram2->flags &= ~MEM_FLAGS_READ; + alc->ram3->flags &= ~MEM_FLAGS_READ; + break; + + // WRTCOUNT = 0, WRITE DISABLE, READ DISABLE + case 0xc082: + case 0xc086: + alc->wrtcount = 0; + alc->ram1->flags &= ~MEM_FLAGS_WRITE; + alc->ram2->flags &= ~MEM_FLAGS_WRITE; + alc->ram3->flags &= ~MEM_FLAGS_WRITE; + alc->ram1->flags &= MEM_FLAGS_WRITE; + alc->ram2->flags &= MEM_FLAGS_WRITE; + alc->ram3->flags &= MEM_FLAGS_WRITE; + break; + + // WRTCOUNT = 0, READ ENABLE + case 0xc083: + case 0xc08b: + alc->wrtcount = 0; + alc->ram1->flags |= MEM_FLAGS_READ; + alc->ram2->flags |= MEM_FLAGS_READ; + alc->ram3->flags |= MEM_FLAGS_READ; + break; + + default: + fprintf(stderr, "[ALC] Unexpected write at $%.4X\n", addr); + break; + } +} + +int ewm_alc_init(struct ewm_alc_t *alc, struct cpu_t *cpu) { + memset(alc, 0x00, sizeof(struct ewm_alc_t)); + + // Order is important. First added is last tried when looking up + // addresses. So we register the ROM first, which means we never + // have to disable it. + + alc->rom = cpu_add_rom_file(cpu, 0xf800, "roms/341-0020.bin"); + alc->ram3 = cpu_add_ram(cpu, 0xe000, 0xe000 + 8192 - 1); + alc->ram2 = cpu_add_ram(cpu, 0xd000, 0xd000 + 4096 - 1); + alc->ram1 = cpu_add_ram(cpu, 0xd000, 0xd000 + 4096 - 1); + alc->iom = cpu_add_iom(cpu, 0xc080, 0xc08f, alc, alc_iom_read, alc_iom_write); + + // TODO Is this correct? Is everyting disabled at boot? + + alc->ram1->enabled = false; + alc->ram2->enabled = false; + alc->ram3->enabled = false; + + //cpu_mem_disable(cpu, alc->ram1, MEM_ENABLED_READ | MEM_ENABLED_WRITE); + //cpu_mem_disable(cpu, alc->ram2, MEM_ENABLED_READ | MEM_ENABLED_WRITE); + //cpu_mem_disable(cpu, alc->ram3, MEM_ENABLED_READ | MEM_ENABLED_WRITE); + //cpu_mem_disable(cpu, alc->rom, MEM_ENABLED_READ | MEM_ENABLED_WRITE); + + return 0; +} + +struct ewm_alc_t *ewm_alc_create(struct cpu_t *cpu) { + struct ewm_alc_t *alc = malloc(sizeof(struct ewm_alc_t)); + if (ewm_alc_init(alc, cpu) != 0) { + free(alc); + alc = NULL; + } + return alc; +} + void a2p_init(struct a2p_t *a2p, struct cpu_t *cpu) { memset(a2p, 0x00, sizeof(struct a2p_t)); @@ -141,10 +319,15 @@ void a2p_init(struct a2p_t *a2p, struct cpu_t *cpu) { a2p->rom = cpu_add_rom_file(cpu, 0xe800, "roms/341-0014.bin"); // AppleSoft BASIC E800 a2p->rom = cpu_add_rom_file(cpu, 0xf000, "roms/341-0015.bin"); // AppleSoft BASIC E800 a2p->rom = cpu_add_rom_file(cpu, 0xf800, "roms/341-0020.bin"); // AppleSoft BASIC Autostart Monitor F8000 - a2p->iom = cpu_add_iom(cpu, 0xc000, 0xc0ff, a2p, a2p_iom_read, a2p_iom_write); + a2p->iom = cpu_add_iom(cpu, 0xc000, 0xc07f, a2p, a2p_iom_read, a2p_iom_write); a2p->dsk = ewm_dsk_create(cpu); + struct ewm_alc_t *alc = ewm_alc_create(cpu); + if (alc == NULL) { + fprintf(stderr, "[A2P] Could not create Apple Language Card\n"); + } + // TODO Introduce ewm_scr_t that captures everything related to the apple 2 screen so that it can be re-used. a2p->screen_txt_data = malloc(2 * 1024); diff --git a/cpu.c b/cpu.c index 0e506d2..5684004 100644 --- a/cpu.c +++ b/cpu.c @@ -46,7 +46,7 @@ typedef void (*cpu_instruction_handler_word_t)(struct cpu_t *cpu, uint16_t oper) // Stack management. void _cpu_push_byte(struct cpu_t *cpu, uint8_t b) { - _mem_set_byte_direct(cpu, 0x0100 + cpu->state.sp, b); + mem_set_byte(cpu, 0x0100 + cpu->state.sp, b); cpu->state.sp -= 1; } @@ -57,7 +57,7 @@ void _cpu_push_word(struct cpu_t *cpu, uint16_t w) { uint8_t _cpu_pull_byte(struct cpu_t *cpu) { cpu->state.sp += 1; - return _mem_get_byte_direct(cpu, 0x0100 + cpu->state.sp); + return mem_get_byte(cpu, 0x0100 + cpu->state.sp); } uint16_t _cpu_pull_word(struct cpu_t *cpu) { @@ -204,21 +204,6 @@ struct mem_t *cpu_add_mem(struct cpu_t *cpu, struct mem_t *mem) { mem->next = cpu->mem; cpu->mem = mem; } - - // If this is RAM mapped to the zero-page and to the stack then we - // keep a shortcut to it so that we can do direct and fast access - // with our _mem_get/set_byte/word_direct functions. - // - // This makes two assumptions: when RAM is added, it covers both - // pages. And that mem->obj points to a block of memory. This is - // fine for the Apple I and Apple II emulators. - - if (mem->type == MEM_TYPE_RAM) { - if (mem->start == 0x0000 && mem->end >= 0x0200) { - cpu->memory = mem->obj; - } - } - return mem; } @@ -238,7 +223,8 @@ struct mem_t *cpu_add_ram(struct cpu_t *cpu, uint16_t start, uint16_t end) { struct mem_t *cpu_add_ram_data(struct cpu_t *cpu, uint16_t start, uint16_t end, uint8_t *data) { struct mem_t *mem = (struct mem_t*) malloc(sizeof(struct mem_t)); - mem->type = MEM_TYPE_RAM; + mem->enabled = true; + mem->flags = MEM_FLAGS_READ | MEM_FLAGS_WRITE; mem->obj = data; mem->start = start; mem->end = end; @@ -284,7 +270,8 @@ static uint8_t _rom_read(struct cpu_t *cpu, struct mem_t *mem, uint16_t addr) { struct mem_t *cpu_add_rom_data(struct cpu_t *cpu, uint16_t start, uint16_t end, uint8_t *data) { struct mem_t *mem = (struct mem_t*) malloc(sizeof(struct mem_t)); - mem->type = MEM_TYPE_ROM; + mem->enabled = true; + mem->flags = MEM_FLAGS_READ; mem->obj = data; mem->start = start; mem->end = end; @@ -326,7 +313,8 @@ struct mem_t *cpu_add_rom_file(struct cpu_t *cpu, uint16_t start, char *path) { struct mem_t *cpu_add_iom(struct cpu_t *cpu, uint16_t start, uint16_t end, void *obj, mem_read_handler_t read_handler, mem_write_handler_t write_handler) { struct mem_t *mem = (struct mem_t*) malloc(sizeof(struct mem_t)); - mem->type = MEM_TYPE_IOM; + mem->enabled = true; + mem->flags = MEM_FLAGS_READ | MEM_FLAGS_WRITE; mem->obj = obj; mem->start = start; mem->end = end; diff --git a/cpu.h b/cpu.h index e6a70eb..3704a55 100644 --- a/cpu.h +++ b/cpu.h @@ -52,19 +52,18 @@ struct cpu_t { FILE *trace; bool strict; struct mem_t *mem; - uint8_t *memory; // This is pointing to the first 2 pages of memory, zero page and stack. struct cpu_instruction_t *instructions; }; -#define MEM_TYPE_RAM 0 -#define MEM_TYPE_ROM 1 -#define MEM_TYPE_IOM 2 +#define MEM_FLAGS_READ 0x01 +#define MEM_FLAGS_WRITE 0x02 typedef uint8_t (*mem_read_handler_t)(struct cpu_t *cpu, struct mem_t *mem, uint16_t addr); typedef void (*mem_write_handler_t)(struct cpu_t *cpu, struct mem_t *mem, uint16_t addr, uint8_t b); struct mem_t { - uint8_t type; + bool enabled; + uint8_t flags; uint16_t start; uint16_t end; void *obj; diff --git a/fmt.c b/fmt.c index 09c89e9..87048c9 100644 --- a/fmt.c +++ b/fmt.c @@ -53,7 +53,7 @@ void cpu_format_stack(struct cpu_t *cpu, char buffer[764]) { p = strcat(p, " "); } char tmp[8]; - sprintf(tmp, "%.2X", _mem_get_byte_direct(cpu, 0x0100 + sp + 1)); + sprintf(tmp, "%.2X", mem_get_byte(cpu, 0x0100 + sp + 1)); p = strcat(p, tmp); } strcat(p, "]"); diff --git a/mem.c b/mem.c index 7c83008..9f4f779 100644 --- a/mem.c +++ b/mem.c @@ -32,47 +32,38 @@ // take more time but do the right thing. uint8_t mem_get_byte(struct cpu_t *cpu, uint16_t addr) { - struct mem_t *mem = cpu->mem; - while (mem != NULL) { - if (addr >= mem->start && addr <= mem->end) { - if (mem->read_handler) { - return ((mem_read_handler_t) mem->read_handler)((struct cpu_t*) cpu, mem, addr); - } else { - if (cpu->strict) { - // TODO: Signal an error about reading to write-only region (does that even exist?) - } - return 0; + struct mem_t *mem = cpu->mem; + while (mem != NULL) { + if (mem->enabled && addr >= mem->start && addr <= mem->end) { + if (mem->read_handler != NULL && mem->flags & MEM_FLAGS_READ) { + return ((mem_read_handler_t) mem->read_handler)((struct cpu_t*) cpu, mem, addr); + } } - } - mem = mem->next; - } + mem = mem->next; + } - if (cpu->strict) { - // TODO: Signal an error about reading non-existent memory - } + if (cpu->strict) { + // TODO: Signal an error about reading non-existent memory? + } - return 0; // TODO What should the default be if we read from non-existent memory? + return 0; } void mem_set_byte(struct cpu_t *cpu, uint16_t addr, uint8_t v) { - struct mem_t *mem = cpu->mem; - while (mem != NULL) { - if (addr >= mem->start && addr <= mem->end) { - if (mem->write_handler) { - ((mem_write_handler_t) mem->write_handler)((struct cpu_t*) cpu, mem, addr, v); - } else { - if (cpu->strict) { - // TODO: Signal an error about writing to read-only region - } + struct mem_t *mem = cpu->mem; + while (mem != NULL) { + if (mem->enabled && addr >= mem->start && addr <= mem->end) { + if (mem->write_handler && mem->flags & MEM_FLAGS_WRITE) { + ((mem_write_handler_t) mem->write_handler)((struct cpu_t*) cpu, mem, addr, v); + } + return; } - return; - } - mem = mem->next; - } + mem = mem->next; + } - if (cpu->strict) { - // TODO: Signal an error about writing non-existent memory - } + if (cpu->strict) { + // TODO: Signal an error about writing non-existent memory? + } } // Getters @@ -195,27 +186,3 @@ void mem_mod_byte_indx(struct cpu_t *cpu, uint8_t addr, mem_mod_t op) { void mem_mod_byte_indy(struct cpu_t *cpu, uint8_t addr, mem_mod_t op) { mem_set_byte_indy(cpu, addr, op(cpu, mem_get_byte_indy(cpu, addr))); } - -// The following get and set memory directly. There are no checks, so -// make sure you are doing the right thing. Mainly used for managing -// the stack, reading instructions, reading vectors and tracing code. - -uint8_t _mem_get_byte_direct(struct cpu_t *cpu, uint16_t addr) { - assert(addr <= 0x200); - return cpu->memory[addr]; -} - -uint16_t _mem_get_word_direct(struct cpu_t *cpu, uint16_t addr) { - assert(addr <= 0x200); - return *((uint16_t*) &cpu->memory[addr]); -} - -void _mem_set_byte_direct(struct cpu_t *cpu, uint16_t addr, uint8_t v) { - assert(addr <= 0x200); - cpu->memory[addr] = v; -} - -void _mem_set_word_direct(struct cpu_t *cpu, uint16_t addr, uint16_t v) { - assert(addr <= 0x200); - *((uint16_t*) &cpu->memory[addr]) = v; -} diff --git a/mem.h b/mem.h index 6295cef..55a0743 100644 --- a/mem.h +++ b/mem.h @@ -63,10 +63,4 @@ void mem_mod_byte_indy(struct cpu_t *cpu, uint8_t addr, mem_mod_t op); uint16_t mem_get_word(struct cpu_t *cpu, uint16_t addr); void mem_set_word(struct cpu_t *cpu, uint16_t addr, uint16_t v); -// Private. How do we keep them private? -uint8_t _mem_get_byte_direct(struct cpu_t *cpu, uint16_t addr); -uint16_t _mem_get_word_direct(struct cpu_t *cpu, uint16_t addr); -void _mem_set_byte_direct(struct cpu_t *cpu, uint16_t addr, uint8_t v); -void _mem_set_word_direct(struct cpu_t *cpu, uint16_t addr, uint16_t v); - #endif