Thursday 20 November 2008

It's Refactor Time !

Well the debugger seems to be working great but I've become less happy about the structure of the code especially after strapping the GUI onto the emulation core and realising a short coming in the memory controller code. So, I'm taking stock and having a bit of a refactor before the project grows much larger.

What I'd missed in the memory controllers was the ability to bypass any side effects of reading or writing to a particular location. There are several places where custom chips react to a read or a write, and if I want to present the contents of memory through a debugger I have to be able to make these memory accesses transparent.

At the moment I manage memory by mapping the Amiga's memory to a simple array with 256 elements. I assign a specific "controller" instance to each "area" of memory. Using this method I can have separate classes that handle chip memory, CIA controllers, the custom chip set registers, reserved memory areas, ROM and so on. Each of the controllers implements a MemoryController interface that looks like this:


public interface MemoryController
{
void reset();
byte peekByte(int address);
short peekWord(int address);
int peekLong(int address);
void pokeByte(int address, byte value);
void pokeWord(int address, short value);
void pokeLong(int address, int value);
int peek(int address, DataSize size);
void poke(int address, int value, DataSize size);
}


As you can see though there is nothing that will let me access the memory in a transparent manner. I may need to add a few more methods such as debugPeekByte(...) or something similar to give me that functionality.

Memory accesses are handled by masking and shifting the requested address to give me an index into the memory map array and retrieving the appropriate controller. The request is then handed off to the controller to manage. This is all marshalled and encapsulated by a MemoryManager class which itself also implements the MemoryController interface. It's quite neat and is much nicer than a big switch statement or massive if/else if/else construct and no doubt a bit speedier too.

I'm also thinking I'm going to change my (not quite completed) breakpoint handling method with something simpler. At the moment I inject a special opcode at the address of the breakpoint and store the address in a map with the original opcode I replaced. The special Breakpoint instruction passes control to the CPU when invoked and also looks up the original code if disassembly is required. It's a bit ugly in it's implementation though and involves calling back and forth into several classes to determine the outcome, and of course it modifies code in memory. I think that it's waiting to bite me in the arse at a later point.

I think a better solution would be to handle the whole instruction execution cycle from a different method if we're running the debugger. This method could compare the current address to a list of breakpoints and handle them higher up the stack as it were. The overhead of doing this would only be while we were debugging. The "normal" execution loop wouldn't have the breakpoint checks in it.

Lots to do!

1 comment:

Eight-Bit Guru said...

"I think a better solution would be to handle the whole instruction execution cycle from a different method if we're running the debugger."


Bingo! You nailed it - the ONLY way to do breakpoint-checking without slowing down the main loop is to have a fully-separated clone loop -with- the check in that runs in debug mode only. Then the 'real' execution core runs at full speed in normal mode.

Incidentally, my MMU logic does a similar thing to what you propose - except that the array itself has delegate hooks out to 'special' handlers (like VIC/VIA/CIA) that only fire when reads and/or writes occur to those specific locations...