File Coverage

lib/Hardware/Simulator/MIX.pm
Criterion Covered Total %
statement 341 736 46.3
branch 72 236 30.5
condition 16 63 25.4
subroutine 44 79 55.7
pod 6 74 8.1
total 479 1188 40.3


line stmt bran cond sub pod time code
1             package Hardware::Simulator::MIX;
2              
3 8     8   206809 use Exporter;
  8         21  
  8         743  
4              
5             @ISA = qw(Exporter);
6              
7             @EXPORT = qw(new reset step mix_char mix_char_code get_overflow
8             is_halted read_mem write_mem set_max_byte get_pc
9             get_reg get_current_time get_max_byte
10             get_exec_count get_exec_time get_last_error get_cmp_flag );
11              
12             $VERSION = 0.4;
13              
14 8     8   40 use strict;
  8         14  
  8         287  
15 8     8   249 use warnings;
  8         20  
  8         391  
16              
17             use constant
18             {
19 8         1585 MIX_OK => 0,
20             MIX_HALT => 1,
21             MIX_ERROR => 2,
22             MIX_IOWAIT => 3
23 8     8   45 };
  8         22  
24              
25             # Unit 0 to 7 is tapes; Unit 8 to 15 is disks and drums.
26             # To specify a tape unit, the convention here is to write (U_TAPE + i).
27             # Typewriter and Papertape share the same unit number,
28             # because they are physically combined.
29             use constant
30             {
31 8         80976 U_TAPE => 0,
32             U_DISK => 8,
33             U_CARDREADER => 16,
34             U_CARDPUNCH => 17,
35             U_PRINTER => 18,
36             U_TYPEWRITER => 19,
37             U_PAPERTAPE => 19
38 8     8   62 };
  8         14  
39              
40             # Op Dispatch Table
41             my @op_dispatch_table = (
42             \&X_NOP, #00
43             \&X_ADD, #01
44             \&X_SUB, #02
45             \&X_MUL, #03
46             \&X_DIV, #04
47             \&X_MISC, #05
48             \&X_SHIFT, #06
49             \&X_MOVE, #07
50             \&X_LDA, #08
51             \&X_LDI, #09
52             \&X_LDI, #10
53             \&X_LDI, #11
54             \&X_LDI, #12
55             \&X_LDI, #13
56             \&X_LDI, #14
57             \&X_LDX, #15
58             \&X_LDAN, #16
59             \&X_LDIN, #17
60             \&X_LDIN, #18
61             \&X_LDIN, #19
62             \&X_LDIN, #20
63             \&X_LDIN, #21
64             \&X_LDIN, #22
65             \&X_LDXN, #23
66             \&X_STA, #24
67             \&X_STI, #25
68             \&X_STI, #26
69             \&X_STI, #27
70             \&X_STI, #28
71             \&X_STI, #29
72             \&X_STI, #30
73             \&X_STX, #31
74             \&X_STJ, #32
75             \&X_STZ, #33
76             \&X_JBUS, #34
77             \&X_IOC, #35
78             \&X_INPUT, #36
79             \&X_OUTPUT, #37
80             \&X_JRED, #38
81             \&X_JMP_COND, #39
82             \&X_JMP_REG, #40
83             \&X_JMP_REG, #41
84             \&X_JMP_REG, #42
85             \&X_JMP_REG, #43
86             \&X_JMP_REG, #44
87             \&X_JMP_REG, #45
88             \&X_JMP_REG, #46
89             \&X_JMP_REG, #47
90             \&X_ADDR_TRANSFER, #48
91             \&X_ADDR_TRANSFER, #49
92             \&X_ADDR_TRANSFER, #50
93             \&X_ADDR_TRANSFER, #51
94             \&X_ADDR_TRANSFER, #52
95             \&X_ADDR_TRANSFER, #53
96             \&X_ADDR_TRANSFER, #54
97             \&X_ADDR_TRANSFER, #55
98             \&X_CMP, #56
99             \&X_CMP, #57
100             \&X_CMP, #58
101             \&X_CMP, #59
102             \&X_CMP, #60
103             \&X_CMP, #61
104             \&X_CMP, #62
105             \&X_CMP, #63
106             );
107              
108             my @regname = qw(rA rI1 rI2 rI3 rI4 rI5 rI6 rX);
109              
110             ################################################################
111             # Initialization
112             ################################################################
113              
114             sub new
115             {
116 7     7 0 1734 my $invocant = shift;
117 7   33     55 my $class = ref($invocant) || $invocant;
118 7         20 my $self = { @_ };
119 7         24 bless $self, $class;
120              
121 7 100       76 $self->{max_byte} = 64 if !exists $self->{max_byte};
122              
123 7 50       50 $self->{timeunit} = 5 if !exists $self->{timeunit};
124 7         27 $self->{ms} = 1000/$self->{timeunit};
125              
126 7         29 $self->{dev} = {};
127 7         51 $self->reset();
128 7         49 return $self;
129             }
130              
131             sub reset
132             {
133 12     12 1 59 my $self = shift;
134              
135 12         64 $self->{rA} = ['+', 0, 0, 0, 0, 0];
136 12         53 $self->{rX} = ['+', 0, 0, 0, 0, 0];
137 12         44 $self->{rJ} = ['+', 0, 0, 0, 0, 0];
138 12         49 $self->{rZ} = ['+', 0, 0, 0, 0, 0];
139              
140 12         42 $self->{rI1} = ['+', 0, 0, 0, 0, 0];
141 12         49 $self->{rI2} = ['+', 0, 0, 0, 0, 0];
142 12         37 $self->{rI3} = ['+', 0, 0, 0, 0, 0];
143 12         55 $self->{rI4} = ['+', 0, 0, 0, 0, 0];
144 12         56 $self->{rI5} = ['+', 0, 0, 0, 0, 0];
145 12         37 $self->{rI6} = ['+', 0, 0, 0, 0, 0];
146              
147 12         29 $self->{mem} = [];
148 12         8262 $self->{execnt} = [];
149 12         4499 $self->{exetime} = [];
150              
151 12         483 for (0 .. 3999) {
152 48000         53178 push @{$self->{mem}}, ['+', 0, 0, 0, 0, 0];
  48000         153184  
153 48000         64333 push @{$self->{execnt}}, 0;
  48000         69148  
154 48000         53613 push @{$self->{exetime}}, 0;
  48000         89698  
155             }
156              
157 12         60 $self->{devstat} = [];
158 12         98 for (0 .. 19) {
159 240         231 push @{$self->{devstat}}, {
  240         695  
160             laststarted => 0,
161             delay => 0
162             };
163             }
164              
165             # MIX running time from last reset, recorded in time units
166 12         40 $self->{time} = 0;
167            
168 12         182 $self->{pc} = 0;
169 12         32 $self->{next_pc} = 0;
170 12         26 $self->{ov_flag} = 0;
171 12         27 $self->{cmp_flag} = 0;
172 12         29 $self->{status} = MIX_OK;
173 12         88 $self->{message} = 'running';
174             }
175              
176             ##############################################################################
177             # Instruction Execution
178             ##############################################################################
179              
180             # Execute an instruction, update the machine state
181             # Return 1 if successfully execute one; 0 if there are something happen.
182             # On returning zero, it is not necessarily an error, maybe
183             # you just need to type a line to get the program continue.
184             sub step
185             {
186 83     83 1 33264 my $self = shift;
187              
188 83 50       317 if ($self->{status} == MIX_IOWAIT) { # Need to read a line to proceed
    50          
189 0         0 return 0;
190             } elsif ($self->{status} != MIX_OK) {
191 0         0 return 0;
192             }
193              
194 83         122 my $start_time = $self->{time};
195              
196             # Fetch instruction
197              
198 83         103 my $loc = $self->{pc};
199 83 50 33     361 if ($loc < 0 || $loc > 3999)
200             {
201 0         0 $self->{status} = MIX_ERROR;
202 0         0 $self->{message} = "instruction overflow: $loc";
203 0         0 return 0;
204             }
205              
206             # one time unit for one memory access
207 83         152 $self->{time} = $self->{time} + 1;
208              
209             # read instruction and unpack the fields
210 83         88 my @word = @{@{$self->{mem}}[$loc]};
  83         91  
  83         375  
211 83         116 my $c = $word[5];
212 83         92 my $f = $word[4];
213 83         121 my $r = $f % 8;
214 83         168 my $l = int($f / 8);
215 83         91 my $i = $word[3];
216 83         150 my $a = $word[1] * $self->{max_byte} + $word[2];
217 83 50       154 $a = ($word[0] eq '+')? $a : (0 - $a);
218 83         95 my $m = $a;
219 83 50 33     211 if ($i >= 1 && $i <= 6)
220             {
221 0         0 my $ireg = $self->{$regname[$i]};
222 0         0 my $offset = @{$ireg}[4] * $self->{max_byte} + @{$ireg}[5];
  0         0  
  0         0  
223 0 0       0 $offset = 0 - $offset if (@{$ireg}[0] eq '-');
  0         0  
224 0         0 $m += $offset;
225             }
226            
227             # default next program counter
228 83         134 $self->{next_pc} = $self->{pc} + 1;
229              
230             # execute instruction
231 83 50 33     210 if ($c >= 0 || $c <= 63 )
232             {
233 83         124 my $opfunc = $op_dispatch_table[$c];
234 83 50       108 goto ERROR_INST if &{$opfunc}($self, $c, $f, $r, $l, $i, $a, $m) == 0;
  83         216  
235             }
236             else
237             {
238 0         0 ERROR_INST:
239             $self->{status} = MIX_ERROR;
240 0         0 $self->{message} = "invalid instruction at $loc";
241 0         0 return 0;
242             }
243              
244             # update program counter
245 83         163 $self->{pc} = $self->{next_pc};
246              
247             # update performance data
248 83         88 @{$self->{execnt}}[$loc]++;
  83         138  
249 83         97 @{$self->{exetime}}[$loc] += $self->{time} - $start_time;
  83         188  
250              
251 83         227 return 1;
252             }
253              
254             # MIX "GO" button
255             sub go
256             {
257 0     0 1 0 my ($self) = @_;
258              
259 0         0 $self->load_card(0);
260 0         0 while ($self->{status} == MIX_OK) {
261 0         0 $self->step();
262             }
263             }
264              
265             sub X_ADD {
266 4     4 0 8 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
267 4         15 my @tmp = $self->read_mem_timed($m, $l, $r);
268 4         16 $self->add(\@tmp);
269 4         18 return 1;
270             }
271              
272             sub X_ADDR_TRANSFER {
273 0     0 0 0 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
274              
275 0         0 my $reg = $self->{$regname[$c-48]};
276 0 0       0 if ($f == 0) { ## INC
    0          
    0          
    0          
277 0         0 my $v = word_to_int($reg, $self->{max_byte});
278 0 0       0 if (int_to_word($v+$m, $reg, $self->{max_byte})) {
279 0         0 $self->{ov_flag} = 0;
280             } else {
281 0         0 $self->{ov_flag} = 1;
282             }
283             } elsif ($f == 1) { ## DEC
284 0         0 my $v = word_to_int($reg, $self->{max_byte});
285 0 0       0 if (int_to_word($v-$m, $reg, $self->{max_byte})) {
286 0         0 $self->{ov_flag} = 0;
287             } else {
288 0         0 $self->{ov_flag} = 1;
289             }
290             } elsif ($f == 2) { ##ENT
291 0         0 int_to_word($m, $reg, $self->{max_byte});
292             } elsif ($f == 3) { ##ENN
293 0         0 int_to_word(-$m, $reg, $self->{max_byte});
294             } else {
295 0         0 return 0;
296             }
297              
298 0         0 return 1;
299             }
300              
301             sub X_CMP {
302 1     1 0 2 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
303              
304 1         7 my $tmp1 = $self->get_reg($regname[$c-56], $l, $r);
305 1         4 my $tmp2 = $self->read_mem_timed($m, $l, $r);
306 1         2 $self->{cmp_flag} = $tmp1 - $tmp2;
307              
308 1         3 return 1;
309             }
310              
311             sub X_DIV {
312 2     2 0 6 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
313              
314 2 50       8 return 0 if $f == 6;
315              
316 2         7 my @tmp = $self->read_mem_timed($m, $l, $r);
317 2         11 $self->div(\@tmp);
318              
319             # DIV requires 10 additional time units
320 2         3 $self->{time} += 10;
321 2         8 return 1;
322             }
323              
324             # Usage: $self->wait_until_device_ready($devnum)
325             #
326             # Used only before IN/OUT operations.
327             #
328             # If the device is busy, that is, the current time - last started < delay,
329             # increase the current time, so that the device would be ready
330             sub wait_until_device_ready
331             {
332 0     0 0 0 my ($self, $devnum) = @_;
333              
334 0 0 0     0 return if $devnum < 0 || $devnum > 19;
335              
336 0         0 my $devstat = @{$self->{devstat}}[$devnum];
  0         0  
337 0         0 my $laststarted = $devstat->{laststarted};
338              
339             # See whether the device is still busy
340 0 0       0 if ($self->{time} - $laststarted < $devstat->{delay})
341             {
342             # advance the current system time to the point
343             # that the device would be ready
344 0         0 $self->{time} = $laststarted + $devstat->{delay};
345             }
346             }
347              
348             sub X_INPUT {
349 0     0 0 0 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
350              
351 0         0 $self->wait_until_device_ready($f);
352 0 0 0     0 if ($f == U_CARDREADER) { ## CARD READER
    0 0        
    0          
    0          
353 0         0 $self->load_card($m);
354             } elsif ($f >= U_TAPE && $f < U_DISK) {
355 0         0 $self->read_tape($f, $m);
356             } elsif ($f >= U_DISK && $f < U_CARDREADER) {
357 0         0 $self->read_disk($f, $m);
358             } elsif ($f == U_TYPEWRITER) { # Input from typewriter
359 0         0 $self->read_typewriter($m);
360             } else {
361 0         0 $self->{status} = MIX_ERROR;
362 0         0 $self->{message} = "invalid input device(#$f)";
363             }
364 0         0 return 1;
365             }
366              
367             sub X_IOC {
368 0     0 0 0 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
369              
370 0         0 $self->wait_until_device_ready($f);
371 0 0 0     0 if ($f == U_PRINTER) { ## Printer: set up new page
    0 0        
    0          
    0          
372 0         0 $self->new_page($m);
373             } elsif (U_TAPE <= $f && $f <= (U_TAPE+7)) {
374 0         0 $self->set_tape_pos($f, $m);
375             } elsif (U_DISK <= $f && $f <= (U_DISK+7)) {
376 0         0 $self->set_disk_pos($f);
377             } elsif ($f == U_PAPERTAPE) {
378 0         0 $self->rewind_paper_tape;
379             } else {
380 0         0 $self->{status} = MIX_ERROR;
381 0         0 $self->{message} = "invalid ioc for device(#$f)";
382             }
383 0         0 return 1;
384             }
385              
386             # Jump when device busy: always no busy
387             sub X_JBUS {
388 0     0 0 0 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
389 0         0 return 1;
390             }
391              
392             sub X_JMP_COND {
393 0     0 0 0 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
394              
395 0 0       0 return 0 if $f > 9;
396 0         0 my $ok = 1;
397 0         0 my $savj = 0;
398 0         0 my $cf = $self->{cmp_flag};
399 0         0 my @cond = ($cf<0,$cf==0,$cf>0,$cf>=0,$cf!=0,$cf<=0);
400              
401 0 0       0 if ($f == 0) {
    0          
    0          
    0          
402 0         0 $ok = 1;
403             }elsif ($f == 1) {
404 0         0 $savj = 1;
405             } elsif ($f == 2) {
406 0         0 $ok = $self->{ov_flag};
407             } elsif ($f == 3) {
408 0         0 $ok = !$self->{ov_flag};
409             } else {
410 0         0 $ok = $cond[$f-4];
411             }
412              
413 0 0       0 if ($ok) {
414 0 0       0 if (!$savj) {
415 0         0 int_to_word($self->{next_pc}, $self->{rJ}, $self->{max_byte});
416             }
417 0         0 $self->{next_pc} = $m;
418             }
419              
420 0         0 return 1;
421             }
422              
423             sub X_JMP_REG {
424 0     0 0 0 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
425 0 0       0 return 0 if $f > 5;
426 0         0 my $val = $self->get_reg($regname[$c-40]);
427 0         0 my @cond = ($val<0,$val==0,$val>0,$val>=0,$val!=0,$val<=0);
428 0 0       0 if ($cond[$f]) {
429 0         0 int_to_word($self->{next_pc}, $self->{rJ}, $self->{max_byte});
430 0         0 $self->{next_pc} = $m;
431             }
432 0         0 return 1;
433             }
434              
435             # Jump ready: jump immediately
436             sub X_JRED {
437 0     0 0 0 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
438              
439 0         0 int_to_word($self->{next_pc}, $self->{rJ}, $self->{max_byte});
440 0         0 $self->{next_pc} = $m;
441              
442 0         0 return 1;
443             }
444              
445             sub X_LDA {
446 7     7 0 12 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
447 7         17 my @tmp = $self->read_mem_timed($m, $l, $r);
448 7         22 $self->set_reg('rA', \@tmp);
449 7         25 return 1;
450             }
451              
452             sub X_LDAN {
453 7     7 0 12 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
454 7         15 my @tmp = $self->read_mem_timed($m, $l, $r);
455 7         20 @tmp = neg_word(\@tmp);
456 7         20 $self->set_reg('rA', \@tmp);
457 7         21 return 1;
458             }
459              
460             sub X_LDI {
461 8     8 0 16 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
462 8         21 my @tmp = $self->read_mem_timed($m, $l, $r);
463 8         32 $self->set_reg('rI' . ($c-8), \@tmp);
464 8         27 return 1;
465             }
466              
467             sub X_LDIN {
468 7     7 0 11 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
469 7         16 my @tmp = $self->read_mem_timed($m, $l, $r);
470 7         17 @tmp = neg_word(\@tmp);
471 7         28 $self->set_reg('rI' . ($c-16), \@tmp);
472 7         23 return 1;
473             }
474              
475             sub X_LDX {
476 7     7 0 12 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
477 7         16 my @tmp = $self->read_mem_timed($m, $l, $r);
478 7         18 $self->set_reg('rX', \@tmp);
479 7         23 return 1;
480             }
481              
482             sub X_LDXN {
483 7     7 0 13 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
484 7         16 my @tmp = $self->read_mem_timed($m, $l, $r);
485 7         21 @tmp = neg_word(\@tmp);
486 7         20 $self->set_reg('rX', \@tmp);
487 7         21 return 1;
488             }
489              
490             sub X_MISC {
491 1     1 0 3 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
492              
493 1 50       4 if ($f == 2) # HLT
    0          
    0          
494             {
495 1         2 $self->{status} = MIX_HALT;
496 1         3 $self->{message} = 'halts normally';
497             }
498             elsif ($f == 0) # NUM
499             {
500 0         0 my @a = @{$self->{rA}};
  0         0  
501 0         0 my @x = @{$self->{rX}};
  0         0  
502 0         0 my $m = $self->{max_byte};
503 0         0 my $M = $m*$m*$m*$m*$m;
504 0         0 my $sa = shift @a;
505 0         0 shift @x;
506 0         0 push @a, @x;
507 0         0 my $val = 0;
508 0         0 while (@a) {
509 0         0 my $d = shift @a;
510 0         0 $val = $val*10+($d % 10);
511             }
512 0 0       0 if ($val >= $M) {
513 0         0 $val = $val % $M;
514 0         0 $self->{ov_flag} = 1;
515             } else {
516 0         0 $self->{ov_flag} = 0;
517             }
518 0         0 int_to_word($val, $self->{rA}, $m);
519 0         0 @{$self->{rA}}[0] = $sa;
  0         0  
520             }
521             elsif ($f == 1) # CHAR
522             {
523 0         0 my $val = word_to_uint($self->{rA}, $self->{max_byte});
524 0         0 my $i;
525 0         0 for ($i = 5; $i >= 1; $i--) {
526 0         0 @{$self->{rX}}[$i] = 30 + $val%10;
  0         0  
527 0         0 $val = int($val/10);
528             }
529 0         0 for ($i = 5; $i >= 1; $i--) {
530 0         0 @{$self->{rA}}[$i] = 30 + $val%10;
  0         0  
531 0         0 $val = int($val/10);
532             }
533             }
534 1         5 return 1;
535             }
536              
537             sub X_MOVE {
538 0     0 0 0 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
539              
540 0         0 my $dest = $self->get_reg('rI1');
541 0         0 for (my $i = 0; $i < $f; $i++, $m++, $dest++) {
542 0         0 my @w = $self->read_mem_timed($m);
543 0         0 $self->write_mem_timed($dest, \@w);
544             }
545 0         0 my @tmp = ('+', 0,0,0,0,0);
546 0         0 int_to_word($dest, \@tmp, $self->{max_byte});
547 0         0 $self->set_reg('rI1', \@tmp);
548              
549 0         0 return 1;
550             }
551              
552             sub X_MUL {
553 3     3 0 8 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
554              
555 3 50       12 return 0 if $f == 6;
556              
557 3         11 my @tmp = $self->read_mem_timed($m, $l, $r);
558 3         16 $self->mul(\@tmp);
559              
560             # MUL requires 8 additional time units
561 3         6 $self->{time} += 8;
562 3         11 return 1;
563             }
564              
565             sub X_NOP {
566 1     1 0 4 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
567 1         4 return 1;
568             }
569              
570             sub X_OUTPUT {
571 0     0 0 0 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
572              
573 0         0 $self->wait_until_device_ready($f);
574 0 0 0     0 if ($f == U_CARDPUNCH) { ## CARD Punch
    0 0        
    0          
    0          
    0          
575 0         0 $self->punch_card($m);
576             } elsif ($f == U_PRINTER) { ## Printer
577 0         0 $self->print_line($m);
578             } elsif (U_TAPE <= $f && $f <= (U_TAPE+7)) {
579 0         0 $self->write_tape($f, $m);
580             } elsif (U_DISK <= $f && $f <= (U_DISK+7)) {
581 0         0 $self->write_disk($f, $m);
582             } elsif ($f == U_PAPERTAPE) { ## Output to paper tape
583 0         0 $self->write_paper_tape($m);
584             } else {
585 0         0 $self->{status} = MIX_ERROR;
586 0         0 $self->{message} = "invalid output device(#$f)";
587             }
588              
589 0         0 return 1;
590             }
591              
592             sub X_SHIFT {
593 1     1 0 4 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
594              
595 1 50       4 return 0 if $m < 0;
596              
597 1         2 my @a = @{$self->{rA}};
  1         5  
598 1         3 my @x = @{$self->{rX}};
  1         7  
599 1         3 my $sa = shift @a;
600 1         2 my $sx = shift @x;
601 1 50       16 if ($f == 0) { ## SLA
    50          
    50          
    50          
    50          
    50          
602 0         0 $m = $m%5;
603 0         0 while (--$m >= 0) {
604 0         0 shift @a;
605 0         0 push @a, 0;
606             }
607             } elsif ($f == 1) { ## SRA
608 0         0 $m = $m%5;
609 0         0 while (--$m >= 0) {
610 0         0 pop @a;
611 0         0 unshift @a, 0;
612             }
613             } elsif ($f == 2) { ## SLAX
614 0         0 $m = $m%10;
615 0         0 while (--$m >= 0) {
616 0         0 shift @a;
617 0         0 push @a, shift @x;
618 0         0 push @x, 0;
619             }
620             } elsif ($f == 3) { ## SRAX
621 0         0 $m = $m%10;
622 0         0 while (--$m >= 0) {
623 0         0 pop @x;
624 0         0 unshift @x, pop @a;
625 0         0 unshift @a, 0;
626             }
627             } elsif ($f == 4) { ## SLC
628 0         0 $m = $m%10;
629 0         0 while (--$m >= 0) {
630 0         0 push @a, shift @x;
631 0         0 push @x, shift @a;
632             }
633             } elsif ($f == 5) { ## SRC
634 1         2 $m = $m%10;
635 1         5 while (--$m >= 0) {
636 0         0 unshift @a, pop @x;
637 0         0 unshift @x, pop @a;
638             }
639             } else {
640 0         0 return 0;
641             }
642              
643 1         3 unshift @a, $sa;
644 1         3 unshift @x, $sx;
645 1         4 $self->set_reg('rA', \@a);
646 1         4 $self->set_reg('rX', \@x);
647              
648             # shift operations takes additional 1 time unit
649 1         3 $self->{time} = $self->{time} + 1;
650              
651 1         5 return 1;
652             }
653              
654             sub X_STA {
655 6     6 0 12 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
656 6         19 $self->write_mem_timed($m, $self->{rA}, $l, $r);
657 6         18 return 1;
658             }
659              
660             sub X_STI {
661 7     7 0 15 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
662 7         20 my $ri = 'rI' . ($c-24);
663 7         21 $self->write_mem_timed($m, $self->{$ri}, $l, $r);
664 7         20 return 1;
665             }
666              
667             sub X_STJ {
668 1     1 0 3 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
669 1         5 $self->write_mem_timed($m, $self->{rJ}, $l, $r);
670 1         3 return 1;
671             }
672              
673             sub X_STX {
674 6     6 0 11 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
675 6         19 $self->write_mem_timed($m, $self->{rX}, $l, $r);
676 6         18 return 1;
677             }
678              
679             sub X_STZ {
680 6     6 0 12 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
681 6         19 $self->write_mem_timed($m, $self->{rZ}, $l, $r);
682 6         17 return 1;
683             }
684              
685             sub X_SUB {
686 1     1 0 2 my ($self, $c, $f, $r, $l, $i, $a, $m) = @_;
687 1         4 my @tmp = $self->read_mem_timed($m, $l, $r);
688 1         6 $self->minus(\@tmp);
689 1         4 return 1;
690             }
691              
692             ##############################################################################
693             # Access registers and memory
694             ##############################################################################
695              
696             sub get_reg
697             {
698 1     1 0 3 my ($self, $reg, $l, $r) = @_;
699              
700 1 50       5 if (!exists $self->{$reg}) {
701 0         0 $self->{status} = MIX_ERROR;
702 0         0 $self->{message} = "accessing non-existed reg: $reg";
703 0         0 return undef;
704             }
705              
706 1         2 my @retval = @{$self->{$reg}};
  1         4  
707 1 50       4 if (defined $l)
708             {
709 1 50       3 $r = $l if !defined $r;
710              
711 1 50       5 if ($l == 0)
712             {
713 1         2 $l = 1;
714             }
715             else
716             {
717 0         0 $retval[0] = '+';
718             }
719              
720 1         2 my $i = 5;
721 1         5 for (my $i = 5; $i > 0; $i--, $r--)
722             {
723 5 50       10 if ($r >= $l)
724             {
725 5         11 $retval[$i] = $retval[$r];
726             }
727             else
728             {
729 0         0 $retval[$i] = 0;
730             }
731             }
732             }
733              
734 1 50       3 if (wantarray)
735             {
736 0         0 return @retval;
737             }
738             else
739             {
740 1         5 my $value = word_to_int(\@retval, $self->{max_byte});
741 1         3 return $value;
742             }
743             }
744              
745             sub set_reg
746             {
747 58     58 0 11393 my ($self, $reg, $wref) = @_;
748              
749 58 50       143 if (!exists $self->{$reg}) {
750 0         0 $self->{status} = MIX_ERROR;
751 0         0 $self->{message} = "accessing non-existed reg: $reg";
752 0         0 return;
753             }
754 58         62 my @word = @{$wref};
  58         138  
755              
756 58         74 my $sign = '+';
757 58 50 66     65 if (@{$wref}[0] eq '+' || @{$wref}[0] eq '-') {
  58         169  
  21         71  
758 58         60 $sign = shift @{$wref};
  58         100  
759             }
760 58         116 @{$self->{$reg}}[0] = $sign;
  58         147  
761              
762 58 100       258 my $l = ($reg =~ m/r(I|J)/)?4:1;
763 58         64 my $r = 5;
764 58   66     269 while ($r >= $l && @word != 0) {
765 224         269 @{$self->{$reg}}[$r] = pop @word;
  224         394  
766 224         864 --$r;
767             }
768             }
769              
770              
771             sub read_mem_timed
772             {
773 54     54 0 68 my $self = shift;
774 54         89 $self->{time} = $self->{time} + 1;
775 54         114 return $self->read_mem(@_);
776             }
777              
778             sub write_mem_timed
779             {
780 26     26 0 41 my $self = shift;
781 26         41 $self->{time} = $self->{time} + 1;
782 26         59 return $self->write_mem(@_);
783             }
784              
785              
786             sub read_mem
787             {
788 81     81 0 182 my ($self,$loc,$l, $r) = @_;
789              
790 81 50 33     359 if ($loc < 0 || $loc > 3999) {
791 0         0 $self->{status} = MIX_ERROR;
792 0         0 $self->{message} = "access invalid memory location: $loc";
793 0         0 return;
794             }
795              
796              
797 81         83 my @retval = @{@{$self->{mem}}[$loc]};
  81         85  
  81         282  
798 81 100       218 if (defined $l)
799             {
800 54 50       100 $r = $l if !defined $r;
801              
802 54 100       89 if ($l == 0)
803             {
804 31         34 $l = 1;
805             }
806             else
807             {
808 23         34 $retval[0] = '+';
809             }
810              
811 54         61 my $i = 5;
812 54         118 for (my $i = 5; $i > 0; $i--, $r--)
813             {
814 270 100       360 if ($r >= $l)
815             {
816 165         356 $retval[$i] = $retval[$r];
817             }
818             else
819             {
820 105         225 $retval[$i] = 0;
821             }
822             }
823             }
824              
825 81 100       144 if (wantarray)
826             {
827 80         406 return @retval;
828             }
829             else
830             {
831 1         3 my $value = word_to_int(\@retval, $self->{max_byte});
832 1         3 return $value;
833             }
834             }
835              
836             # $loc: location, must be in [0..3999]
837             # $wref: reference to a mix word
838             # $l,$r: field specification of destinated word, 0<=$l<=$r<=5
839             sub write_mem
840             {
841 143     143 0 23435 my ($self,$loc,$wref, $l, $r) = @_;
842              
843 143 50 33     717 if ($loc < 0 || $loc > 3999) {
844 0         0 $self->{status} = MIX_ERROR;
845 0         0 $self->{message} = "access invalid memory location: $loc";
846 0         0 return;
847             }
848              
849 143         173 my @word = @{$wref};
  143         337  
850              
851 143 100       309 if (!defined $l) {
    50          
852 116         146 $l = 0;
853 116         134 $r = 5;
854             } elsif (!defined $r) {
855 0         0 $r = $l;
856             }
857 143         172 my $dest = @{$self->{mem}}[$loc];
  143         311  
858              
859 143         350 for (my $i = $r; $i >= $l; $i--) {
860 777 100       1467 @{$dest}[$i] = pop @word if $i > 0;
  651         909  
861 777 100       2131 if ($i == 0) {
862 126 50 66     561 if (@word > 0 && ($word[0] eq '+' || $word[0] eq '-')) {
      33        
863 126         155 @{$dest}[0] = $word[0];
  126         627  
864             } else {
865 0         0 @{$dest}[0] = '+';
  0         0  
866             }
867             }
868             }
869              
870             }
871              
872             #######################################################################
873             # Arithmetic Operations
874             #######################################################################
875              
876             sub add
877             {
878 5     5 0 6 my ($self, $w) = @_;
879 5         8 my $m = $self->{max_byte};
880 5         8 my $a = $self->{rA};
881              
882 5 100       18 if (!int_to_word(word_to_int($w,$m)+word_to_int($a,$m), $a, $m)) {
883 1         2 $self->{ov_flag} = 1;
884             } else {
885 4         9 $self->{ov_flag} = 0;
886             }
887             }
888              
889             sub minus
890             {
891 1     1 0 2 my ($self, $w) = @_;
892 1         2 my @t = @{$w};
  1         2  
893 1 50       5 if ($t[0] eq '+') {
894 1         2 $t[0] = '-';
895             } else {
896 0         0 $t[0] = '+';
897             }
898 1         3 $self->add(\@t);
899             }
900              
901             sub mul
902             {
903 3     3 0 6 my ($self, $w) = @_;
904 3         8 my $a = $self->{rA};
905 3         6 my $x = $self->{rX};
906 3         6 my $m = $self->{max_byte};
907 3         8 my $M = $m*$m*$m*$m*$m;
908              
909 3         10 my $v = word_to_int($a,$m)*word_to_int($w,$m);
910              
911 3 100       11 my $sign = ($v>=0?'+':'-');
912 3 100       9 $v = -$v if $v < 0;
913              
914 3         39 int_to_word($v%$M, $x, $m);
915 3         12 int_to_word(int($v/$M), $a, $m);
916              
917 3         3 @{$x}[0] = @{$a}[0] = $sign;
  3         6  
  3         9  
918 3         9 $self->{ov_flag} = 0;
919             }
920              
921             sub div
922             {
923 2     2 0 4 my ($self, $w) = @_;
924 2         30 my $a = $self->{rA};
925 2         5 my $x = $self->{rX};
926 2         3 my $m = $self->{max_byte};
927 2         4 my $M = $m*$m*$m*$m*$m;
928              
929 2         9 my $v = word_to_uint($w,$m);
930              
931 2 50       8 if ($v==0) {
932 0         0 $self->{ov_flag} = 1;
933 0         0 return;
934             }
935              
936 2         4 my $va = word_to_uint($a,$m);
937 2         5 my $vx = word_to_uint($x,$m);
938 2         5 my $V = $va*$M+$vx;
939              
940 2         3 my $sign;
941 2         3 my $sa = @{$a}[0];
  2         4  
942 2 50       4 if ($sa eq @{$w}[0]) {
  2         9  
943 2         5 $sign = '+';
944             } else {
945 0         0 $sign = '-';
946             }
947            
948 2         7 int_to_word($V%$v, $x, $m);
949 2         4 @{$x}[0] = $sa;
  2         4  
950 2 50       8 if (int_to_word(int($V/$v), $a, $m)) {
951 2         5 $self->{ov_flag} = 0;
952             } else {
953 0         0 $self->{ov_flag} = 1;
954             }
955 2         5 @{$a}[0] = $sign;
  2         7  
956             }
957              
958              
959             sub set_max_byte
960             {
961 0     0 0 0 my $self = shift;
962 0         0 $self->{max_byte} = shift;
963             }
964              
965             sub get_max_byte
966             {
967 0     0 0 0 my $self = shift;
968 0         0 return $self->{max_byte};
969             }
970              
971             sub get_last_error
972             {
973 0     0 0 0 my $self = shift;
974 0         0 my $status = $self->{status};
975 0         0 my $msg = uc($self->{message});
976 0 0       0 return "OK" if $status == MIX_OK;
977 0 0       0 return "HALT" if $status == MIX_HALT;
978 0 0       0 return $msg if $status == MIX_ERROR;
979 0 0       0 return "WAIT IO READY - " . $msg if $status == MIX_IOWAIT;
980             }
981              
982              
983             # For tape and disk units, each item in buffer is a word, like
984             # ['+', 0, 0, 0, 1, 2]
985             # For card reader and punch, each item of buffer is a line.
986             # For printer, each item of buffer is a page.
987             # e.g. $mix->add_device(16, \@cards);
988             sub add_device
989             {
990 0     0 1 0 my ($self, $u, $buf) = @_;
991             # FIXME: paper tape?
992 0 0 0     0 return 0 if $u > 19 || $u < 0;
993 0         0 $self->{dev}->{$u} = {};
994 0 0       0 if (defined $buf) {
995 0         0 $self->{dev}->{$u}->{buf} = $buf;
996             } else {
997 0         0 $self->{dev}->{$u}->{buf} = [];
998             }
999 0         0 $self->{dev}->{$u}->{pos} = 0;
1000 0         0 return 1;
1001             }
1002              
1003              
1004             sub get_overflow
1005             {
1006 0     0 0 0 my $self = shift;
1007 0         0 return $self->{ov_flag};
1008             }
1009              
1010             sub get_cmp_flag
1011             {
1012 0     0 0 0 my $self = shift;
1013 0         0 return $self->{cmp_flag};
1014             }
1015              
1016             sub get_pc
1017             {
1018 0     0 0 0 my $self = shift;
1019 0         0 return $self->{pc};
1020             }
1021              
1022             sub get_current_time
1023             {
1024 16     16 0 3571 my $self = shift;
1025 16         44 return $self->{time};
1026             }
1027              
1028             sub get_exec_count
1029             {
1030 0     0 0 0 my ($self, $loc) = @_;
1031 0         0 return @{$self->{execnt}}[$loc];
  0         0  
1032             }
1033              
1034             sub get_exec_time
1035             {
1036 0     0 0 0 my ($self, $loc) = @_;
1037 0         0 return @{$self->{exetime}}[$loc];
  0         0  
1038             }
1039              
1040              
1041             sub get_device_buffer
1042             {
1043 0     0 1 0 my $self = shift;
1044 0         0 my $u = shift;
1045 0 0       0 if (exists $self->{dev}->{$u}) {
1046 0         0 return $self->{dev}->{$u}->{buf};
1047             } else {
1048 0         0 return undef;
1049             }
1050             }
1051              
1052             sub write_tape
1053             {
1054 0     0 0 0 my ($self, $u, $m) = @_;
1055              
1056             #FIXME: error checking
1057              
1058 0         0 my $tape = $self->{dev}->{$u};
1059 0         0 my $n = @{$tape->{buf}};
  0         0  
1060 0         0 for (my $i = 0; $i < 100; $i++) {
1061 0         0 my @w = $self->read_mem($m+$i);
1062 0 0       0 if ($tape->{pos} < $n) {
1063 0         0 @{$tape->{buf}}[ $tape->{pos} ] = \@w;
  0         0  
1064             } else {
1065 0         0 push @{$tape->{buf}}, \@w;
  0         0  
1066             }
1067 0         0 $tape->{pos}++;
1068             }
1069              
1070             }
1071              
1072             sub read_tape
1073             {
1074 0     0 0 0 my ($self, $u, $m) = @_;
1075              
1076             #FIXME: error checking
1077              
1078 0         0 my $tape = $self->{dev}->{$u};
1079 0         0 my $n = @{$tape->{buf}};
  0         0  
1080              
1081 0   0     0 for (my $i = 0; $i < 100 && $tape->{pos} < $n; $i++) {
1082 0         0 my $w = @{$tape->{buf}}[ $tape->{pos} ];
  0         0  
1083 0         0 $self->write_mem($m+$i, $w);
1084 0         0 $tape->{pos}++;
1085             }
1086             }
1087              
1088              
1089             # TODO: tape and disk io
1090              
1091             # device ability is aligned with IBM1130.org
1092             # tape io: 10ms
1093             # disk io: 10ms
1094             # seek : 10ms
1095              
1096             sub set_tape_pos {
1097 0     0 0 0 my ($self) = @_;
1098             }
1099             sub set_disk_pos {
1100 0     0 0 0 my ($self) = @_;
1101             }
1102             sub write_disk {
1103 0     0 0 0 my ($self) = @_;
1104             }
1105             sub read_disk {
1106 0     0 0 0 my ($self) = @_;
1107             }
1108              
1109             # Load cards into memory started at $loc
1110             sub load_card
1111             {
1112 0     0 1 0 my ($self,$loc) = @_;
1113              
1114             # Check if card reader installed
1115 0 0       0 if (!exists $self->{dev}->{+U_CARDREADER}) {
1116 0         0 $self->{status} = MIX_ERROR;
1117 0         0 $self->{message} = "missing card reader";
1118 0         0 return 0;
1119             }
1120              
1121 0         0 my $reader = $self->{dev}->{+U_CARDREADER};
1122 0         0 my $buf = $reader->{buf};
1123 0         0 my $pos = $reader->{pos};
1124              
1125             # Check if there are cards unread
1126 0 0       0 if ($pos >= @{$buf}) {
  0         0  
1127 0         0 $self->{status} = MIX_ERROR;
1128 0         0 $self->{message} = "no card in card reader";
1129 0         0 return 0;
1130             }
1131            
1132 0         0 my $crd = @{$buf}[$pos];
  0         0  
1133 0         0 $reader->{pos}++;
1134              
1135             # Pad spaces to make the card have 80 characters
1136 0 0       0 if (length($crd)!=80) {
1137 0         0 $crd .= " " x (80-length($crd));
1138             }
1139 0         0 my @w = ('+');
1140 0         0 for (my $i = 0; $i < 80; $i++) {
1141 0         0 my $c = mix_char_code( substr($crd,$i,1) );
1142 0 0       0 if ($c == -1) {
1143 0         0 $self->{status} = MIX_ERROR;
1144 0         0 $self->{message} = "invalid card: '$crd'";
1145 0         0 return 0;
1146             } else {
1147 0         0 push @w, $c;
1148 0 0       0 if (@w == 6) {
1149 0         0 $self->write_mem($loc++, \@w);
1150 0         0 @w = ('+');
1151             }
1152             }
1153             }
1154              
1155 0         0 my $devstat = @{$self->{devstat}}[U_CARDREADER];
  0         0  
1156 0         0 $devstat->{laststarted} = $self->{time};
1157             # Read one card need 0.1 second
1158 0         0 $devstat->{delay} = 100 * $self->{ms};
1159            
1160 0         0 return 1;
1161             }
1162              
1163              
1164             sub punch_card
1165             {
1166 0     0 0 0 my ($self, $loc) = @_;
1167              
1168 0 0       0 if (!exists $self->{dev}->{+U_CARDPUNCH}) {
1169 0         0 $self->{status} = MIX_ERROR;
1170 0         0 $self->{message} = "missing card punch";
1171 0         0 return;
1172             }
1173              
1174 0         0 my $crd;
1175 0         0 for (my $i = 0; $i < 16; $i++) {
1176 0         0 my @w = $self->read_mem($loc++);
1177 0         0 shift @w;
1178 0         0 while (@w) {
1179 0         0 my $ch = mix_char(shift @w);
1180 0 0       0 if (defined $ch) {
1181 0         0 $crd .= $ch;
1182             } else {
1183 0         0 $crd .= "^";
1184             }
1185             }
1186             }
1187              
1188 0         0 my $dev = $self->{dev}->{+U_CARDPUNCH};
1189 0         0 push @{$dev->{buf}}, $crd;
  0         0  
1190              
1191 0         0 my $devstat = @{$self->{devstat}}[U_CARDPUNCH];
  0         0  
1192 0         0 $devstat->{laststarted} = $self->{time};
1193 0         0 $devstat->{delay} = 500 * $self->{ms}; # Punch 2 cards per second
1194             }
1195              
1196             sub print_line
1197             {
1198 0     0 0 0 my ($self, $loc) = @_;
1199 0         0 my $printer = $self->{dev}->{+U_PRINTER};
1200 0 0       0 if (!defined $printer) {
1201 0         0 $self->{status} = MIX_ERROR;
1202 0         0 $self->{message} = "missing printer";
1203 0         0 return;
1204             }
1205              
1206 0         0 my $page = pop @{$printer->{buf}};
  0         0  
1207 0 0       0 $page = "" if !defined $page;
1208              
1209 0         0 my $line;
1210 0         0 for (my $i = 0; $i < 24; $i++) {
1211 0         0 my @w = $self->read_mem($loc++);
1212 0         0 shift @w;
1213 0         0 while (@w) {
1214 0         0 my $ch = mix_char(shift @w);
1215 0 0       0 if (defined $ch) {
1216 0         0 $line .= $ch;
1217             } else {
1218 0         0 $line .= "^";
1219             }
1220             }
1221             }
1222 0         0 $line =~ s/\s+$//;
1223 0         0 $page .= $line . "\n";
1224 0         0 push @{$printer->{buf}}, $page;
  0         0  
1225              
1226 0         0 my $devstat = @{$self->{devstat}}[U_PRINTER];
  0         0  
1227 0         0 $devstat->{laststarted} = $self->{time};
1228 0         0 $devstat->{delay} = 100 * $self->{ms}; # Print 10 lines per second
1229             }
1230              
1231             sub new_page
1232             {
1233 0     0 0 0 my ($self, $m) = @_;
1234 0         0 my $printer = $self->{dev}->{+U_PRINTER};
1235              
1236 0 0       0 if (!defined $printer) {
1237 0         0 $self->{status} = MIX_ERROR;
1238 0         0 $self->{message} = "missing printer";
1239 0         0 return;
1240             }
1241              
1242 0 0       0 if ($m == 0) {
1243 0         0 push @{$printer->{buf}}, "";
  0         0  
1244             } else {
1245 0         0 $self->{status} = MIX_ERROR;
1246 0         0 $self->{message} = "printer ioctrl error: M should be zero";
1247             }
1248              
1249 0         0 my $devstat = @{$self->{devstat}}[U_PRINTER];
  0         0  
1250 0         0 $devstat->{laststarted} = $self->{time};
1251 0         0 $devstat->{delay} = 10 * $self->{ms};
1252             }
1253              
1254              
1255             sub read_typewriter
1256             {
1257 0     0 0 0 my ($self, $loc) = @_;
1258             # FIXME: use constant
1259 0         0 my $typewriter = $self->{dev}->{19};
1260              
1261 0 0       0 if (!defined $typewriter) {
1262 0         0 $self->{status} = MIX_ERROR;
1263 0         0 $self->{message} = "missing typewriter";
1264 0         0 return 0;
1265             }
1266              
1267 0 0       0 if (!exists($typewriter->{line}))
1268             {
1269 0         0 $self->{status} = MIX_IOWAIT;
1270 0         0 $self->{message} = "need to type a line";
1271 0         0 return 0;
1272             }
1273              
1274 0         0 my $line = $typewriter->{line};
1275             # Pad spaces to make the line has 70 characters
1276 0 0       0 if (length($line)!=70) {
1277 0         0 $line .= " " x (70-length($line));
1278             }
1279 0         0 my @w = ('+');
1280 0         0 for (my $i = 0; $i < 70; $i++) {
1281 0         0 my $c = mix_char_code( substr($line,$i,1) );
1282 0 0       0 if ($c == -1) {
1283 0         0 $self->{status} = MIX_ERROR;
1284 0         0 $self->{message} = "invalid line: '$line'";
1285 0         0 return 0;
1286             } else {
1287 0         0 push @w, $c;
1288 0 0       0 if (@w == 6) {
1289 0         0 $self->write_mem($loc++, \@w);
1290 0         0 @w = ('+');
1291             }
1292             }
1293             }
1294              
1295 0         0 my $devstat = @{$self->{devstat}}[19];
  0         0  
1296 0         0 $devstat->{laststarted} = $self->{time};
1297 0         0 $devstat->{delay} = 100 * $self->{ms}; # Read 10 cards per second
1298            
1299            
1300             }
1301              
1302             sub is_halted
1303             {
1304 0     0 0 0 my $self = shift;
1305 0 0       0 return 0 if $self->{status} == MIX_OK;
1306 0         0 return 1;
1307             }
1308              
1309              
1310             ########################################################################
1311             # Utilities
1312             ########################################################################
1313              
1314             # Input: partial word, for example, (+ 10 20)
1315             # Output: add 0s to fix the word: (+ 0 0 0 10 20)
1316             # For load instructions when reading only part of the fields.
1317             sub fix_word
1318             {
1319 0     0 0 0 my @tmp = @_;
1320 0         0 my $sign = shift @tmp;
1321 0 0 0     0 if ($sign eq '+' || $sign eq '-') {
1322            
1323             } else {
1324 0         0 unshift @tmp, $sign;
1325 0         0 $sign = '+';
1326             }
1327 0         0 while (@tmp != 5) {
1328 0         0 unshift @tmp, 0;
1329             }
1330 0         0 unshift @tmp, $sign;
1331 0         0 return @tmp;
1332             }
1333              
1334             sub neg_word
1335             {
1336 21     21 0 23 my @tmp = @{$_[0]};
  21         75  
1337 21 100       48 if ($tmp[0] eq '-') {
    50          
1338 8         13 $tmp[0] = '+';
1339             } elsif ($tmp[0] eq '+') {
1340 13         16 $tmp[0] = '-';
1341             } else {
1342 0         0 unshift @tmp, '-';
1343             }
1344 21         82 return @tmp;
1345             }
1346              
1347             sub word_to_int
1348             {
1349 18     18 0 24 my ($wref, $m) = @_;
1350 18         21 my $val = 0;
1351            
1352 18 50       39 $m = 64 if (!defined $m);
1353            
1354 18         33 for my $i (1 .. 5) {
1355 90         93 $val = $val * $m + @{$wref}[$i];
  90         135  
1356             }
1357 18 100       24 if (@{$wref}[0] eq '+') {
  18         39  
1358 15         48 return $val;
1359             } else {
1360 3         9 return -$val;
1361             }
1362             }
1363              
1364             sub word_to_uint
1365             {
1366 6     6 0 8 my ($wref, $m) = @_;
1367 6         9 my $val = 0;
1368            
1369 6 50       13 $m = 64 if (!defined $m);
1370            
1371 6         23 for my $i (1 .. 5) {
1372 30         28 $val = $val * $m + @{$wref}[$i];
  30         44  
1373             }
1374 6         15 return $val;
1375             }
1376              
1377             # If overflow return 0;
1378             # If ok, return 1;
1379             sub int_to_word
1380             {
1381 15     15 0 23 my ($val, $wref, $m) = @_;
1382 15         17 my $i = 5;
1383              
1384 15 50       34 $m = 64 if (!defined $m);
1385              
1386 15 50       24 if ($val < 0) {
1387 0         0 @{$wref}[0] = '-';
  0         0  
1388 0         0 $val = -$val;
1389             } else {
1390 15         19 @{$wref}[0] = '+';
  15         28  
1391             }
1392              
1393 15         36 for (; $i > 0; $i--) {
1394 75         81 @{$wref}[$i] = $val % $m;
  75         92  
1395 75         149 $val = int($val/$m);
1396             }
1397 15         37 return $val==0;
1398             }
1399              
1400             my $debug_mode = 0;
1401              
1402             sub debug
1403             {
1404 0 0   0 0 0 return if !$debug_mode;
1405 0         0 print "DEBUG: ";
1406 0         0 print $_ foreach @_;
1407 0         0 print "\n";
1408             }
1409              
1410             my $mix_charset = " ABCDEFGHI^JKLMNOPQR^^STUVWXYZ0123456789.,()+-*/=\$<>@;:'";
1411              
1412             # Return a MIX char by its code.
1413             # valid input: 0 .. 55
1414             # If the input is not in the range above, an `undef' is returned.
1415             sub mix_char
1416             {
1417 10 100 100 10 0 73 return undef if $_[0] < 0 || $_[0] >= length($mix_charset);
1418 8         40 return substr($mix_charset, $_[0], 1);
1419             }
1420              
1421             # Return code for a MIX char
1422             # If not found, return -1.
1423             # Note, char '^' is not a valid char in MIX charset.
1424             sub mix_char_code
1425             {
1426 5 100   5 0 17 return -1 if $_[0] eq "^";
1427 4         23 return index($mix_charset, $_[0]);
1428             }
1429              
1430             1;
1431              
1432             __END__