File Coverage

blib/lib/CPU/Emulator/DCPU16.pm
Criterion Covered Total %
statement 167 217 76.9
branch 51 86 59.3
condition 14 26 53.8
subroutine 29 43 67.4
pod 13 13 100.0
total 274 385 71.1


line stmt bran cond sub pod time code
1             package CPU::Emulator::DCPU16;
2              
3 4     4   55778 use strict;
  4         9  
  4         131  
4 4     4   21 use warnings;
  4         5  
  4         120  
5              
6 4     4   2323 use CPU::Emulator::DCPU16::Assembler;
  4         10  
  4         114  
7 4     4   1775 use CPU::Emulator::DCPU16::Disassembler;
  4         12  
  4         130  
8              
9 4     4   2370 use CPU::Emulator::DCPU16::Device::Console;
  4         13  
  4         12188  
10              
11             our $VERSION = 0.3;
12             our $MAX_REGISTERS = 8;
13             our $MAX_MEMORY = 65536; # 0x10000
14              
15             =head1 NAME
16              
17             CPU::Emulator::DCPU16 - an emulator for Notch's DCPU-16 virtual CPU for the game 0x10c
18              
19             =head1 SYNOPSIS
20              
21             open(my $fh, ">:raw", $file) || die "Couldn't read file $file: $!";
22             my $program = do { local $/=undef; <$fh> };
23             $program = CPU::Emulator::DCPU16::Assembler->assemble($program) if $file =~ /\.dasm(16)?$/;
24              
25             # Create a new CPU and load a file
26             my $cpu = CPU::Emulator::DCPU16->new();
27             $cpu->load($program);
28            
29             # Run it ...
30             $cpu->run;
31             # ... which is basically the same as
32             do { $cpu->step } until $cpu->halt;
33              
34             =head1 DESCRIPTION
35              
36             DCPU-16 is a spec for a virtual CPU by Notch from Mojang (of Minecraft fame).
37              
38             The spec is available here
39              
40             http://0x10c.com/doc/dcpu-16.txt
41              
42             =cut
43            
44            
45             =head1 METHODS
46              
47             =cut
48              
49             =head2 new
50              
51             Create a new CPU.
52              
53             =cut
54             sub new {
55 2     2 1 18 my $class = shift;
56 2         5 my %opts = @_;
57 2         10 return bless \%opts, $class;
58             }
59              
60             sub _init {
61 5     5   8 my $self = shift;
62 5         18 $self->halt = 0;
63 5         25 $self->pc = 0;
64 5         17 $self->sp = 0xffff;
65 5         14 $self->o = 0;
66            
67 5         11 $self->{_devices} = [];
68            
69             # TODO these could be done with scalars and bit masks
70 5         26189 $self->{_registers} = [(0x0000) x $MAX_REGISTERS],
71             $self->{_memory} = [(0x0000) x $MAX_MEMORY],
72            
73             }
74              
75             =head2 load [opt[s]]
76              
77             Load a program. Forces as re-init of the CPU.
78              
79             You can also do
80              
81             my $cpu = CPU::Emulator::DCPU16->load($program, %opts);
82            
83             which is exactly the same as
84              
85             my $cpu = CPU::Emulator::DCPU16->new(%opts);
86             $cpu->load($program);
87              
88             =cut
89             sub load {
90 5     5 1 13 my $self = shift;
91 5         7 my $bytes = shift;
92 5         12 my %opts = @_;
93 5 100       22 $self = $self->new(%opts) unless ref($self);
94 5         20 $self->_init;
95 5         5276 my @bytes = $self->bytes_to_array($bytes);
96 5 50       27 die "No program was loaded\n" unless @bytes;
97 5         22 $self->{_program_top} = scalar(@bytes);
98 5         9 splice(@{$self->{_memory}}, 0, scalar(@bytes), @bytes);
  5         39  
99 5         50 return $self;
100             }
101              
102             =head2 bytes_to_array
103              
104             Turn a scalar of bytes into an array of words
105              
106             =cut
107             sub bytes_to_array {
108 10     10 1 51 my $class = shift;
109 10         25 my $bytes = shift;
110 10         18 my @ret;
111 10         76 while (my $word = substr($bytes, 0, 2, '')) {
112 113         458 push @ret, ord($word) * 2**8 + ord(substr($word, 1, 1));
113             }
114 10         124 @ret;
115             }
116              
117             =head2 map_device [opt[s]]
118              
119             Map a device of the given class to these addresses
120              
121             =cut
122             sub map_device {
123 1     1 1 3 my $self = shift;
124 1         3 my $dev = shift;
125 1         3 my $start = shift;
126 1         3 my $end = shift;
127 1         3 my %opts = @_;
128 1         2 push @{$self->{_devices}}, $dev->new($self->{_memory}, $start, $end, %opts);
  1         20  
129 1         8 $self->{_devices}->[-1];
130             }
131              
132             =head2 run [opt[s]]
133              
134             Run CPU until completion.
135              
136             Options available are:
137              
138             =over 4
139              
140             =item debug
141              
142             Whether or not we should print debug information and at what level.
143              
144             Default is 0 (no debug output).
145              
146             =item limit
147              
148             Maxinum number of instructions to execute.
149              
150             Default is 0 (no limit).
151              
152             =item cycle_penalty
153              
154             The time penalty for each instruction cycle in milliseconds.
155              
156             Default is 0 (no penalty)
157              
158             =item full_memory
159              
160             Allow the PC to continue past the last instruction of the program (i.e the program_top).
161              
162             This would allow programs to rewrite themselves into a larger program.
163              
164             Default is 0 (no access)
165              
166             =back
167              
168             =cut
169             sub run {
170 5     5 1 10 my $self = shift;
171 5         16 my %opts = @_;
172 5         8 my $count = 1;
173 5   100     31 $opts{limit} ||= 0;
174 5   50     34 $opts{debug} ||= 0;
175 5 50       19 $self->_debug($self->_dump_header) if $opts{debug}>=1;
176            
177 5         9 do {
178 735         1910 $self->step(%opts);
179 735 100 100     2092 $self->halt = 1 if $opts{limit}>0 and ++$count>$opts{limit};
180 735 100 66     1398 $self->halt = 1 if $self->pc >= $self->program_top && !$opts{full_memory};
181             } until $self->halt;
182             }
183              
184             =head2 step [opt[s]]
185              
186             Run a single clock cycle of the CPU.
187              
188             Takes the same options as C.
189            
190             =cut
191             sub step {
192 735     735 1 1008 my $self = shift;
193 735         1539 my %opts = @_;
194            
195 735   50     2713 $opts{debug} ||= 0;
196 735   50     2453 $opts{cycle_penalty} ||= 0;
197 735 50       1437 $self->_debug($self->_dump_state) if $opts{debug}>=1;
198            
199 735         1353 my $pc = $self->pc;
200 735         1306 my $word = $self->memory($self->pc);
201 735 50       1528 die "Unknown memory at PC ".sprintf("0x%04x",$self->pc)."\n" unless defined $word;
202 735         791 my $op = $word & 0x0F;
203 735         1004 my $a = ($word >> 4) & 0x3f;
204 735         981 my $b = ($word >> 10) & 0x3f;
205              
206 735         1405 $self->pc += 1;
207 735         2403 $self->o = 0;
208            
209 735         769 my $cost = 0;
210            
211 735         1021 my $meth;
212             # Basic opcodes
213 735 100       1227 if ($op) {
    50          
214 734         1115 $meth = qw(NOOP _SET _ADD _SUB _MUL _DIV _MOD _SHL _SHR _AND _BOR _XOR _IFE _IFN _IFG _IFB)[$op];
215 734 50       1563 die "Illegal opcode $op\n" unless defined $meth;
216             # Defined non-basic opcodes
217             } elsif ($a == 0x01) {
218 1         2 $meth = "_JSR";
219             # Reserved non-basic opcodes
220             } else {
221 0         0 die "Illegal extended opcode $a\n";
222             }
223              
224 735         1413 my $aa = $self->_get_value($a, \$cost);
225 735         1455 my $bb = $self->_get_value($b, \$cost);
226            
227 735         1919 $self->$meth($aa, $bb, \$cost);
228 735 50       10115 select(undef, undef, undef, $cost*$opts{cycle_penalty}/1000) if $opts{cycle_penalty}>0;
229 735         778 $_->tick for @{$self->{_devices}};
  735         2954  
230 735         3760 return $cost;
231             }
232              
233             =head1 METHODS TO GET THE STATE OF THE CPU
234              
235             =head2 pc
236              
237             The current program counter.
238              
239             =cut
240             sub pc : lvalue {
241 3328     3328 1 3937 my $self = shift;
242 3328 50       6310 $self->{_pc} = shift if @_;
243 3328         7193 $self->{_pc};
244             }
245              
246             =head2 sp
247              
248             The current stack pointer.
249              
250             =cut
251             sub sp : lvalue {
252 7     7 1 11 my $self = shift;
253 7 50       15 $self->{_sp} = shift if @_;
254 7         16 $self->{_sp};
255             }
256              
257             =head2 o
258              
259             The current overflow.
260              
261             =cut
262             sub o : lvalue {
263 1008     1008 1 1206 my $self = shift;
264 1008 50       1764 $self->{_o} = shift if @_;
265 1008         1796 $self->{_o};
266             }
267              
268             =head2 halt [halt state]
269              
270             Halt the CPU or check to see whether it's halted.
271              
272             =cut
273             sub halt : lvalue {
274 745     745 1 1097 my $self = shift;
275 745 50       1515 $self->{_halt} = shift if @_;
276 745         2472 $self->{_halt};
277             }
278              
279             =head2 program_top
280              
281             The address of the first memory location after the loaded program.
282              
283             =cut
284             sub program_top : lvalue {
285 735     735 1 800 my $self = shift;
286 735 50       1411 $self->{_program_top} = shift if @_;
287 735         3599 $self->{_program_top};
288             }
289              
290             =head2 register
291              
292             Get or set the value of a register.
293              
294             =cut
295             sub register : lvalue {
296 110     110 1 133 my $self = shift;
297 110 50       283 return $self->{_registers} unless @_;
298 110 50 33     112 my $loc = shift; die "Invalid register $loc at pc ".$self->pc." (".sprintf("%02x", $self->pc).")\n" if $loc<0 || $loc>=$MAX_REGISTERS;
  110         437  
299 110 50       209 $self->{_registers}[$loc] = shift if @_;
300 110         483 $self->{_registers}[$loc];
301             }
302             # TODO ugly
303             sub _reg_ref {
304 794     794   866 my $self = shift;
305 794 50 33     800 my $loc = shift; die "Invalid register $loc at pc ".$self->pc." (".sprintf("%02x", $self->pc).")\n" if $loc<0 || $loc>=$MAX_REGISTERS;
  794         3269  
306 794         2033 \($self->{_registers}[$loc]);
307             }
308              
309             =head2 memory
310              
311             Get or set the value of a memory location.
312              
313             =cut
314             sub memory : lvalue {
315 755     755 1 1095 my $self = shift;
316 755 50       1372 return $self->{_memory} unless @_;
317 755         797 my $loc = shift;
318 755 50 33     5838 die "Invalid memory $loc at pc ".$self->pc." (".sprintf("%02x", $self->pc).")\n" if $loc<0 || $loc>=$MAX_MEMORY;
319 755 50       2194 $self->{_memory}[$loc] = shift if @_;
320 755         1746 $self->{_memory}[$loc];
321             }
322             # TODO ugly
323             sub _mem_ref {
324 464     464   568 my $self = shift;
325 464         454 my $loc = shift;
326 464 50 33     1976 die "Invalid memory $loc at pc ".$self->pc." (".sprintf("%02x", $self->pc).")\n" if $loc<0 || $loc>=$MAX_MEMORY;
327 464         1101 \($self->{_memory}[$loc]);
328             }
329              
330             sub _dump_header {
331 0     0   0 "PC SP OV A B C X Y Z I J Instruction\n".
332             "---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- -----------";
333             }
334              
335             sub _dump_state {
336 0     0   0 my $self = shift;
337 0         0 sprintf("%04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %s",
338             $self->pc, $self->sp, $self->o,
339             $self->register(0), $self->register(1), $self->register(2), $self->register(3),
340             $self->register(4), $self->register(5), $self->register(6), $self->register(7),
341 0         0 CPU::Emulator::DCPU16::Disassembler->disassemble($self->pc, @{$self->memory}));
342             }
343              
344             sub _debug {
345 0     0   0 my $self = shift;
346 0         0 my $mess = shift;
347 0         0 print "$mess\n";
348             }
349              
350             sub _get_value {
351 1470     1470   8271 my $self = shift;
352 1470         1839 my $value = shift;
353 1470         1676 my $cost = shift;
354 1470         9777 my $ret;
355 1470 100       5418 if ($value < 0x08) {
    100          
    100          
    100          
    50          
    50          
    50          
    100          
    50          
    100          
    100          
356 794         1538 $ret = $self->_reg_ref($value);
357             } elsif ($value < 0x10) {
358 94         287 $ret = $self->_mem_ref($self->register($value & 7));
359             } elsif ($value < 0x18) {
360 10         12 $$cost += 1;
361 10         15 my $next = $self->memory($self->pc++);
362 10         24 $ret = $self->_mem_ref($next + $self->register($value & 7) & 0xffff);
363             } elsif ($value == 0x18) {
364 1         3 $ret = $self->_mem_ref($self->sp++);
365             } elsif ($value == 0x19) {
366 0         0 $ret = $self->_mem_ref($self->sp);
367             } elsif ($value == 0x1A) {
368 0         0 $ret = $self->_mem_ref($self->sp--);
369             } elsif ($value == 0x1B) {
370 0         0 $ret = \($self->{_sp});
371             } elsif ($value == 0x1C) {
372 97         153 $ret = \($self->{_pc});
373             } elsif ($value == 0x1D) {
374 0         0 $ret = \($self->{_o});
375             } elsif ($value == 0x1E) {
376 4         7 $$cost += 1;
377 4         10 $ret = $self->_mem_ref($self->memory($self->pc++));
378             } elsif ($value == 0x1F) {
379 355         424 $$cost += 1;
380 355         674 $ret = $self->_mem_ref($self->pc++);
381             } else {
382 115         144 $ret = ($value - 0x20)
383             }
384 1470 100       3978 return ref($ret) ? $ret : \$ret;
385             }
386              
387             our %_skiptable = (0x10 => 1, 0x11 => 1, 0x12 => 1, 0x13 => 1, 0x14 => 1, 0x15 => 1, 0x1E => 1, 0x1F => 1);
388             sub _skip {
389 4     4   10 my $self = shift;
390 4         12 my $cost = shift;
391 4         6 $$cost++;
392 4         20 my $op = $self->memory($self->pc++);
393 4         13 $self->pc += $_skiptable{$op >> 10};
394 4 50       19 $self->pc += $_skiptable{($op >> 4) & 31} if (($op & 0x0F) == 0);
395             }
396              
397 0     0   0 sub _NOOP {
398             # Just what it says on the tin
399             }
400              
401             sub _JSR {
402 1     1   2 my ($self, $a, $b, $cost) = @_;
403 1         2 $$cost += 2;
404 1         4 $self->memory(--$self->sp) = $self->pc;
405 1         3 $self->pc = $$b;
406              
407             }
408              
409             # 0x1: SET a, b - sets a to b
410             sub _SET {
411 368     368   547 my ($self, $a, $b, $cost) = @_;
412 368         409 $$cost += 1;
413 368         771 $$a = $$b;
414             }
415              
416             # 0x2: ADD a, b - sets a to a+b, sets O to 0x0001 if there's an overflow, 0x0 otherwise
417             sub _ADD {
418 255     255   357 my ($self, $a, $b, $cost) = @_;
419 255         307 $$cost += 2;
420 255         271 $$a += $$b;
421 255         747 $self->o = $$a >> 16;
422             }
423              
424             # 0x3: SUB a, b - sets a to a-b, sets O to 0xffff if there's an underflow, 0x0 otherwise
425             sub _SUB {
426 12     12   13 my ($self, $a, $b, $cost) = @_;
427 12         13 $$cost += 2;
428 12         16 $$a -= $$b;
429 12         23 $self->o = $$a >> 16;
430             }
431              
432             # 0x4: MUL a, b - sets a to a*b, sets O to ((a*b)>>16)&0xffff
433             sub _MUL {
434 0     0   0 my ($self, $a, $b, $cost) = @_;
435 0         0 $$cost += 2;
436 0         0 $$a *= $$b;
437 0         0 $self->o = $$a >> 16;
438             }
439              
440             # 0x5: DIV a, b - sets a to a/b, sets O to ((a<<16)/b)&0xffff. if b==0, sets a and O to 0 instead.
441             sub _DIV {
442 0     0   0 my ($self, $a, $b, $cost) = @_;
443 0         0 $$cost += 3;
444 0 0       0 if ($$b) {
445 0         0 $$a /= $$b;
446             } else {
447 0         0 $$a = 0;
448             }
449 0         0 $self->o = $$a >> 16;
450             }
451              
452             # 0x6: MOD a, b - sets a to a%b. if b==0, sets a to 0 instead.
453             sub _MOD {
454 0     0   0 my ($self, $a, $b, $cost) = @_;
455 0         0 $$cost += 3;
456 0 0       0 if ($$b) {
457 0         0 $$a %= $$b;
458             } else {
459 0         0 $$a = 0;
460             }
461             }
462              
463             # 0x7: SHL a, b - sets a to a<>16)&0xffff
464             sub _SHL {
465 1     1   2 my ($self, $a, $b, $cost) = @_;
466 1         2 $$cost += 2;
467 1         2 $$a <<= $$b;
468 1         2 $self->o = $$a >> 16;
469             }
470              
471             # 0x8: SHR a, b - sets a to a>>b, sets O to ((a<<16)>>b)&0xffff
472             sub _SHR {
473 0     0   0 my ($self, $a, $b, $cost) = @_;
474 0         0 $$cost += 2;
475 0         0 $$a >>= $$b;
476 0         0 $self->o = $$a >> 16;
477             }
478              
479             # 0x9: AND a, b - sets a to a&b
480             sub _AND {
481 0     0   0 my ($self, $a, $b, $cost) = @_;
482 0         0 $$cost += 1;
483 0         0 $$a &= $$b;
484             }
485              
486             # 0xa: BOR a, b - sets a to a|b
487             sub _BOR {
488 0     0   0 my ($self, $a, $b, $cost) = @_;
489 0         0 $$cost += 1;
490 0         0 $$a |= $b;
491             }
492              
493             # 0xb: XOR a, b - sets a to a^b
494             sub _XOR {
495 0     0   0 my ($self, $a, $b, $cost) = @_;
496 0         0 $$cost += 1;
497 0         0 $$a ^= $b;
498             }
499              
500             # 0xc: IFE a, b - performs next instruction only if a==b
501             sub _IFE {
502 0     0   0 my ($self, $a, $b, $cost) = @_;
503 0         0 $$cost += 2;
504 0 0       0 $self->_skip($cost) unless $$a+0 == $$b+0;
505             }
506              
507             # 0xd: IFN a, b - performs next instruction only if a!=b
508             sub _IFN {
509 98     98   140 my ($self, $a, $b, $cost) = @_;
510 98         124 $$cost += 2;
511 98 100       314 $self->_skip($cost) unless $$a+0 != $$b+0;
512             }
513              
514             # 0xe: IFG a, b - performs next instruction only if a>b
515             sub _IFG {
516 0     0     my ($self, $a, $b, $cost) = @_;
517 0           $$cost += 2;
518 0 0         $self->_skip($cost) unless $$a+0 > $$b+0;
519             }
520              
521             # 0xf: IFB a, b - performs next instruction only if (a&b)!=0
522             sub _IFB {
523 0     0     my ($self, $a, $b, $cost) = @_;
524 0           $$cost += 2;
525 0 0         $self->_skip($cost) unless ($$a+0 & $$b+0) != 0;
526             }
527              
528              
529             =head1 SEE ALSO
530              
531             L
532              
533             L
534              
535             =head1 ACKNOWLEDGEMENTS
536              
537             Implementation inspiration came from:
538              
539             =over 4
540              
541             =item Matt Bell's Javascript implementation (https://github.com/mappum/DCPU-16)
542              
543             =item Brian Swetland's C Implementation (https://github.com/swetland/dcpu16)
544              
545             =item Jesse Luehrs's Perl Implementation (https://github.com/doy/games-emulation-dcpu16)
546              
547             =back
548              
549             =head1 AUTHOR
550              
551             Simon Wistow
552              
553             =head1 COPYRIGHT
554              
555             Copyright 2011 - Simon Wistow
556              
557             Released under the same terms as Perl itself.
558              
559             =head1 DEVELOPMENT
560              
561             Latest development version available from
562              
563             https://github.com/simonwistow/CPU-Emulator-DCPU16
564              
565             =cut
566              
567             1;