/* *********************************************************************** * * Example APU implementation with address translation and a memory access unit (MAU) that is not 1 byte * * 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_address_translation.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 { MAX_BREAKPOINTS = 4 }; /** parameters that define how the APU's memory is mapped into the main core's * memory */ enum { MEMORY_BASE_ADDRESS_DATA = 0x10000000, MEMORY_ACCESS_UNIT_DATA = 2, MEMORY_BASE_ADDRESS_PROGRAM = 0x20000000, MEMORY_ACCESS_UNIT_PROGRAM = 4, MEMORY_BASE_ADDRESS_USER = 0x30000000, MEMORY_ACCESS_UNIT_USER = 1 }; /** 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, MEMORY_ACCESS_USER = 2 }; /** * 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]; apuWord pc; int state; /**< one of APU_STATE_* */ } virtual_state; /** * Read single word of APU program memory * * @param context APU context * @param address Byte address of word to read. * @param data Where to write the read data * @return APU_OK or APU_FAIL */ static int ReadProgramMemory(apuContext context, apuWord address, uint32_t *data) { uint8_t raw_data[MEMORY_ACCESS_UNIT_PROGRAM]; int error; error = APU_ReadMemory(context, address + MEMORY_BASE_ADDRESS_PROGRAM, T32_MEMORY_ACCESS_DATA, raw_data, MEMORY_ACCESS_UNIT_PROGRAM, 1); if (error != APU_OK) { return error; } *data = (uint32_t)raw_data[0] << 0 | (uint32_t)raw_data[1] << 8 | (uint32_t)raw_data[2] << 16 | (uint32_t)raw_data[3] << 24; return APU_OK; } /** * Write single word of APU program memory * * @param context APU context * @param address Byte address of word to read. * @param data data to write * @return APU_OK or APU_FAIL */ static int WriteProgramMemory(apuContext context, apuWord address, uint32_t data) { uint8_t raw_data[MEMORY_ACCESS_UNIT_PROGRAM]; raw_data[0] = (uint8_t)(data >> 0); raw_data[1] = (uint8_t)(data >> 8); raw_data[2] = (uint8_t)(data >> 16); raw_data[3] = (uint8_t)(data >> 24); return APU_WriteMemory(context, address + MEMORY_BASE_ADDRESS_PROGRAM, T32_MEMORY_ACCESS_DATA, raw_data, MEMORY_ACCESS_UNIT_PROGRAM, 1); } /** * Read single word of APU data memory * * @param context APU context * @param address Byte address of word to read. * @param data Where to write the read data * @return APU_OK or APU_FAIL */ static int ReadDataMemory(apuContext context, apuWord address, uint16_t *data) { uint8_t raw_data[MEMORY_ACCESS_UNIT_DATA]; int error; error = APU_ReadMemory(context, address + MEMORY_BASE_ADDRESS_DATA, T32_MEMORY_ACCESS_DATA, raw_data, MEMORY_ACCESS_UNIT_DATA, 1); if (error != APU_OK) { return error; } *data = (uint16_t)raw_data[0] << 0 | (uint16_t)raw_data[1] << 8; return APU_OK; } /** * Write single word of APU data memory * * @param context APU context * @param address Byte address of word to read. * @param data data to write * @return APU_OK or APU_FAIL */ static int WriteDataMemory(apuContext context, apuWord address, uint16_t data) { uint8_t raw_data[MEMORY_ACCESS_UNIT_DATA]; raw_data[0] = (uint8_t)(data >> 0); raw_data[1] = (uint8_t)(data >> 8); return APU_WriteMemory(context, address + MEMORY_BASE_ADDRESS_DATA, T32_MEMORY_ACCESS_DATA, raw_data, MEMORY_ACCESS_UNIT_DATA, 1); } /** * Read single word of APU user memory * * @param context APU context * @param address Byte address of word to read. * @param data Where to write the read data * @return APU_OK or APU_FAIL */ static int ReadUserMemory(apuContext context, apuWord address, uint8_t *data) { return APU_ReadMemory(context, address + MEMORY_BASE_ADDRESS_USER, T32_MEMORY_ACCESS_DATA, data, MEMORY_ACCESS_UNIT_USER, 1); } /** * Write single word of APU user memory * * @param context APU context * @param address Byte address of word to read. * @param data data to write * @return APU_OK or APU_FAIL */ static int WriteUserMemory(apuContext context, apuWord address, uint8_t data) { return APU_WriteMemory(context, address + MEMORY_BASE_ADDRESS_USER, T32_MEMORY_ACCESS_DATA, &data, MEMORY_ACCESS_UNIT_USER, 1); } /** * 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 Byte 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; int error; error = ReadProgramMemory(context, virtual_state.pc * MEMORY_ACCESS_UNIT_PROGRAM, &instr); if (error != APU_OK) { virtual_state.state = APU_STATE_STOPPED; return; } if (ignore_break == 0 && IsBreakpoint(APU_BPTYPE_PROGRAM, virtual_state.pc * MEMORY_ACCESS_UNIT_PROGRAM)) { /* program breakpoint hit */ virtual_state.state = APU_STATE_STOPPED; APU_Printf(context, "Program breakpoint 0x%08X hit", virtual_state.pc); return; } switch (instr >> 24) { case 0x00: /* NOP */ ++virtual_state.pc; break; case 0x01: /* JMP */ virtual_state.pc = instr & 0x00FFFFFF; break; case 0x02: /* INC */ addr = instr & 0x00FFFFFF; if (ignore_break == 0 && IsBreakpoint(APU_BPTYPE_READ | APU_BPTYPE_WRITE, addr * MEMORY_ACCESS_UNIT_DATA)) { /* data breakpoint hit */ virtual_state.state = APU_STATE_STOPPED; APU_Printf(context, "Data breakpoint 0x%08X hit", addr); } else { uint16_t data; error = ReadDataMemory(context, addr * MEMORY_ACCESS_UNIT_DATA, &data); if (error != APU_OK) { virtual_state.state = APU_STATE_STOPPED; break; } ++data; error = WriteDataMemory(context, addr * MEMORY_ACCESS_UNIT_DATA, data); if (error != APU_OK) { virtual_state.state = APU_STATE_STOPPED; break; } ++virtual_state.pc; } 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, "undef"); 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 * MEMORY_ACCESS_UNIT_PROGRAM; } 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; int error; switch (cbs->x.memory.flags) { case MEMORY_ACCESS_DATA: if (cbs->x.memory.address % MEMORY_ACCESS_UNIT_DATA != 0 || cbs->x.memory.length % MEMORY_ACCESS_UNIT_DATA != 0) { /* this should never happen */ APU_Printf(context, "unaligned data access to address 0x%08X, length 0x%08X", cbs->x.memory.address, cbs->x.memory.length); return APU_FAIL; } for (i = 0; i < cbs->x.memory.length; i += MEMORY_ACCESS_UNIT_DATA) { uint16_t data; error = ReadDataMemory(context, cbs->x.memory.address + i, &data); if (error != APU_OK) { ++errors; } else { cbs->x.memory.data[i + 0] = (uint8_t)(data >> 0); cbs->x.memory.data[i + 1] = (uint8_t)(data >> 8); } } break; case MEMORY_ACCESS_PROGRAM: if (cbs->x.memory.address % MEMORY_ACCESS_UNIT_PROGRAM != 0 || cbs->x.memory.length % MEMORY_ACCESS_UNIT_PROGRAM != 0) { /* this should never happen */ APU_Printf(context, "unaligned program access to address 0x%08X, length 0x%08X", cbs->x.memory.address, cbs->x.memory.length); return APU_FAIL; } for (i = 0; i < cbs->x.memory.length; i += MEMORY_ACCESS_UNIT_PROGRAM) { uint32_t data; error = ReadProgramMemory(context, cbs->x.memory.address + i, &data); if (error != APU_OK) { ++errors; } else { cbs->x.memory.data[i + 0] = (uint8_t)(data >> 0); cbs->x.memory.data[i + 1] = (uint8_t)(data >> 8); cbs->x.memory.data[i + 2] = (uint8_t)(data >> 16); cbs->x.memory.data[i + 3] = (uint8_t)(data >> 24); } } break; case MEMORY_ACCESS_USER: for (i = 0; i < cbs->x.memory.length; i += MEMORY_ACCESS_UNIT_USER) { uint8_t data; error = ReadUserMemory(context, cbs->x.memory.address + i, &data); if (error != APU_OK) { ++errors; } else { cbs->x.memory.data[i + 0] = (uint8_t)(data >> 0); } } break; default: /* this should never happen */ APU_Printf(context, "access to unknown access class %d", cbs->x.memory.flags); return APU_FAIL; } return errors == 0 ? APU_OK : APU_FAIL; } static int APUAPI CallbackMemoryWrite(apuContext context, apuCallbackStruct *cbs, apuPtr proprietary) { apuWord i; int errors = 0; int error; switch (cbs->x.memory.flags) { case MEMORY_ACCESS_DATA: if (cbs->x.memory.address % MEMORY_ACCESS_UNIT_DATA != 0 || cbs->x.memory.length % MEMORY_ACCESS_UNIT_DATA != 0) { /* this should never happen */ APU_Printf(context, "unaligned data access to address 0x%08X, length 0x%08X", cbs->x.memory.address, cbs->x.memory.length); return APU_FAIL; } for (i = 0; i < cbs->x.memory.length; i += MEMORY_ACCESS_UNIT_DATA) { uint16_t data; data = (uint16_t)cbs->x.memory.data[i + 0] << 0 | (uint16_t)cbs->x.memory.data[i + 1] << 8; error = WriteDataMemory(context, cbs->x.memory.address + i, data); if (error != APU_OK) { ++errors; } } break; case MEMORY_ACCESS_PROGRAM: if (cbs->x.memory.address % MEMORY_ACCESS_UNIT_PROGRAM != 0 || cbs->x.memory.length % MEMORY_ACCESS_UNIT_PROGRAM != 0) { /* this should never happen */ APU_Printf(context, "unaligned program access to address 0x%08X, length 0x%08X", cbs->x.memory.address, cbs->x.memory.length); return APU_FAIL; } for (i = 0; i < cbs->x.memory.length; i += MEMORY_ACCESS_UNIT_PROGRAM) { uint32_t data; data = (uint32_t)cbs->x.memory.data[i + 0] << 0 | (uint32_t)cbs->x.memory.data[i + 1] << 8 | (uint32_t)cbs->x.memory.data[i + 2] << 16 | (uint32_t)cbs->x.memory.data[i + 3] << 24; error = WriteProgramMemory(context, cbs->x.memory.address + i, data); if (error != APU_OK) { ++errors; } } break; case MEMORY_ACCESS_USER: for (i = 0; i < cbs->x.memory.length; i += MEMORY_ACCESS_UNIT_USER) { uint8_t data; data = (uint8_t)cbs->x.memory.data[i + 0]; error = WriteUserMemory(context, cbs->x.memory.address + i, data); if (error != APU_OK) { ++errors; } } break; default: /* this should never happen */ APU_Printf(context, "access to unknown access class %d", cbs->x.memory.flags); return APU_FAIL; } return errors == 0 ? APU_OK : APU_FAIL; } static int APUAPI CallbackTranslate(apuContext context, apuCallbackStruct *cbs, apuPtr proprietary) { /* Note: All addresses used in the APU API are byte addresses. Any scaling * of user-visible values due to the memory access unit is performed by * TRACE32. */ if (cbs->x.translate.flags == MEMORY_ACCESS_PROGRAM) { if (cbs->x.translate.direction == APU_TRANSLATE_TO_MAINCORE) { cbs->x.translate.address += MEMORY_BASE_ADDRESS_PROGRAM; } else { cbs->x.translate.address -= MEMORY_BASE_ADDRESS_PROGRAM; } } else if (cbs->x.translate.flags == MEMORY_ACCESS_DATA) { if (cbs->x.translate.direction == APU_TRANSLATE_TO_MAINCORE) { cbs->x.translate.address += MEMORY_BASE_ADDRESS_DATA; } else { cbs->x.translate.address -= MEMORY_BASE_ADDRESS_DATA; } } else { return APU_FAIL; } return APU_OK; } static int APUAPI CallbackStep(apuContext context, apuCallbackStruct *cbs, apuPtr proprietary) { VirtualStep(context, 1); return APU_OK; } int APUAPI APU_Init(apuContext context, apuCallbackStruct * cbs) { strcpy(cbs->x.init.modelname, __DATE__ " Virtual APU"); if (cbs->x.init.argc != 1) { APU_Warning(context, "parameters: none"); return APU_FAIL; } APU_DefineEndianess(context, APU_ENDIANNESS_LITTLE); APU_DefineMemory(context, MEMORY_ACCESS_DATA, "D", MEMORY_ACCESS_UNIT_DATA, MEMORY_ACCESS_UNIT_DATA); APU_DefineMemory(context, MEMORY_ACCESS_PROGRAM, "P", MEMORY_ACCESS_UNIT_PROGRAM, MEMORY_ACCESS_UNIT_PROGRAM); APU_DefineMemory(context, MEMORY_ACCESS_USER, "USR", MEMORY_ACCESS_UNIT_USER, MEMORY_ACCESS_UNIT_USER); 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_RegisterTranslateCallback(context, CallbackTranslate, NULL); APU_RegisterStepCallback(context, CallbackStep, NULL); return APU_OK; }