/* * ============================================================================ * CP_1610: CP-1610 Main Core * * Author: J. Zbiciak * Last Revision: 9/12/98 * ============================================================================ * CPU_INIT -- Initializes the CP-1610 structure to a basic setup * CPU_RUN -- Runs the CPU for some number of microcycles. * CPU_RD -- Perform a read from the CPU (macro in cp_1610.h) * CPU_WR -- Perform a write from the CPU (macro in cp_1610.h) * CPU_RD_RAM_8 -- Reads from an 8-bit RAM. 0 wait states * CPU_RD_RAM_16 -- Reads from a 16-bit RAM. 0 wait states * CPU_RD_ROM_10 -- Reads from a 10-bit ROM. 0 wait states * CPU_RR_NULL -- Ignored read. 0 wait states * CPU_WR_RAM_8 -- Writes to an 8-bit RAM. 0 wait states * CPU_WR_RAM_16 -- Writes to a 16-bit RAM. 0 wait states * CPU_WR_NULL -- Ignored write. 0 wait states * ============================================================================ * * Notes/To-do: * * -- The CPU_RD_xxx, CPU_WR_xxx functions usually should not be called * directly. Rather, the CPU_RD, CPU_WR macros should be used to assure * proper address decoding and dispatch. * * -- The CPU supports interrupts but doesn't currently have a method for * receiving them. Peripherals wishing to interrupt the CPU need to * somehow set cpu->irq to '1' to trigger an interrupt. * * -- The CPU supports external branch conditions. Peripherals need to set * cpu->ext to the appropriate state to trigger these. * * -- SIN, TCI, etc. don't do anything yet. What do they need to do? * * -- Functions should be provided for setting up RAM, ROM images, and * registering peripherals. * * ============================================================================ */ #include "config.h" #include #include #include #include "cp_1610.h" #include "op_decode.h" #include "op_exec.h" /* * ============================================================================ * CPU_INIT -- Initializes a CPU_T structure * * This function sets up a basic CPU structure. It allocates a local "flat * memory" which corresponds to the CP-1610 memory map, and it sets up the * initial state of the instruction decoder logic. * * When it's finished, the CPU structure is configured to *not* have any * memory deviced enabled at all. Calls to "CPU_ADD_xxx" should be issued * to configure the memory map as appropriate. * ============================================================================ */ int cpu_init ( cpu_t *cpu ) { int i; /* -------------------------------------------------------------------- */ /* Avoid problems with a garbage CPU structure by setting it to all- */ /* bits-zero. Note: This could be a portability problem to machines */ /* which represent NULL pointers as something other than all-bits- */ /* -zero, but what modern machines do that? */ /* -------------------------------------------------------------------- */ memset((void*)cpu, 0, sizeof(cpu_t)); /* -------------------------------------------------------------------- */ /* Allocate (and zero) our flat memory. This flat-memory area is */ /* used for all RAM and ROM devices and to shadow values that are */ /* written to peripherals, where appropriate. */ /* -------------------------------------------------------------------- */ cpu->raw_mem = calloc(1, sizeof(cpu_word_t) << CPU_MEMSIZE); if (!cpu->raw_mem) { /* ---------------------------------------------------------------- */ /* XXX: This should be done more cleanly, like it is in the */ /* res_file library code. */ /* ---------------------------------------------------------------- */ fprintf(stderr,"Out of memory in cpu_init()\n"); exit(1); } /* -------------------------------------------------------------------- */ /* Set the entire memory map to not-implemented and not-cacheable. */ /* Outside routines will need to configure the memory map to make the */ /* CPU useful. :-) It is especially important to set the cacheable */ /* bits for performance. */ /* -------------------------------------------------------------------- */ for (i = 0; i < (1 << (CPU_MEMSIZE - CPU_DECODE_PAGE)); i++) { cpu->rd_mem[i] = cpu_rd_null; cpu->wr_mem[i] = cpu_wr_null; } for (i = 0; i < (1 << (CPU_MEMSIZE - CPU_DECODE_PAGE - 5)); i++) { cpu->cacheable[i] = 0; } /* -------------------------------------------------------------------- */ /* Mark all instructions as needing decode. The fn_decode function */ /* will cache the decoded instruction if the "cacheable" bit is set */ /* for the page containing the instruction. */ /* -------------------------------------------------------------------- */ for (i = 0; i < (1 << CPU_MEMSIZE); i++) { cpu->execute[i] = fn_decode; } return 0; } /* * ============================================================================ * CPU_RUN -- Runs the CPU for some number of microcycles * * This is the main CPU loop. It is responsible for fetching instructions, * decoding them if necessary (or using predecoded instructions if possible) * and calling the required execute functions. * * The cpu_run function will run as many instructions as are necessary to * just barely exceed the specified number of microcycles. eg. It will * execute a new instruction if the specified total number of microcycles * has not yet been exceeded. The new instruction may exceed the specified * number of microcycles. The total number of microcycles exhausted is * returned as an int. * ============================================================================ */ int cpu_run ( cpu_t *cpu, unsigned microcycles ) { unsigned tot_microcycles = 0, cycles, instrs = 0; unsigned next_checkpoint = 0; unsigned pc; instr_func_t execute; instr_t *instr; /* -------------------------------------------------------------------- */ /* Loop until our total count of microcycles exceeds the requested */ /* run period. If microcycles is greater than 0, then we execute a */ /* minimum of 1 instruction. Setting "microcycles" to 1 is a great */ /* way to single-step (say, in a debugger or something). */ /* -------------------------------------------------------------------- */ while (tot_microcycles < microcycles) { /* ---------------------------------------------------------------- */ /* If there is an interrupt pending, and we are interruptible, */ /* then take the interrupt. */ /* ---------------------------------------------------------------- */ if (cpu->irq & cpu->intr) { int ws = 0; /* ------------------------------------------------------------ */ /* Take an interrupt. */ /* -- Push new PC onto stack. */ /* -- Set PC to interrupt vector. */ /* -- Clear interrupt-pending flag. */ /* ------------------------------------------------------------ */ CPU_WR(cpu, &ws, cpu->r[6], cpu->r[7]); cpu->r[6]++; tot_microcycles += ws + 12; cpu->tot_cycle += ws + 12; pc = cpu->r[7] = cpu->int_vec; cpu->irq = 0; } /* ---------------------------------------------------------------- */ /* We copy register 7 into the "pc" variable for speed. */ /* ---------------------------------------------------------------- */ pc = cpu->r[7]; /* ---------------------------------------------------------------- */ /* Execute for up to 1000 microcycles, unless we have a pending */ /* interrupt. Then only run for about 10 microcycles. */ /* ---------------------------------------------------------------- */ next_checkpoint += (cpu->irq ? 100 : 1000); if (next_checkpoint > microcycles) next_checkpoint = microcycles; while (tot_microcycles < next_checkpoint) { /* ------------------------------------------------------------ */ /* Grab our execute function and instruction pointer. */ /* ------------------------------------------------------------ */ execute = cpu->execute[pc]; instr = cpu->instr[pc]; /* ------------------------------------------------------------ */ /* The flag cpu->intr is our interruptibility state. It is */ /* set equal to our interrupt enable bit, and is cleared by */ /* non-interruptible instructions, thus making it a "logical- */ /* AND" of the two conditions. */ /* ------------------------------------------------------------ */ cpu->intr = cpu->I; /* ------------------------------------------------------------ */ /* Execute the next instruction, and record its cycle count */ /* and new PC value. Count-down the DBD state. */ /* ------------------------------------------------------------ */ cycles = execute(instr,cpu); pc = cpu->r[7]; cpu->D >>= 1; /* ------------------------------------------------------------ */ /* Tally up instruction count and microcycle count. */ /* ------------------------------------------------------------ */ tot_microcycles += cycles; cpu->tot_cycle += cycles; instrs++; } } /* -------------------------------------------------------------------- */ /* Update our global "total instructions" count. */ /* -------------------------------------------------------------------- */ cpu->tot_instr += instrs; /* -------------------------------------------------------------------- */ /* Return how long we ran for. */ /* -------------------------------------------------------------------- */ return tot_microcycles; } /* * ============================================================================ * CPU_RD_MEM_8 -- Reads from an 8-bit memory. 0 wait states * CPU_RD_MEM_10 -- Reads from a 10-bit memory. 0 wait states * CPU_RD_MEM_12 -- Reads from a 10-bit memory. 0 wait states * CPU_RD_MEM_14 -- Reads from a 10-bit memory. 0 wait states * CPU_RD_MEM_16 -- Reads from a 16-bit memory. 0 wait states * CPU_RR_NULL -- Ignored read. 0 wait states * CPU_WR_MEM_8 -- Writes to an 8-bit memory. 0 wait states * CPU_WR_MEM_10 -- Writes to a 10-bit memory. 0 wait states * CPU_WR_MEM_12 -- Writes to a 12-bit memory. 0 wait states * CPU_WR_MEM_14 -- Writes to a 14-bit memory. 0 wait states * CPU_WR_MEM_16 -- Writes to a 16-bit memory. 0 wait states * CPU_WR_NULL -- Ignored write. 0 wait states * ============================================================================ */ cpu_word_t cpu_rd_mem_8 (cpu_p cpu,int *ws,cpu_word_t addr) { /* -------------------------------------------------------------------- */ /* Read raw memory, return lower 8 bits only. */ /* -------------------------------------------------------------------- */ *ws = 0; return cpu->raw_mem[addr] & 0xFF; } cpu_word_t cpu_rd_mem_10 (cpu_p cpu,int *ws,cpu_word_t addr) { /* -------------------------------------------------------------------- */ /* Read raw memory, return lower 10 bits only. */ /* -------------------------------------------------------------------- */ *ws = 0; return cpu->raw_mem[addr] & 0x3FF; } cpu_word_t cpu_rd_mem_12 (cpu_p cpu,int *ws,cpu_word_t addr) { /* -------------------------------------------------------------------- */ /* Read raw memory, return lower 12 bits only. */ /* -------------------------------------------------------------------- */ *ws = 0; return cpu->raw_mem[addr] & 0xFFF; } cpu_word_t cpu_rd_mem_14 (cpu_p cpu,int *ws,cpu_word_t addr) { /* -------------------------------------------------------------------- */ /* Read raw memory, return lower 14 bits only. */ /* -------------------------------------------------------------------- */ *ws = 0; return cpu->raw_mem[addr] & 0x3FFF; } cpu_word_t cpu_rd_mem_16 (cpu_p cpu,int *ws,cpu_word_t addr) { /* -------------------------------------------------------------------- */ /* Read raw memory. */ /* -------------------------------------------------------------------- */ *ws = 0; return cpu->raw_mem[addr]; } cpu_word_t cpu_rd_hnd_8 (cpu_p cpu,int *ws,cpu_word_t addr) { static int count = 0; /* -------------------------------------------------------------------- */ /* Read raw memory. */ /* -------------------------------------------------------------------- */ *ws = 0; count++; count &= 0x3FF; return (count > 0x300) ? 0xEF : 0xFF; } cpu_word_t cpu_rd_null (cpu_p cpu,int *ws,cpu_word_t addr) { *ws = 0; (void)cpu; /* unused */ (void)addr; /* unused */ return ~0; } void cpu_wr_mem_8 (cpu_p cpu,int *ws,cpu_word_t a,cpu_word_t data) { unsigned x; *ws = 0; /* -------------------------------------------------------------------- */ /* Invalidate any predecoded instructions that might overlap this */ /* address. Don't bother to check to see if they actually exist -- */ /* that's too slow. :-) */ /* -------------------------------------------------------------------- */ x = 0xFFFF & (a-2); cpu->execute[x] = fn_decode; x = 0xFFFF & (a-1); cpu->execute[x] = fn_decode; x = 0xFFFF & (a-0); cpu->execute[x] = fn_decode; /* -------------------------------------------------------------------- */ /* Write raw memory, storing lower 8 bits only. */ /* -------------------------------------------------------------------- */ cpu->raw_mem[a] = data & 0xFF; } void cpu_wr_mem_10 (cpu_p cpu,int *ws,cpu_word_t a,cpu_word_t data) { unsigned x; *ws = 0; /* -------------------------------------------------------------------- */ /* Invalidate any predecoded instructions that might overlap this */ /* address. Don't bother to check to see if they actually exist -- */ /* that's too slow. :-) */ /* -------------------------------------------------------------------- */ x = 0xFFFF & (a-2); cpu->execute[x] = fn_decode; x = 0xFFFF & (a-1); cpu->execute[x] = fn_decode; x = 0xFFFF & (a-0); cpu->execute[x] = fn_decode; /* -------------------------------------------------------------------- */ /* Write raw memory, storing lower 10 bits only. */ /* -------------------------------------------------------------------- */ cpu->raw_mem[a] = data & 0x3FF; } void cpu_wr_mem_12 (cpu_p cpu,int *ws,cpu_word_t a,cpu_word_t data) { unsigned x; *ws = 0; /* -------------------------------------------------------------------- */ /* Invalidate any predecoded instructions that might overlap this */ /* address. Don't bother to check to see if they actually exist -- */ /* that's too slow. :-) */ /* -------------------------------------------------------------------- */ x = 0xFFFF & (a-2); cpu->execute[x] = fn_decode; x = 0xFFFF & (a-1); cpu->execute[x] = fn_decode; x = 0xFFFF & (a-0); cpu->execute[x] = fn_decode; /* -------------------------------------------------------------------- */ /* Write raw memory, storing lower 12 bits only. */ /* -------------------------------------------------------------------- */ cpu->raw_mem[a] = data & 0xFFF; } void cpu_wr_mem_14 (cpu_p cpu,int *ws,cpu_word_t a,cpu_word_t data) { unsigned x; *ws = 0; /* -------------------------------------------------------------------- */ /* Invalidate any predecoded instructions that might overlap this */ /* address. Don't bother to check to see if they actually exist -- */ /* that's too slow. :-) */ /* -------------------------------------------------------------------- */ x = 0xFFFF & (a-2); cpu->execute[x] = fn_decode; x = 0xFFFF & (a-1); cpu->execute[x] = fn_decode; x = 0xFFFF & (a-0); cpu->execute[x] = fn_decode; /* -------------------------------------------------------------------- */ /* Write raw memory, storing lower 14 bits only. */ /* -------------------------------------------------------------------- */ cpu->raw_mem[a] = data & 0x3FFF; } void cpu_wr_mem_16 (cpu_p cpu,int *ws,cpu_word_t a,cpu_word_t data) { unsigned x; *ws = 0; /* -------------------------------------------------------------------- */ /* Invalidate any predecoded instructions that might overlap this */ /* address. Don't bother to check to see if they actually exist -- */ /* that's too slow. :-) */ /* -------------------------------------------------------------------- */ x = 0xFFFF & (a-2); cpu->execute[x] = fn_decode; x = 0xFFFF & (a-1); cpu->execute[x] = fn_decode; x = 0xFFFF & (a-0); cpu->execute[x] = fn_decode; /* -------------------------------------------------------------------- */ /* Write raw memory. */ /* -------------------------------------------------------------------- */ cpu->raw_mem[a] = data; } void cpu_wr_null (cpu_p cpu,int *ws,cpu_word_t addr,cpu_word_t data) { *ws = 0; (void)cpu; /* unused */ (void)addr; /* unused */ } /* * ============================================================================ * CPU_ADD_RAM -- Adds a RAM starting at a given address of a given size * CPU_ADD_ROM -- Adds a ROM starting at a given address of a given size * * These functions add 0ws RAMs and ROMs into the CPU's memory map. The * CPU_ADD_ROM function expects a ROM image to be presented to it. The * ROM image should be an array of cpu_word_t's. * * Note that the size of the RAM or ROM is limited to the address decoding * granularity. * * Both functions expect a width (8,10,12,14,16), a starting address, a * size (specified in "number of CPU words"), and a "cacheability" bit. * Most things should set the cacheability bit to 1. * * To do: Bank-switched ROMs are not supported by this scheme. Perhaps * do this with an outside peripheral function instead of these general * functions? * ============================================================================ */ int cpu_add_ram ( cpu_t *cpu, int width, cpu_word_t addr, cpu_word_t size, int cache ) { /* -------------------------------------------------------------------- */ /* Lookup tables between width and memory accessing functions. */ /* -------------------------------------------------------------------- */ cpu_memr_t rd_fn[17] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, cpu_rd_mem_8, NULL, cpu_rd_mem_10, NULL, cpu_rd_mem_12, NULL, cpu_rd_mem_14, NULL, cpu_rd_mem_16 }; cpu_memw_t wr_fn[17] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, cpu_wr_mem_8, NULL, cpu_wr_mem_10, NULL, cpu_wr_mem_12, NULL, cpu_wr_mem_14, NULL, cpu_wr_mem_16 }; cpu_word_t i, p; /* -------------------------------------------------------------------- */ /* Sanity check our width argument. Abort if it's grossly bad. */ /* -------------------------------------------------------------------- */ if (width < 0 || width > 16 || !wr_fn[width] || !rd_fn[width]) { fprintf(stderr, "Invalid RAM width %d @ 0x%.4x\n",width,addr); exit(1); } /* -------------------------------------------------------------------- */ /* Set up our read/write function pointers and cacheability bits. */ /* This isn't hugely efficient, but it doesn't need to be anyway. */ /* -------------------------------------------------------------------- */ for (i = addr; i < (addr + size); i += 1 << CPU_DECODE_PAGE) { p = i >> CPU_DECODE_PAGE; cpu->rd_mem[p] = rd_fn[width]; cpu->wr_mem[p] = wr_fn[width]; if (cache) cpu->cacheable[p >> 5] |= (1 << (p&31)); else cpu->cacheable[p >> 5] &= ~(1 << (p&31)); } return 0; } int cpu_add_rom ( cpu_t *cpu, int width, cpu_word_t addr, cpu_word_t size, int cache, const cpu_word_t *image ) { /* -------------------------------------------------------------------- */ /* Lookup tables between width and memory accessing functions. */ /* -------------------------------------------------------------------- */ cpu_memr_t rd_fn[17] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, cpu_rd_hnd_8, cpu_rd_mem_8, NULL, cpu_rd_mem_10, NULL, cpu_rd_mem_12, NULL, cpu_rd_mem_14, NULL, cpu_rd_mem_16 }; cpu_word_t i, p, mask; /* -------------------------------------------------------------------- */ /* Sanity check our width argument. Abort if it's grossly bad. */ /* -------------------------------------------------------------------- */ if (width < 0 || width > 16 || !rd_fn[width]) { fprintf(stderr, "Invalid ROM width %d @ 0x%.4x\n",width,addr); exit(1); } /* -------------------------------------------------------------------- */ /* Set up our read/write function pointers and cacheability bits. */ /* This isn't hugely efficient, but it doesn't need to be anyway. */ /* -------------------------------------------------------------------- */ for (i = addr; i < (addr + size); i += 1 << CPU_DECODE_PAGE) { p = i >> CPU_DECODE_PAGE; cpu->rd_mem[p] = rd_fn[width]; cpu->wr_mem[p] = cpu_wr_null; if (cache) cpu->cacheable[p >> 5] |= (1 << (p&31)); else cpu->cacheable[p >> 5] &= ~(1 << (p&31)); } /* -------------------------------------------------------------------- */ /* Initialize our ROM image from the image provided by the user. */ /* Mask away the bits that are outside the ROM's width. */ /* -------------------------------------------------------------------- */ mask = ~((~0) << width); for (i = 0; i < size; i++) { cpu->raw_mem[i + addr] = image[i] & mask; } return 0; } /* * ============================================================================ * Copyright (c) 1998, Joseph Zbiciak. * ============================================================================ */