--- M6502/M6502.pm 2007/07/30 17:56:13 30 +++ M6502/M6502.pm 2007/08/02 12:23:18 90 @@ -5,16 +5,21 @@ use Data::Dump qw/dump/; use Carp qw/confess/; +use Exporter 'import'; +our @EXPORT = qw'dump_R @mem $PC $A $P $X $Y $S $IPeriod $ICount $IRequest $IAutoReset $TrapBadOps $Trap $Trace $run_for $debug'; +our $VERSION = '0.0.2'; +require XSLoader; +XSLoader::load('M6502', $VERSION); =head1 NAME -M6502 - perl bindings for 6502 emulator +M6502 - perl bindings for M6502 CPU emulator -=cut +=head1 FUNCTIONS -my $debug = 1; +=cut -our $VERSION = qw(0.0.1); +our $debug = 0; our @mem = (0xff) x 0x10000; # 64M @@ -22,82 +27,205 @@ our $PC = 0xbeef; # CPU registars our ( $A, $P, $X, $Y, $S ) = (0) x 5; -# Set IPeriod to number of CPU cycles between calls to Loop6502 -our $IPeriod = 1; - -=head1 init -Called before C +our $IPeriod=1; # Set IPeriod to number of CPU cycles between calls to Loop6502 +our $ICount; +our $IRequest; # Set to the INT_IRQ when pending IRQ +our $IAutoReset; # Set to 1 to autom. reset IRequest +our $TrapBadOps=1; # Set to 1 to warn of illegal opcodes +our $Trap; # Set Trap to address to trace from +our $Trace; # Set Trace=1 to start tracing + +# Exec6502 cycles +our $run_for = 0; + +=head2 init + +Setup read and write memory hooks (to implement memory mapped devices) + + $init->( + read => sub { + return $mem[$_[0]]; + }, + write => sub { + $mem[$_[0]] = $_[1]; + }, + ); =cut +our $_rw_hooks = { + read => sub { + return $mem[$_[0]]; + }, + write => sub { + $mem[$_[0]] = $_[1]; + }, +}; + sub init { - warn "inside init\n"; - print "stdout\n"; + my $self = shift; + my $args = {@_}; + warn "inside init low-level M6502 from ",ref($self),"\n"; + + foreach my $p ( qw/read write/ ) { + confess "need $p argument as coderef" unless ( $args->{$p} && ref($args->{$p}) eq 'CODE' ); + $_rw_hooks->{$p} = $args->{$p}; + } + }; -=head2 read +=head2 poke_code -Read from memory +Write series of bytes into memory passing through MMU (C and C) +functions. If you don't want to trigger MMU, use C. - $byte = read( $address ); + $emu->poke_code( 0xbeef, 0xff, 0x00, 0xff, 0x00, 0xaa ); =cut -sub read { - my ($addr) = @_; - my $byte = $mem[$addr]; - warn "# read(",dump(@_),") = ",dump( $byte ),"\n" if $debug; - return $byte; +sub poke_code { + my $self = shift; + my $addr = shift; + warn sprintf("## M6502::poke_code(%04x,%s)\n", $addr, dump( @_ )) if $self->debug; + #$mem[$addr++] = $_ foreach @_; + # call low-level write + $_rw_hooks->{write}->( $addr++, $_ ) foreach @_; } -=head2 write +=head2 ram -Write into emory +Read series of bytes into memory without MMU interaction - write( $address, $byte ); + my @code = $emu->ram( 0xc000, 0xc1000 ); =cut -sub write { - warn "# write(",dump(@_),")\n" if $debug; - my ($addr,$byte) = @_; - $mem[$addr] = $byte; +sub ram { + my $self = shift; + my ( $from, $to ) = @_; + warn sprintf("## M6502::ram(%04x,%04x)\n", $from, $to) if $self->debug; + return @mem[ $from .. $to ]; } -=head2 poke_code +=head2 write_chunk -Write series of bytes into memory without passing through MMU +Low-level update of memory, overriding user specified MMU functions C and C - $emu->poke_code( 0xbeef, 0xff, 0x00, 0xff, 0x00, 0xaa ); + $emu->write_chunk( $address, $chunk_of_data ); =cut -sub poke_code { - my $self = shift; - my $addr = shift; - warn sprintf("# poke_code(%04x,%s)\n", $addr, dump( @_ )) if $self->debug; - $mem[$addr++] = $_ foreach @_; +sub write_chunk { + my ($self, $addr, $chunk) = @_; + my $len = length($chunk); + splice @mem, $addr, $len, unpack('C*', $chunk); } -=head2 ram +=head2 prompt + +Call this after C<< $run_for >> cycles have been run on processor -Read searies of bytes from memory without passing through MMU +=cut - $emu->ram( $from, $to ); +sub prompt { + warn "prompt -- you should override this\n"; + return 1; +} + +=head1 XS Callbacks + +This functions are called from C + +=head2 _read + +Read from memory C callback + + $byte = M6502::_read( $address ); =cut -sub ram { +sub _read { + return $_rw_hooks->{read}->( @_ ); +} + +=head2 _write + +Write into memory C callback + + M6502:_write( $address, $byte ); + +=cut + +sub _write { + return $_rw_hooks->{write}->( @_ ); +} + +=head2 _update_perl_R + +called by C to push changes in registars back to perl variables + +=cut + +sub _update_perl_R { + warn "## M6502::update_perl_R(",dump(@_),")\n" if $debug; + ( $A, $P, $X, $Y, $S, $PC, $IPeriod, $ICount, $IRequest, $IAutoReset, $TrapBadOps, $Trap, $Trace ) = @_; + dump_R(); +} + +=head2 dump_R + +helper function which dumps registers in humanly readable form + + my $dump = dump_R; + +=cut + +sub dump_R { + my $dump = sprintf(" PC: %04x A:%02x P:%02x X:%02x Y:%02x S:%02x " + . "IPeriod:%d ICount:%d IRequest:%02x IAutoReset:%02x TrapBadOps:%d Trap:%d Trace:%d" + . "\n", + $PC, $A, $P, $X, $Y, $S, $IPeriod, $ICount, $IRequest, $IAutoReset, $TrapBadOps, $Trap, $Trace, + ); + warn "## M6502::dump_R $dump" if $debug; + return $dump; +} + +=head2 debug + +Turn perl and C-level debugging on/off + + $emu->debug( 0 ); + $emu->debug( 1 ); + print $emu->debug; + +=cut + +sub debug { my $self = shift; - my ($from,$to) = @_; - if ($from + $to) { - printf "ram %04x - %04x\n", $from, $to; - return $mem[$from .. $to - 1]; + my $value = shift; + if (defined($value)) { + $debug = M6502::set_debug($value); + } else { + $debug = M6502::get_debug(); } - printf "ram %04x\n", $from; - return $mem[$from] if defined($from); - confess "no from address"; + return $debug; } +=head1 SEE ALSO + +L is sample implementation using this module + +=head1 AUTHOR + +Dobrica Pavlinusic, C<< >> + +=head1 COPYRIGHT & LICENSE + +Copyright 2007 Dobrica Pavlinusic, All Rights Reserved. + +This program is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=cut 1;