mirror of
https://github.com/st3fan/ewm.git
synced 2024-12-28 00:29:49 +00:00
Fixes #48 Implement the Apple Language Card
This commit is contained in:
parent
653217f5c0
commit
7cb4a4fd30
185
a2p.c
185
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);
|
||||
|
28
cpu.c
28
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;
|
||||
|
9
cpu.h
9
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;
|
||||
|
2
fmt.c
2
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, "]");
|
||||
|
81
mem.c
81
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;
|
||||
}
|
||||
|
6
mem.h
6
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
|
||||
|
Loading…
Reference in New Issue
Block a user