/* *********************************************************************** * * Example APU implementation with stepping and breakpoint support * * Description * This example APU is simulated in software, so it can be used without any * special hardware. * * For simplicity, it supports only three instructions: a NOP, an unconditional * jump and an increment instruction that operates on a byte of memory. * * Run example_virtualapu.cmm to see the APU in action. * * (c) Lauterbach GmbH * http://www.lauterbach.com/ * * $Id$ * * *********************************************************************** */ #include "t32apu.h" #include #include #include /** changable parameters of the virtual core */ enum { MEMORY_SIZE_DATA = 1024, MEMORY_SIZE_PROGRAM = 1024, MAX_BREAKPOINTS = 4 }; /** access classes. Note that it's currently not possible to use any access * classes but P and D. Support may be added in the future. */ enum { MEMORY_ACCESS_DATA = 0, MEMORY_ACCESS_PROGRAM = 1 }; /** * This state would usually be stored somewhere inside the APU core. As we * simulate the core, we have it as a global variable. */ static struct { /* on-chip breakpoint information */ struct { int type; /**< bitwise or of APU_BTYPE_*, 0 = unused slot */ apuWord address; apuWord addressto; } breakpoints[MAX_BREAKPOINTS]; uint8_t memory_data[MEMORY_SIZE_DATA]; uint8_t memory_program[MEMORY_SIZE_PROGRAM]; apuWord pc; int state; /**< one of APU_STATE_* */ } virtual_state; /** * Query whether there is an active breakpoint at an address. * * @param type Bitwise or of APU_BTYPE_*. A breakpoint is considered a * match if at least one matching bit is set in both @a type * and the breakpoint's type. * @param address Address to query. * @return breakpoint number on match, 0 otherwise */ static int IsBreakpoint(int type, apuWord address) { int i; for (i = 0; i < MAX_BREAKPOINTS; ++i) { if ((virtual_state.breakpoints[i].type & type) == 0) { continue; } if (virtual_state.breakpoints[i].address > address) { continue; } if (virtual_state.breakpoints[i].addressto < address) { continue; } return i + 1; } return 0; } /** * Simulate a single step of the CPU. * * @param ignore_break If != 0, don't stop at breakpoints. */ static void VirtualStep(apuContext context, int ignore_break) { uint32_t instr; uint32_t addr; if (virtual_state.pc > MEMORY_SIZE_PROGRAM - 4) { virtual_state.state = APU_STATE_STOPPED; return; } instr = virtual_state.memory_program[virtual_state.pc + 0] << 0 | virtual_state.memory_program[virtual_state.pc + 1] << 8 | virtual_state.memory_program[virtual_state.pc + 2] << 16 | virtual_state.memory_program[virtual_state.pc + 3] << 24; if (ignore_break == 0 && IsBreakpoint(APU_BPTYPE_PROGRAM, virtual_state.pc)) { /* program breakpoint hit */ virtual_state.state = APU_STATE_STOPPED; return; } switch(instr >> 24) { case 0x00: /* NOP */ virtual_state.pc += 4; break; case 0x01: /* JMP */ virtual_state.pc = instr & 0x00FFFFFF; break; case 0x02: /* INC */ addr = instr & 0x00FFFFFF; if (addr >= MEMORY_SIZE_DATA) { /* error: access out of bounds */ virtual_state.state = APU_STATE_STOPPED; } else if (ignore_break == 0 && IsBreakpoint(APU_BPTYPE_READ | APU_BPTYPE_WRITE, addr)) { /* data breakpoint hit */ virtual_state.state = APU_STATE_STOPPED; } else { ++virtual_state.memory_data[addr]; virtual_state.pc += 4; } break; default: virtual_state.state = APU_STATE_STOPPED; break; } } static int APUAPI CallbackBreak(apuContext context, apuCallbackStruct *cbs, apuPtr proprietary) { virtual_state.state = APU_STATE_STOPPED; return APU_OK; } static int APUAPI CallbackBreakpoint(apuContext context, apuCallbackStruct *cbs, apuPtr proprietary) { int i; if (cbs->x.breakpoint.bpid < 0 || cbs->x.breakpoint.bpid > MAX_BREAKPOINTS) { /* should never happen */ return APU_FAIL; } if (cbs->x.breakpoint.bpid != 0) { /* delete existing breakpoint */ virtual_state.breakpoints[cbs->x.breakpoint.bpid - 1].type = 0; APU_Printf(context, "Breakpoint %d deleted", cbs->x.breakpoint.bpid); return APU_OK; } for (i = 0; i < MAX_BREAKPOINTS; ++i) { if (virtual_state.breakpoints[i].type == 0) { /* empty slot found; set breakpoint */ virtual_state.breakpoints[i].type = cbs->x.breakpoint.bptype; virtual_state.breakpoints[i].address = cbs->x.breakpoint.address; virtual_state.breakpoints[i].addressto = cbs->x.breakpoint.addressto; cbs->x.breakpoint.bpid = i + 1; APU_Printf(context, "Breakpoint %d at 0x%08X--0x%08X: type = 0x%X (%c%c%c)", i + 1, virtual_state.breakpoints[i].address, virtual_state.breakpoints[i].addressto, virtual_state.breakpoints[i].type, (virtual_state.breakpoints[i].type & APU_BPTYPE_PROGRAM) != 0 ? 'p' : '-', (virtual_state.breakpoints[i].type & APU_BPTYPE_READ) != 0 ? 'r' : '-', (virtual_state.breakpoints[i].type & APU_BPTYPE_WRITE) != 0 ? 'w' : '-'); return APU_OK; } } /* no empty slot found; by not setting bpid we tell TRACE32 that the * breakpoint was not set */ APU_Printf(context, "No more room for breakpoint"); return APU_OK; } static int APUAPI CallbackDisassembler(apuContext context, apuCallbackStruct * cbs, apuPtr proprietary) { char *target = cbs->x.dis.mnemo; uint32_t instr = cbs->x.dis.data[0] << 0 | cbs->x.dis.data[1] << 8 | cbs->x.dis.data[2] << 16 | cbs->x.dis.data[3] << 24; cbs->x.dis.instlen = 4; switch (instr >> 24) { case 0x00: strcpy(target, "NOP"); break; case 0x01: sprintf(target, "JMP 0x%06X", instr & 0x00FFFFFF); break; case 0x02: sprintf(target, "INC [0x%06X]", instr & 0x00FFFFFF); break; default: strcpy(target, "invalid"); cbs->x.dis.instlen = 0; break; } return APU_OK; } static int APUAPI CallbackGetState(apuContext context, apuCallbackStruct *cbs, apuPtr proprietary) { cbs->x.state.state = virtual_state.state; if (virtual_state.state != APU_STATE_RUNNING) { cbs->x.state.pc = virtual_state.pc; } else { /* single step the APU to simulate that it's running. Real hardware would * run asynchronously. */ VirtualStep(context, 0); } return APU_OK; } static int APUAPI CallbackGo(apuContext context, apuCallbackStruct *cbs, apuPtr proprietary) { virtual_state.state = APU_STATE_RUNNING; VirtualStep(context, 1); /* ignore any breakpoints on the first instruction */ return APU_OK; } static int APUAPI CallbackMemoryRead(apuContext context, apuCallbackStruct *cbs, apuPtr proprietary) { apuWord i; int errors = 0; uint8_t *memory; apuWord size; if (cbs->x.memory.flags == MEMORY_ACCESS_DATA) { memory = virtual_state.memory_data; size = MEMORY_SIZE_DATA; } else if (cbs->x.memory.flags == MEMORY_ACCESS_PROGRAM) { memory = virtual_state.memory_program; size = MEMORY_SIZE_PROGRAM; } for (i = 0; i < cbs->x.memory.length; ++i) { if (cbs->x.memory.address + i >= size) { ++errors; } else { cbs->x.memory.data[i] = memory[cbs->x.memory.address + i]; } } return errors == 0 ? APU_OK : APU_FAIL; } static int APUAPI CallbackMemoryWrite(apuContext context, apuCallbackStruct *cbs, apuPtr proprietary) { apuWord i; int errors = 0; uint8_t *memory; apuWord size; if (cbs->x.memory.flags == MEMORY_ACCESS_DATA) { memory = virtual_state.memory_data; size = MEMORY_SIZE_DATA; } else if (cbs->x.memory.flags == MEMORY_ACCESS_PROGRAM) { memory = virtual_state.memory_program; size = MEMORY_SIZE_PROGRAM; } for (i = 0; i < cbs->x.memory.length; ++i) { if (cbs->x.memory.address + i >= size) { ++errors; } else { memory[cbs->x.memory.address + i] = cbs->x.memory.data[i]; } } return errors == 0 ? APU_OK : APU_FAIL; } static int APUAPI CallbackStep(apuContext context, apuCallbackStruct *cbs, apuPtr proprietary) { VirtualStep(context, 1); return APU_OK; } /* For demo purposes, just show how to decode am apuParamCallbackStruct. */ static void DumpParameters(apuContext context, apuParamCallbackStruct *param) { int i; APU_Printf(context, "version = %d, argc = %d, commandline = %s", param->version, param->argc, param->commandline); for (i = 0; i < param->argc; ++i) { APU_Printf(context, "%d: RAW %s", i, param->argp[i]); if (param->version >= 2) { switch (param->argptype[i]) { case APU_PARAM_TYPE_KEYWORD: APU_Printf(context, "-> keyword"); break; case APU_PARAM_TYPE_BOOL: APU_Printf(context, "-> bool %s", param->argpword[i] != 0 ? "true" : "false"); break; case APU_PARAM_TYPE_INT: APU_Printf(context, "-> int 0x%016" PRIX64, param->argpword64[i]); break; case APU_PARAM_TYPE_INTRANGE: APU_Printf(context, "-> int range 0x%016" PRIX64 "--0x%016" PRIX64, param->argpword64[i], param->argpwordupper64[i] - 1); break; case APU_PARAM_TYPE_ADDRESS: APU_Printf(context, "-> address 0x%016" PRIX64, param->argpaddress64[i]); break; case APU_PARAM_TYPE_ADDRESSRANGE: APU_Printf(context, "-> address range 0x%016" PRIX64 "--0x%016" PRIX64, param->argpaddress64[i], param->argpaddressupper64[i] - 1); break; case APU_PARAM_TYPE_STRING: APU_Printf(context, "-> string %s", param->argpstring[i] != NULL ? param->argpstring[i] : ""); break; default: APU_Printf(context, "-> unknown type %d", param->argptype[i]); break; } } } } static int APUAPI CallbackCommand(apuContext context, apuCallbackStruct *cbs, apuPtr proprietary) { DumpParameters(context, &cbs->x.command); return APU_OK; } int APUAPI APU_Init(apuContext context, apuCallbackStruct *cbs) { strcpy(cbs->x.init.modelname, __DATE__ " Virtual APU"); DumpParameters(context, &cbs->x.init); APU_DefineEndianess(context, APU_ENDIANNESS_LITTLE); APU_DefineMemory(context, MEMORY_ACCESS_DATA, "D", 1, 4); APU_DefineMemory(context, MEMORY_ACCESS_PROGRAM, "P", 1, 4); APU_RegisterBreakCallback(context, CallbackBreak, NULL); APU_RegisterBreakpointCallback(context, CallbackBreakpoint, NULL, APU_BPTYPE_PROGRAM | APU_BPTYPE_READ | APU_BPTYPE_WRITE); APU_RegisterDisassemblerCallback(context, CallbackDisassembler, NULL, 1, 6); APU_RegisterGetStateCallback(context, CallbackGetState, NULL); APU_RegisterGoCallback(context, CallbackGo, NULL); APU_RegisterMemoryReadCallback(context, CallbackMemoryRead, NULL); APU_RegisterMemoryWriteCallback(context, CallbackMemoryWrite, NULL); APU_RegisterStepCallback(context, CallbackStep, NULL); APU_RegisterCommandCallback(context, CallbackCommand, NULL); return APU_OK; }