#include "pico.h" #include "pico/stdlib.h" #include #include "cpc.h" #include #include extern "C" { #include "emuapi.h" #include "platform_config.h" } #ifndef CHIPS_IMPL #define CHIPS_IMPL #endif #include "z80.h" #include "crtc.h" #include "ga.h" #include "roms/rom464.h" #define WIDTH 320 #define HEIGHT 200 #define CYCLES_PER_FRAME 79872 //79872 //19968 #define NBLINES 312 #define CYCLES_PER_SCANLINE CYCLES_PER_FRAME/NBLINES #define LOWER_ROM_END 0x4000 #define UPPER_ROM_BEGIN 0xC000 // Declarations of instances of the RAM, VRAM, processor and other required components. uint8_t RAM[0x10000]; // 64k unsigned char* bitstream = 0; // 16k video ram to be used by PIO. static z80_t CPU; uint64_t pins; bool interrupt_generated = false; int sline = 0; // Helper functions char read_z80(uint16_t Addr) { if(Addr <= LOWER_ROM_END && ga_config.lower_rom_enable) { // printf("At program counter %x, Z80 read from address %x in OS ROM\n", CPU.PC.W, Addr); return gb_rom_464_0[Addr]; } else if(Addr >= UPPER_ROM_BEGIN && ga_config.upper_rom_enable) { // printf("At program counter %x, Z80 read from address %x in BASIC ROM\n", CPU.PC.W, Addr); return gb_rom_464_1[Addr - 0xC000]; } else { // printf("At program counter %x, Z80 read from address %x in RAM\n", CPU.PC.W, Addr); return RAM[Addr]; } } void write_z80(uint16_t Addr, uint8_t Value) { RAM[Addr] = Value; } void out_z80(uint16_t Port, uint8_t Value) { if(!(Port & 0x8000)) write_gate_array(Value); // The Gate Array is selected when bit 15 is set to 0. if(!(Port & 0x4000)) write_crt_controller(Port, Value); // The CRTC is selected when bit 14 is set to 0. if(!(Port & 0x2000)) { // upper rom bank number. ROM banking needs to be done regardless of CPC model // The Upper ROM Bank Number (in range of 0x00..0xFF) to be mapped to memory at 0xC000..0xFFFF // byte req_bank_number = Value & 15; // if(ga_config.upper_rom_enable) // { // } } } uint8_t in_z80(uint16_t Port) { if(!(Port & 0x4000)) return read_crt_controller(Port); // The CRTC is selected when bit 14 is set to 0. return 0xFF; } /** * Creates initial emulation state (i.e. sets up color palette, clears memory etc.) */ void cpc_Init(void) { for(int i = 0; i < PALETTE_SIZE; i++) { emu_SetPaletteEntry(firmware_palette[i].R, firmware_palette[i].G, firmware_palette[i].B, i); } if (bitstream == 0) bitstream = (unsigned char *)emu_Malloc(WIDTH*HEIGHT); //*HEIGHT pins = z80_init(&CPU); memset(RAM, 0, sizeof(RAM)); } /** * Starts the emulator by setting the initial program counter and emulates initial hardware state. */ void cpc_Start(char* filename) { ga_config.lower_rom_enable = true; ga_config.upper_rom_enable = false; ga_config.interrupt_counter = 0; } /** * Steps through emulation and renders the screen. */ void cpc_Step(void) { // if not (z80 wait and ga wait) then tick, otherwise stall. // or rather the tick will be a stall if both waits are asserted, i think that's better. bool interrupt_acknowledged = false; pins = z80_tick(&CPU, pins); if (pins & Z80_MREQ) { const uint16_t addr = Z80_GET_ADDR(pins); if (pins & Z80_RD) { uint8_t data = read_z80(addr); Z80_SET_DATA(pins, data); } else if (pins & Z80_WR) { uint8_t data = Z80_GET_DATA(pins); write_z80(addr, data); } } else if (pins & Z80_IORQ) { const uint16_t port = Z80_GET_ADDR(pins); if (pins & Z80_M1) { // an interrupt acknowledge cycle, depending on the emulated system, // put either an instruction byte, or an interrupt vector on the data bus Z80_SET_DATA(pins, 0x0038); interrupt_acknowledged = true; } else if (pins & Z80_RD) { // handle IO input request at port in_z80(port); } else if (pins & Z80_WR) { // handle IO output request at port uint8_t data = Z80_GET_DATA(pins); out_z80(port, data); } } crtc_step(); interrupt_generated = ga_step(); if(ga_config.wait_signal) { // printf("Waiting in the next cycle.\n"); pins = pins | Z80_WAIT; } else { // printf("Not waiting in the next cycle.\n"); pins = pins & ~Z80_WAIT; } if(interrupt_generated) { // To request an interrupt, or inject a wait state just set the respective pin // (Z80_INT, Z80_NMI, Z80_WAIT), don't forget to clear the pin again later (the // details on when those pins are set and cleared depend heavily on the // emulated system). // request an interrupt from the CPU // TODO how to set the Z80_INT pin? pins = pins | Z80_INT; } if(interrupt_acknowledged) { pins = pins & ~Z80_INT; ga_config.interrupt_counter &= 0x1f; } if(is_hsync_active() && !vsync_wait) { if(sline + 1 == NBLINES) { emu_DrawVsync(); vsync_wait = true; } sline = (sline + 1) % NBLINES; emu_DrawLine8(bitstream, WIDTH, HEIGHT, sline); } } void cpc_Input(int bClick) { }