Another week, another release. This time I've totally revamped the code for the instructions. It's been simplified to the point where it actually makes sense at a glance. For example, DEY (decrement the Y register by 1) looked like this:
my $self = shift; my $reg = $self->registers; $reg->{ y } = ( $reg->{ y } - 1 ) & 0xff; $reg->{ status } &= CPU::Emulator::6502::CLEAR_SIGN; $reg->{ status } &= CPU::Emulator::6502::CLEAR_ZERO; $reg->{ status } |= CPU::Emulator::6502::SET_ZERO if !$reg->{ y }; $reg->{ status } |= CPU::Emulator::6502::SET_SIGN if $reg->{ y } & 0x80; $reg->{ pc }++;
There's a lot of junk in there for setting the N and Z flags and incrementing the program counter. It now looks like this:
my $self = shift; my $reg = $self->registers; $reg->{ y } = ( $reg->{ y } - 1 ) & 0xff; $self->set_nz( $reg->{ y } );
Much nicer.
I've also eliminated any cycle counter code from the instructions. Rather than adding 2 cycles for each instruction, then adding the remainder in the instruction itself, I've simply added the total number of cycles to the metadata for each instruction. The absolute addressing mode for the EOR instruction (exclusive or between the accumulator and memory contents) is setup like so:
0x4D => { addressing => 'absolute', cycles => 4, code => \&eor, },
I plan to add some unit tests for the addressing modes and instructions for the next release.
Other than that, I have a TODO to re-work the CPU read() and write() code. It is currently a twisty maze of if {} elsif {} statements due to the fact that NES uses memory-mapped IO, I think this could be better written as a hash table of subs that will delegate to the PPU and APU as needed. Last on that list is the need to remove all of the swapping code in the base mapper. That process can happen many times per frame and will seriously slow down the emulator -- instead I can just use a list of pointers to mimic the swap. That refactoring will probably have to wait until i get some actual PPU functionality written, though.