package Games::Emulation::DCPU16; use strict; use warnings; use integer; use constant { STATE_NEW_OP => 0, STATE_READ_ARG_1 => 1, STATE_READ_ARG_2 => 2, STATE_OP_EXECUTE => 3, TIME_TAKEN_NONE => 0, TIME_TAKEN_DELAY => 1, TIME_TAKEN_WORK => 2, }; sub new { my $class = shift; bless { memory => [(0x0000) x 0x10000], registers => [(0x0000) x 8], PC => 0x0000, SP => 0x0000, O => 0x0000, clock => 0, halt => undef, delay => 0, time_taken => TIME_TAKEN_NONE, state => STATE_NEW_OP, has_delayed => undef, current_op => undef, lvalue => undef, value1 => undef, value2 => undef, next_word => undef, pc_offset => 0, jumped => undef, basic_opcode_map => [ undef, '_op_SET', '_op_ADD', '_op_SUB', '_op_MUL', '_op_DIV', '_op_MOD', '_op_SHL', '_op_SHR', '_op_AND', '_op_BOR', '_op_XOR', '_op_IFE', '_op_IFN', '_op_IFG', '_op_IFB', ], non_basic_opcode_map => [ undef, '_op_JSR', (undef) x (0x3f - 0x02 - 1), '_op_HLT', # XXX extension ], }, $class; } sub memory { shift->{memory} } sub registers { shift->{registers} } sub PC { shift->{PC} } sub SP { shift->{SP} } sub O { shift->{O} } sub clock { shift->{clock} } sub load { my $self = shift; my ($bytecode) = @_; my $idx = 0; while (my $word = substr($bytecode, 0, 2, '')) { $self->{memory}[$idx++] = ord($word) * 2**8 + ord(substr($word, 1, 1)); } } sub run { my $self = shift; $self->step until $self->{halt}; } sub step { my $self = shift; $self->{clock}++; if ($self->{delay}) { $self->{delay}--; return; } $self->{time_taken} = TIME_TAKEN_NONE; while (1) { my $state = $self->{state}; if ($state == STATE_NEW_OP) { $self->{state} = $self->_parse_op($self->{memory}[$self->{PC}]); $self->{pc_offset} = 1; undef $self->{jumped}; } elsif ($state == STATE_READ_ARG_1) { $self->{state} = $self->_parse_value($self->{value1}) } elsif ($state == STATE_READ_ARG_2) { $self->{state} = $self->_parse_value($self->{value2}) } elsif ($state == STATE_OP_EXECUTE) { $self->{state} = $self->_execute_current_op; } else { die "Invalid state"; } last if $self->{time_taken} != TIME_TAKEN_NONE; } } # XXX this duplicates a bit from _parse_value sub _op_length { my $self = shift; my $length = 1; $self->_parse_op($self->{memory}[$self->{PC} + $self->{pc_offset}]); for my $value ($self->{value1}, $self->{value2}) { next unless defined $value; $length++ if $value >= 0x10 && $value < 0x18; $length++ if $value >= 0x1e && $value < 0x20; } return $length; } sub _parse_op { my $self = shift; my ($opcode) = @_; my $basic_op = $opcode & 0x0f; if ($basic_op) { $self->{value1} = ($opcode >> 4) & 0x3f; $self->{value2} = ($opcode >> 10) & 0x3f; $self->{current_op} = $self->{basic_opcode_map}[$basic_op]; } else { my $non_basic_op = ($opcode >> 4) & 0x3f; $self->{value1} = ($opcode >> 10) & 0x3f; $self->{value2} = undef; $self->{current_op} = $self->{non_basic_opcode_map}[$non_basic_op]; } die "Illegal opcode" unless $self->{current_op}; return STATE_READ_ARG_1; } sub _parse_value { my $self = shift; my ($value) = @_; my $state = $self->{state}; my $key = $state == STATE_READ_ARG_1 ? 'value1' : 'value2'; if ($value < 0x08) { $self->{$key} = $self->{registers}[$value]; $self->{lvalue} = \$self->{registers}[$value] if $state == STATE_READ_ARG_1; } elsif ($value < 0x10) { my $addr = $self->{registers}[$value & 0x07]; $self->{$key} = $self->{memory}[$addr]; } elsif ($value < 0x18) { $self->_next_word; return $state if $self->{time_taken} != TIME_TAKEN_WORK; my $addr = $self->{registers}[$value & 0x07] + $self->{next_word}; $self->{$key} = $self->{memory}[$addr]; $self->{lvalue} = \$self->{memory}[$addr] if $state == STATE_READ_ARG_1; } elsif ($value > 0x1f) { die "Illegal value" unless $value <= 0x3f; $self->{$key} = $value - 0x20; } elsif ($value == 0x18) { $self->{$key} = $self->{memory}[($self->{SP}++ & 0xffff)]; $self->{SP} &= 0xffff; } elsif ($value == 0x19) { $self->{$key} = $self->{memory}[$self->{SP}]; } elsif ($value == 0x1a) { $self->{$key} = $self->{memory}[(--$self->{SP} & 0xffff)]; $self->{SP} &= 0xffff; } elsif ($value == 0x1b) { $self->{$key} = $self->{SP}; $self->{lvalue} = \$self->{SP} if $state == STATE_READ_ARG_1; } elsif ($value == 0x1c) { $self->{$key} = $self->{PC}; $self->{lvalue} = \$self->{PC} if $state == STATE_READ_ARG_1; } elsif ($value == 0x1d) { $self->{$key} = $self->{O}; } elsif ($value == 0x1e) { $self->_next_word; return $state if $self->{time_taken} != TIME_TAKEN_WORK; my $addr = $self->{next_word}; $self->{$key} = $self->{memory}[$addr]; $self->{lvalue} = \$self->{memory}[$addr] if $state == STATE_READ_ARG_1; } elsif ($value == 0x1f) { $self->_next_word; return $state if $self->{time_taken} != TIME_TAKEN_WORK; $self->{$key} = $self->{next_word}; } return $state == STATE_READ_ARG_2 || !defined($self->{value2}) ? STATE_OP_EXECUTE : STATE_READ_ARG_2; } sub _execute_current_op { my $self = shift; my $op_meth = $self->{current_op}; $self->$op_meth($self->{value1}, $self->{value2}); return $self->{state} if $self->{time_taken} != TIME_TAKEN_WORK; $self->{PC} += $self->{pc_offset} unless $self->{jumped}; $self->{lvalue} = undef; return STATE_NEW_OP; } sub _next_word { my $self = shift; undef $self->{next_word}; return 1 if $self->_delay(1); $self->{next_word} = $self->{memory}[$self->{PC} + $self->{pc_offset}]; $self->{pc_offset}++; return; } sub _delay { my $self = shift; my ($delay) = @_; return unless $delay; $delay--; if (!$delay || $self->{has_delayed}) { $self->{time_taken} = TIME_TAKEN_WORK; undef $self->{has_delayed}; return; } else { $self->{time_taken} = TIME_TAKEN_DELAY; $self->{delay} = $delay - 1; $self->{has_delayed} = 1; return 1; } } sub _op_SET { my $self = shift; my ($a, $b) = @_; return if $self->_delay(1); my $lvalue = $self->{lvalue}; return unless $lvalue; $self->{jumped} = 1 if $lvalue == \$self->{PC}; ${ $self->{lvalue} } = $b; } sub _op_ADD { my $self = shift; my ($a, $b) = @_; return if $self->_delay(2); my $lvalue = $self->{lvalue}; return unless $lvalue; $$lvalue = $a + $b; $self->{O} = $$lvalue >> 16; $$lvalue &= 0xffff; } sub _op_SUB { my $self = shift; my ($a, $b) = @_; return if $self->_delay(2); my $lvalue = $self->{lvalue}; return unless $lvalue; $$lvalue = $a - $b; $self->{O} = $$lvalue >> 16; $$lvalue &= 0xffff; } sub _op_MUL { my $self = shift; my ($a, $b) = @_; return if $self->_delay(2); my $lvalue = $self->{lvalue}; return unless $lvalue; $$lvalue = $a * $b; $self->{O} = $$lvalue >> 16; $$lvalue &= 0xffff; } sub _op_DIV { my $self = shift; my ($a, $b) = @_; return if $self->_delay(3); my $lvalue = $self->{lvalue}; return unless $lvalue; if ($b == 0) { $$lvalue = 0; $self->{O} = 0; } else { $$lvalue = $a / $b; $self->{O} = (($a << 16) / $b) & 0xffff; $$lvalue &= 0xffff; } } sub _op_MOD { my $self = shift; my ($a, $b) = @_; return if $self->_delay(3); my $lvalue = $self->{lvalue}; return unless $lvalue; if ($b == 0) { $$lvalue = 0; } else { $$lvalue = ($a % $b) & 0xffff; } } sub _op_SHL { my $self = shift; my ($a, $b) = @_; return if $self->_delay(2); my $lvalue = $self->{lvalue}; return unless $lvalue; $$lvalue = $a << $b; $self->{O} = $$lvalue >> 16; $$lvalue &= 0xffff; } sub _op_SHR { my $self = shift; my ($a, $b) = @_; return if $self->_delay(2); my $lvalue = $self->{lvalue}; return unless $lvalue; $$lvalue = $a >> $b; $self->{O} = (($a << 16) >> $b) & 0xffff; $$lvalue &= 0xffff; } sub _op_AND { my $self = shift; my ($a, $b) = @_; return if $self->_delay(1); my $lvalue = $self->{lvalue}; return unless $lvalue; $$lvalue = $a & $b; } sub _op_BOR { my $self = shift; my ($a, $b) = @_; return if $self->_delay(1); my $lvalue = $self->{lvalue}; return unless $lvalue; $$lvalue = $a | $b; } sub _op_XOR { my $self = shift; my ($a, $b) = @_; return if $self->_delay(1); my $lvalue = $self->{lvalue}; return unless $lvalue; $$lvalue = $a ^ $b; } sub _op_IFE { my $self = shift; my ($a, $b) = @_; if ($a == $b) { return if $self->_delay(2); } else { return if $self->_delay(3); $self->{pc_offset} += $self->_op_length; } } sub _op_IFN { my $self = shift; my ($a, $b) = @_; if ($a != $b) { return if $self->_delay(2); } else { return if $self->_delay(3); $self->{pc_offset} += $self->_op_length; } } sub _op_IFG { my $self = shift; my ($a, $b) = @_; if ($a > $b) { return if $self->_delay(2); } else { return if $self->_delay(3); $self->{pc_offset} += $self->_op_length; } } sub _op_IFB { my $self = shift; my ($a, $b) = @_; if ($a & $b) { return if $self->_delay(2); } else { return if $self->_delay(3); $self->{pc_offset} += $self->_op_length; } } sub _op_JSR { my $self = shift; my ($a) = @_; return if $self->_delay(2); $self->{memory}[(--$self->{SP} & 0xffff)] = $self->{PC} + $self->{pc_offset}; $self->{SP} &= 0xffff; $self->{PC} = $a; $self->{jumped} = 1; } sub _op_HLT { my $self = shift; my ($a) = @_; $self->{halt} = 1; } =for notes what happens when an invalid op is read? =cut 1;