1 |
package ACME::6502; |
2 |
|
3 |
use warnings FATAL => 'all'; |
4 |
use strict; |
5 |
use Carp; |
6 |
use TieMem; |
7 |
use Class::Std; |
8 |
|
9 |
use version; our $VERSION = qv('0.0.6'); |
10 |
|
11 |
# CPU flags |
12 |
use constant { |
13 |
N => 0x80, |
14 |
V => 0x40, |
15 |
R => 0x20, |
16 |
B => 0x10, |
17 |
D => 0x08, |
18 |
I => 0x04, |
19 |
Z => 0x02, |
20 |
C => 0x01 |
21 |
}; |
22 |
|
23 |
use constant FLAGS => 'NVRBDIZC'; |
24 |
|
25 |
# Other CPU constants |
26 |
use constant { |
27 |
STACK => 0x0100, |
28 |
BREAK => 0xFFFE |
29 |
}; |
30 |
|
31 |
# Opcode to thunk into perlspace |
32 |
use constant { |
33 |
ESCAPE_OP => 0x0B, |
34 |
ESCAPE_SIG => 0xAD |
35 |
}; |
36 |
|
37 |
my %cpu : ATTR; |
38 |
|
39 |
|
40 |
=head2 BUILD |
41 |
|
42 |
=head2 AUTOMETHOD |
43 |
|
44 |
=cut |
45 |
|
46 |
sub BUILD { |
47 |
my ($self, $id, $args) = @_; |
48 |
|
49 |
confess "no vram?" unless $args->{vram}; |
50 |
|
51 |
my @mem; |
52 |
my @ram = (0) x 65536; |
53 |
tie @mem, 'TieMem', $args; |
54 |
@ram = (); |
55 |
|
56 |
my @os; |
57 |
my ($a, $x, $y, $s, $p, $pc) = (0) x 6; |
58 |
my $mmap; |
59 |
|
60 |
# Page used to allocate vector thunks |
61 |
my $jumptab = $args->{jumptab} || 0xFA00; |
62 |
|
63 |
my $bad_inst = sub { |
64 |
croak sprintf("Bad instruction at &%04x (&%02x)\n", |
65 |
$pc - 1, $mem[$pc - 1]); |
66 |
}; |
67 |
|
68 |
my @zn = (Z, (0) x 127, (N) x 128); |
69 |
|
70 |
my $inst = sub { |
71 |
my $src = join("\n", @_); |
72 |
my $cr = eval "sub { @_ }"; |
73 |
confess "$@" if $@; |
74 |
return $cr; |
75 |
}; |
76 |
|
77 |
my @decode = ( |
78 |
$inst->( # 00 BRK |
79 |
_push('($pc + 1) >> 8', '($pc + 1)'), |
80 |
_push('$p | B'), |
81 |
'$p = $p | I & ~D;', |
82 |
_jmp_i(BREAK) |
83 |
), |
84 |
$inst->(_ora(_zpix())), # 01 ORA (zp, x) |
85 |
$bad_inst, # 02 |
86 |
$bad_inst, # 03 |
87 |
$inst->(_tsb(_zp())), # 04 TSB zp |
88 |
$inst->(_ora(_zp())), # 05 ORA zp |
89 |
$inst->(_asl(_zp())), # 06 ASL zp |
90 |
$bad_inst, # 07 |
91 |
$inst->(_push('$p | R')), # 08 PHP |
92 |
$inst->(_ora(_imm())), # 09 ORA #imm |
93 |
$inst->(_asl(_acc())), # 0A ASL A |
94 |
$bad_inst, # 0B |
95 |
$inst->(_tsb(_abs())), # 0C TSB zp |
96 |
$inst->(_ora(_abs())), # 0D ORA abs |
97 |
$inst->(_asl(_abs())), # 0E ASL abs |
98 |
$bad_inst, # 0F BBR0 rel |
99 |
$inst->(_bfz(_rel(), N)), # 10 BPL rel |
100 |
$inst->(_ora(_zpiy())), # 11 ORA (zp), y |
101 |
$inst->(_ora(_zpi())), # 12 ORA (zp) |
102 |
$bad_inst, # 13 |
103 |
$inst->(_trb(_zpi())), # 14 TRB (zp) |
104 |
$inst->(_ora(_zpx())), # 15 ORA zp, x |
105 |
$inst->(_asl(_zpx())), # 16 ASL zp, x |
106 |
$bad_inst, # 17 |
107 |
$inst->('$p &= ~C;'), # 18 CLC |
108 |
$inst->(_ora(_absy())), # 19 ORA abs, y |
109 |
$inst->(_inc(_acc())), # 1A INC A |
110 |
$bad_inst, # 1B |
111 |
$inst->(_trb(_abs())), # 1C TRB abs |
112 |
$inst->(_ora(_absx())), # 1D ORA abs, x |
113 |
$inst->(_asl(_absx())), # 1E ASL abs, x |
114 |
$bad_inst, # 1F BBR1 rel |
115 |
$inst->( # 20 JSR |
116 |
_push('($pc + 1) >> 8', '($pc + 1)'), |
117 |
_jmp() |
118 |
), |
119 |
$inst->(_and(_zpix())), # 21 AND (zp, x) |
120 |
$bad_inst, # 22 |
121 |
$bad_inst, # 23 |
122 |
$inst->(_bit(_zp())), # 24 BIT zp |
123 |
$inst->(_and(_zp())), # 25 AND zp |
124 |
$inst->(_rol(_zp())), # 26 ROL zp |
125 |
$bad_inst, # 27 |
126 |
$inst->(_pop_p()), # 28 PLP |
127 |
$inst->(_and(_imm())), # 29 AND #imm |
128 |
$inst->(_rol(_acc())), # 2A ROL A |
129 |
$bad_inst, # 2B |
130 |
$inst->(_bit(_abs())), # 2C BIT abs |
131 |
$inst->(_and(_abs())), # 2D AND abs |
132 |
$inst->(_rol(_abs())), # 2E ROL abs |
133 |
$bad_inst, # 2F BBR2 rel |
134 |
$inst->(_bfnz(_rel(), N)), # 30 BPL rel |
135 |
$inst->(_and(_zpiy())), # 31 AND (zp), y |
136 |
$inst->(_and(_zpi())), # 32 AND (zp) |
137 |
$bad_inst, # 33 |
138 |
$inst->(_bit(_zpx())), # 34 BIT zp, x |
139 |
$inst->(_and(_zpx())), # 35 AND zp, x |
140 |
$inst->(_rol(_zpx())), # 36 ROL zp, x |
141 |
$bad_inst, # 37 |
142 |
$inst->('$p |= C;'), # 38 SEC |
143 |
$inst->(_and(_absy())), # 39 AND abs, y |
144 |
$inst->(_dec(_acc())), # 3A DEC A |
145 |
$bad_inst, # 3B |
146 |
$inst->(_bit(_absx())), # 3C BIT abs, x |
147 |
$inst->(_and(_absx())), # 3D AND abs, x |
148 |
$inst->(_rol(_absx())), # 3E ROL abs, x |
149 |
$bad_inst, # 3F BBR3 rel |
150 |
$inst->(_pop('$p'), _rts()), # 40 RTI |
151 |
$inst->(_eor(_zpix())), # 41 EOR (zp, x) |
152 |
$bad_inst, # 42 |
153 |
$bad_inst, # 43 |
154 |
$bad_inst, # 44 |
155 |
$inst->(_eor(_zp())), # 45 EOR zp |
156 |
$inst->(_lsr(_zp())), # 46 LSR zp |
157 |
$bad_inst, # 47 |
158 |
$inst->(_push('$a')), # 48 PHA |
159 |
$inst->(_eor(_imm())), # 49 EOR imm |
160 |
$inst->(_lsr(_acc())), # 4A LSR A |
161 |
$bad_inst, # 4B |
162 |
$inst->(_jmp()), # 4C JMP abs |
163 |
$inst->(_eor(_abs())), # 4D EOR abs |
164 |
$inst->(_lsr(_abs())), # 4E LSR abs |
165 |
$bad_inst, # 4F BBR4 rel |
166 |
$inst->(_bfz(_rel(), V)), # 50 BVC rel |
167 |
$inst->(_eor(_zpiy())), # 51 EOR (zp), y |
168 |
$inst->(_eor(_zpi())), # 52 EOR (zp) |
169 |
$bad_inst, # 53 |
170 |
$bad_inst, # 54 |
171 |
$inst->(_eor(_zpx())), # 55 EOR zp, x |
172 |
$inst->(_lsr(_zpx())), # 56 LSR zp, x |
173 |
$bad_inst, # 57 |
174 |
$inst->('$p &= ~I;'), # 58 CLI |
175 |
$inst->(_eor(_absy())), # 59 EOR abs, y |
176 |
$inst->(_push('$y')), # 5A PHY |
177 |
$bad_inst, # 5B |
178 |
$bad_inst, # 5C |
179 |
$inst->(_eor(_absx())), # 5D EOR abs, x |
180 |
$inst->(_lsr(_absx())), # 5E LSR abs, x |
181 |
$bad_inst, # 5F BBR5 rel |
182 |
$inst->(_rts()), # 60 RTS |
183 |
$inst->(_adc(_zpx())), # 61 ADC zp, x |
184 |
$bad_inst, # 62 |
185 |
$bad_inst, # 63 |
186 |
$inst->(_sto(_zp(), '0')), # 64 STZ zp |
187 |
$inst->(_adc(_zp())), # 65 ADC zp |
188 |
$inst->(_ror(_zp())), # 66 ROR zp |
189 |
$bad_inst, # 67 |
190 |
$inst->(_pop('$a'), _status('$a')), # 68 PLA |
191 |
$inst->(_adc(_imm())), # 69 ADC #imm |
192 |
$inst->(_ror(_acc())), # 6A ROR A |
193 |
$bad_inst, # 6B |
194 |
$inst->(_jmpi()), # 6C JMP (abs) |
195 |
$inst->(_adc(_abs())), # 6D ADC abs |
196 |
$inst->(_ror(_abs())), # 6E ROR abs |
197 |
$bad_inst, # 6F BBR6 rel |
198 |
$inst->(_bfnz(_rel(), V)), # 70 BVS rel |
199 |
$inst->(_adc(_zpiy())), # 71 ADC (zp), y |
200 |
$inst->(_adc(_zpi())), # 72 ADC (zp) |
201 |
$bad_inst, # 73 |
202 |
$inst->(_sto(_zpx(), '0')), # 74 STZ zp, x |
203 |
$inst->(_adc(_zpx())), # 75 ADC zp, x |
204 |
$inst->(_adc(_zpx())), # 76 ROR zp, x |
205 |
$bad_inst, # 77 |
206 |
$inst->('$p |= I;'), # 78 STI |
207 |
$inst->(_adc(_absy())), # 79 ADC abs, y |
208 |
$inst->(_pop('$y'), _status('$y')), # 7A PLY |
209 |
$bad_inst, # 7B |
210 |
$inst->(_jmpix()), # 7C JMP (abs, x) |
211 |
$inst->(_adc(_absx())), # 7D ADC abs, x |
212 |
$inst->(_ror(_absx())), # 7E ROR abs, x |
213 |
$bad_inst, # 7F BBR7 rel |
214 |
$inst->(_bra(_rel())), # 80 BRA rel |
215 |
$inst->(_sto(_zpix(), '$a')), # 81 STA (zp, x) |
216 |
$bad_inst, # 82 |
217 |
$bad_inst, # 83 |
218 |
$inst->(_sto(_zp(), '$y')), # 84 STY zp |
219 |
$inst->(_sto(_zp(), '$a')), # 85 STA zp |
220 |
$inst->(_sto(_zp(), '$x')), # 86 STX zp |
221 |
$bad_inst, # 87 |
222 |
$inst->(_dec(('', '$y'))), # 88 DEY |
223 |
$inst->(_bit(_imm())), # 89 BIT #imm |
224 |
$inst->('$a = $x;' . _status('$a')), # 8A TXA |
225 |
$bad_inst, # 8B |
226 |
$inst->(_sto(_abs(), '$y')), # 8C STY abs |
227 |
$inst->(_sto(_abs(), '$a')), # 8D STA abs |
228 |
$inst->(_sto(_abs(), '$x')), # 8E STX abs |
229 |
$bad_inst, # 8F BBS0 rel |
230 |
$inst->(_bfz(_rel(), C)), # 90 BCC rel |
231 |
$inst->(_sto(_zpiy(), '$a')), # 91 STA (zp), y |
232 |
$inst->(_sto(_zpi(), '$a')), # 92 STA (zp) |
233 |
$bad_inst, # 93 |
234 |
$inst->(_sto(_zpx(), '$y')), # 94 STY zp, x |
235 |
$inst->(_sto(_zpx(), '$a')), # 95 STA zp, x |
236 |
$inst->(_sto(_zpy(), '$x')), # 96 STX zp, y |
237 |
$bad_inst, # 97 |
238 |
$inst->('$a = $y;' . _status('$a')), # 98 TYA |
239 |
$inst->(_sto(_absy(), '$a')), # 99 STA abs, y |
240 |
$inst->('$s = $x;'), # 9A TXS |
241 |
$bad_inst, # 9B |
242 |
$inst->(_sto(_abs(), '0')), # 9C STZ abs |
243 |
$inst->(_sto(_absx(), '$a')), # 9D STA abs, x |
244 |
$inst->(_sto(_absx(), '0')), # 9E STZ abs, x |
245 |
$bad_inst, # 9F BBS1 rel |
246 |
$inst->(_lod(_imm(), '$y')), # A0 LDY #imm |
247 |
$inst->(_lod(_zpix(), '$a')), # A1 LDA (zp, x) |
248 |
$inst->(_lod(_imm(), '$x')), # A2 LDX #imm |
249 |
$bad_inst, # A3 |
250 |
$inst->(_lod(_zp(), '$y')), # A4 LDY zp |
251 |
$inst->(_lod(_zp(), '$a')), # A5 LDA zp |
252 |
$inst->(_lod(_zp(), '$x')), # A6 LDX zp |
253 |
$bad_inst, # A7 |
254 |
$inst->('$y = $a;' . _status('$y')), # A8 TAY |
255 |
$inst->(_lod(_imm(), '$a')), # A9 LDA #imm |
256 |
$inst->('$x = $a;' . _status('$x')), # AA TAX |
257 |
$bad_inst, # AB |
258 |
$inst->(_lod(_abs(), '$y')), # AC LDY abs |
259 |
$inst->(_lod(_abs(), '$a')), # AD LDA abs |
260 |
$inst->(_lod(_abs(), '$x')), # AE LDX abs |
261 |
$bad_inst, # AF BBS2 rel |
262 |
$inst->(_bfnz(_rel(), C)), # B0 BCS rel |
263 |
$inst->(_lod(_zpiy(), '$a')), # B1 LDA (zp), y |
264 |
$inst->(_lod(_zpi(), '$a')), # B2 LDA (zp) |
265 |
$bad_inst, # B3 |
266 |
$inst->(_lod(_zpx(), '$y')), # B4 LDY zp, x |
267 |
$inst->(_lod(_zpx(), '$a')), # B5 LDA zp, x |
268 |
$inst->(_lod(_zpy(), '$x')), # B6 LDX zp, y |
269 |
$bad_inst, # B7 |
270 |
$inst->('$p &= ~V;'), # B8 CLV |
271 |
$inst->(_lod(_absy(), '$a')), # B9 LDA abs, y |
272 |
$inst->('$x = $s;'), # BA TSX |
273 |
$bad_inst, # BB |
274 |
$inst->(_lod(_absx(), '$y')), # BC LDY abs, x |
275 |
$inst->(_lod(_absx(), '$a')), # BD LDA abs, x |
276 |
$inst->(_lod(_absy(), '$x')), # BE LDX abs, y |
277 |
$bad_inst, # BF BBS3 rel |
278 |
$inst->(_cmp(_imm(), '$y')), # C0 CPY #imm |
279 |
$inst->(_cmp(_zpix(), '$a')), # C1 CMP (zp, x) |
280 |
$bad_inst, # C2 |
281 |
$bad_inst, # C3 |
282 |
$inst->(_cmp(_zp(), '$y')), # C4 CPY zp |
283 |
$inst->(_cmp(_zp(), '$a')), # C5 CMP zp |
284 |
$inst->(_dec(_zp())), # C6 DEC zp |
285 |
$bad_inst, # C7 |
286 |
$inst->(_inc(('', '$y'))), # C8 INY |
287 |
$inst->(_cmp(_imm(), '$a')), # C9 CMP #imm |
288 |
$inst->(_dec(('', '$x'))), # CA DEX |
289 |
$bad_inst, # CB |
290 |
$inst->(_cmp(_abs(), '$y')), # CC CPY abs |
291 |
$inst->(_cmp(_abs(), '$a')), # CD CMP abs |
292 |
$inst->(_dec(_abs())), # CE DEC abs |
293 |
$bad_inst, # CF BBS4 rel |
294 |
$inst->(_bfz(_rel(), Z)), # D0 BNE rel |
295 |
$inst->(_cmp(_zpiy(), '$a')), # D1 CMP (zp), y |
296 |
$inst->(_cmp(_zpi(), '$a')), # D2 CMP (zp) |
297 |
$bad_inst, # D3 |
298 |
$bad_inst, # D4 |
299 |
$inst->(_cmp(_zpx(), '$a')), # D5 CMP zp, x |
300 |
$inst->(_dec(_zpx())), # D6 DEC zp, x |
301 |
$bad_inst, # D7 |
302 |
$inst->('$p &= ~D;'), # D8 CLD |
303 |
$inst->(_cmp(_absy(), '$a')), # D9 CMP abs, y |
304 |
$inst->(_push('$x')), # DA PHX |
305 |
$bad_inst, # DB |
306 |
$bad_inst, # DC |
307 |
$inst->(_cmp(_absx(), '$a')), # DD CMP abs, x |
308 |
$inst->(_dec(_absx())), # DE DEC abs, x |
309 |
$bad_inst, # DF BBS5 rel |
310 |
$inst->(_cmp(_imm(), '$x')), # E0 CPX #imm |
311 |
$inst->(_sbc(_zpix(), '$a')), # E1 SBC (zp, x) |
312 |
$bad_inst, # E2 |
313 |
$bad_inst, # E3 |
314 |
$inst->(_cmp(_zp(), '$x')), # E4 CPX zp |
315 |
$inst->(_sbc(_zp())), # E5 SBC zp |
316 |
$inst->(_inc(_zp())), # E6 INC zp |
317 |
$bad_inst, # E7 |
318 |
$inst->(_inc(('', '$x'))), # E8 INX |
319 |
$inst->(_sbc(_imm())), # E9 SBC #imm |
320 |
$inst->(), # EA NOP |
321 |
$bad_inst, # EB |
322 |
$inst->(_cmp(_abs(), '$x')), # EC CPX abs |
323 |
$inst->(_sbc(_abs())), # ED SBC abs |
324 |
$inst->(_inc(_abs())), # EE INC abs |
325 |
$bad_inst, # EF BBS6 rel |
326 |
$inst->(_bfnz(_rel(), Z)), # F0 BEQ rel |
327 |
$inst->(_sbc(_zpiy())), # F1 SBC (zp), y |
328 |
$inst->(_sbc(_zpi())), # F2 SBC (zp) |
329 |
$bad_inst, # F3 |
330 |
$bad_inst, # F4 |
331 |
$inst->(_sbc(_zpx())), # F5 SBC zp, x |
332 |
$inst->(_inc(_zpx())), # F6 INC zp, x |
333 |
$bad_inst, # F7 |
334 |
$inst->('$p |= D;'), # F8 SED |
335 |
$inst->(_sbc(_absy())), # F9 SBC abs, y |
336 |
$inst->(_pop('$x'), _status('$x')), # FA PLX |
337 |
$bad_inst, # FB |
338 |
$bad_inst, # FC |
339 |
$inst->(_sbc(_absx())), # FD SBC abs, x |
340 |
$inst->(_inc(_absx())), # FE INC abs, x |
341 |
$bad_inst # FF BBS7 rel |
342 |
); |
343 |
|
344 |
confess "Escape handler opcode not available" |
345 |
unless $decode[ESCAPE_OP] == $bad_inst; |
346 |
|
347 |
# Patch in the OS escape op handler |
348 |
$decode[ESCAPE_OP] = sub { |
349 |
if ($mem[$pc] != ESCAPE_SIG) { |
350 |
$bad_inst->(); |
351 |
} else { |
352 |
$pc += 2; |
353 |
$self->call_os($mem[$pc - 1]); |
354 |
} |
355 |
}; |
356 |
|
357 |
$cpu{$id} = { |
358 |
set_jumptab => sub { |
359 |
$jumptab = shift; |
360 |
}, |
361 |
|
362 |
read_str => sub { |
363 |
my $addr = shift; |
364 |
my $str = ''; |
365 |
while ($mem[$addr] != 0x0D) { |
366 |
$str .= chr($mem[$addr++]); |
367 |
} |
368 |
return $str; |
369 |
}, |
370 |
|
371 |
read_chunk => sub { |
372 |
my ($from, $to) = @_; |
373 |
return pack('C*', @mem[$from .. $to - 1]); |
374 |
}, |
375 |
|
376 |
write_chunk => sub { |
377 |
my ($addr, $chunk) = @_; |
378 |
my $len = length($chunk); |
379 |
splice @mem, $addr, $len, unpack('C*', $chunk); |
380 |
}, |
381 |
|
382 |
read_8 => sub { |
383 |
my $addr = shift; |
384 |
return $mem[$addr]; |
385 |
}, |
386 |
|
387 |
write_8 => sub { |
388 |
my ($addr, $val) = @_; |
389 |
$mem[$addr] = $val; |
390 |
}, |
391 |
|
392 |
read_16 => sub { |
393 |
my $addr = shift; |
394 |
return $mem[$addr] | ($mem[$addr + 1] << 8); |
395 |
}, |
396 |
|
397 |
write_16 => sub { |
398 |
my ($addr, $val) = @_; |
399 |
$mem[$addr + 0] = ($val >> 0) & 0xFF; |
400 |
$mem[$addr + 1] = ($val >> 8) & 0xFF; |
401 |
}, |
402 |
|
403 |
read_32 => sub { |
404 |
my $addr = shift; |
405 |
return $mem[$addr] | ($mem[$addr + 1] << 8) | |
406 |
($mem[$addr + 2] << 16) | ($mem[$addr + 3] << 32); |
407 |
}, |
408 |
|
409 |
write_32 => sub { |
410 |
my ($addr, $val) = @_; |
411 |
$mem[$addr + 0] = ($val >> 0) & 0xFF; |
412 |
$mem[$addr + 1] = ($val >> 8) & 0xFF; |
413 |
$mem[$addr + 2] = ($val >> 16) & 0xFF; |
414 |
$mem[$addr + 3] = ($val >> 24) & 0xFF; |
415 |
}, |
416 |
|
417 |
run => sub { |
418 |
my $ic = shift; |
419 |
my $cb = shift; |
420 |
if (defined($cb)) { |
421 |
while ($ic-- > 0) { |
422 |
$cb->($pc, $mem[$pc], $a, $x, $y, $s, $p); |
423 |
$decode[$mem[$pc++]]->(); |
424 |
$pc &&= 0xffff; |
425 |
} |
426 |
} else { |
427 |
while ($ic-- > 0) { |
428 |
my $op = $mem[$pc]; |
429 |
# printf "pc: %04x %s\n", $pc, $op; |
430 |
$pc++; |
431 |
$decode[$op]->(); |
432 |
} |
433 |
} |
434 |
}, |
435 |
|
436 |
load_rom => sub { |
437 |
my ($f, $a) = @_; |
438 |
open my $fh, '<', $f or croak "Can't read $f ($!)\n"; |
439 |
binmode $fh; |
440 |
my $sz = -s $fh; |
441 |
sysread $fh, my $buf, $sz |
442 |
or croak "Error reading $f ($!)\n"; |
443 |
if ( $a < 0 ) { |
444 |
$buf = substr($buf, -$a); |
445 |
$a = 0; |
446 |
} |
447 |
$self->write_chunk($a, $buf); |
448 |
}, |
449 |
|
450 |
poke_code => sub { |
451 |
my $addr = shift; |
452 |
$mem[$addr++] = $_ for @_; |
453 |
}, |
454 |
|
455 |
make_vector => sub { |
456 |
my ($call, $vec, $func) = @_; |
457 |
$mem[$call + 0] = 0x6C; # JMP (indirect) |
458 |
$mem[$call + 1] = $vec & 0xFF; |
459 |
$mem[$call + 2] = ($vec >> 8) & 0xFF; |
460 |
|
461 |
my $addr = $jumptab; |
462 |
$mem[$jumptab++] = ESCAPE_OP; |
463 |
$mem[$jumptab++] = ESCAPE_SIG; |
464 |
$mem[$jumptab++] = $func; |
465 |
$mem[$jumptab++] = 0x60; |
466 |
|
467 |
$mem[$vec + 0] = $addr & 0xFF; |
468 |
$mem[$vec + 1] = ($addr >> 8) & 0xFF; |
469 |
}, |
470 |
|
471 |
get_state => sub { |
472 |
return ($a, $x, $y, $s, $p, $pc); |
473 |
}, |
474 |
|
475 |
get_xy => sub { |
476 |
return $x || ($y << 8); |
477 |
}, |
478 |
|
479 |
set_xy => sub { |
480 |
my $v = shift; |
481 |
$x = $v & 0xFF; |
482 |
$y = ($v >> 8) & 0xFF; |
483 |
}, |
484 |
|
485 |
decode_flags => sub { |
486 |
my $f = shift; |
487 |
my $b = 0x80; |
488 |
my $n = FLAGS; |
489 |
my $desc = ''; |
490 |
while ($n) { |
491 |
$desc .= ($f & $b) ? substr($n, 0, 1) : '-'; |
492 |
$n = substr($n, 1); |
493 |
$b >>= 1; |
494 |
} |
495 |
return $desc; |
496 |
}, |
497 |
|
498 |
mmap => sub { |
499 |
my ( $addr, $sub ) = @_; |
500 |
confess "callback not sub" unless ref($sub) eq 'CODE'; |
501 |
$mmap->{$addr} = $sub; |
502 |
} |
503 |
}; |
504 |
|
505 |
# Generate register accessors. |
506 |
for my $reg (qw(a x y s p pc)) { |
507 |
$cpu{$id}->{"get_$reg"} = eval "sub { return \$$reg; }"; |
508 |
$cpu{$id}->{"set_$reg"} = eval "sub { \$$reg = \$_[0]; }"; |
509 |
} |
510 |
} |
511 |
|
512 |
sub AUTOMETHOD { |
513 |
my ($self, $id, @args) = @_; |
514 |
my $methname = $_; |
515 |
|
516 |
if (exists($cpu{$id}->{$methname})) { |
517 |
return sub { |
518 |
return $cpu{$id}->{$methname}->(@args); |
519 |
} |
520 |
} |
521 |
|
522 |
return; |
523 |
} |
524 |
|
525 |
sub call_os { |
526 |
my $self = shift; |
527 |
my $id = ident($self); |
528 |
croak "call_os() not supported"; |
529 |
} |
530 |
|
531 |
# Functions that generate code fragments |
532 |
sub _push { |
533 |
my $r = ''; |
534 |
for (@_) { |
535 |
$r .= |
536 |
'$mem[STACK + $s] = (' . $_ |
537 |
. ') & 0xFF; $s = ($s - 1) & 0xFF;' . "\n"; |
538 |
} |
539 |
return $r; |
540 |
} |
541 |
|
542 |
sub _pop { |
543 |
my $r = ''; |
544 |
for (@_) { |
545 |
$r .= |
546 |
'$s = ($s + 1) & 0xFF; ' . $_ |
547 |
. ' = $mem[STACK + $s];' . "\n"; |
548 |
} |
549 |
return $r; |
550 |
} |
551 |
|
552 |
sub _pop_p { |
553 |
return '$s = ($s + 1) & 0xFF; $p = $mem[STACK + $s] | R | B;' |
554 |
. "\n"; |
555 |
} |
556 |
|
557 |
# Addressing modes return a list containing setup code, lvalue |
558 |
sub _zpix { |
559 |
return ( |
560 |
'my $ea = $mem[$pc++] + $x; ' |
561 |
. '$ea = $mem[$ea & 0xFF] | ($mem[($ea + 1) & 0xFF] << 8)' |
562 |
. ";\n", |
563 |
'$mem[$ea]' |
564 |
); |
565 |
} |
566 |
|
567 |
sub _zpi { |
568 |
return ( |
569 |
'my $ea = $mem[$pc++]; ' |
570 |
. '$ea = $mem[$ea & 0xFF] | ($mem[($ea + 1) & 0xFF] << 8)' |
571 |
. ";\n", |
572 |
'$mem[$ea]' |
573 |
); |
574 |
} |
575 |
|
576 |
sub _zpiy { |
577 |
return ( |
578 |
'my $ea = $mem[$pc++]; ' |
579 |
. '$ea = ($mem[$ea & 0xFF] | ($mem[($ea + 1) & 0xFF] << 8)) + $y' |
580 |
. ";\n", |
581 |
'$mem[$ea]' |
582 |
); |
583 |
} |
584 |
|
585 |
sub _zp { |
586 |
return ('my $ea = $mem[$pc++];' . "\n", '$mem[$ea]'); |
587 |
} |
588 |
|
589 |
sub _zpx { |
590 |
return ('my $ea = ($mem[$pc++] + $x) & 0xFF;' . "\n", '$mem[$ea]'); |
591 |
} |
592 |
|
593 |
sub _zpy { |
594 |
return ('my $ea = ($mem[$pc++] + $y) & 0xFF;' . "\n", '$mem[$ea]'); |
595 |
} |
596 |
|
597 |
sub _abs { |
598 |
return ('my $ea = $mem[$pc] | ($mem[$pc+1] << 8); $pc += 2;' . "\n", |
599 |
'$mem[$ea]'); |
600 |
} |
601 |
|
602 |
sub _absx { |
603 |
return ( |
604 |
'my $ea = ($mem[$pc] | ($mem[$pc+1] << 8)) + $x; $pc += 2;' |
605 |
. "\n", |
606 |
'$mem[$ea]' |
607 |
); |
608 |
} |
609 |
|
610 |
sub _absy { |
611 |
return ( |
612 |
'my $ea = ($mem[$pc] | ($mem[$pc+1] << 8)) + $y; $pc += 2;' |
613 |
. "\n", |
614 |
'$mem[$ea]' |
615 |
); |
616 |
} |
617 |
|
618 |
sub _imm { |
619 |
return ('my $v = $mem[$pc++];' . "\n", '$v'); |
620 |
} |
621 |
|
622 |
sub _acc { |
623 |
return ('', '$a'); |
624 |
} |
625 |
|
626 |
sub _rel { |
627 |
# Doesn't return an lvalue |
628 |
return ('my $t = $mem[$pc++];' . "\n", |
629 |
'($pc + $t - (($t & 0x80) ? 0x100 : 0))'); |
630 |
} |
631 |
|
632 |
sub _status { |
633 |
my $reg = shift || '$a'; |
634 |
return '$p = ($p & ~(N | Z) | $zn[' . $reg . ']);' . "\n"; |
635 |
} |
636 |
|
637 |
sub _ora { |
638 |
return $_[0] . '$a |= ' . $_[1] . ";\n" . _status(); |
639 |
} |
640 |
|
641 |
sub _and { |
642 |
return $_[0] . '$a &= ' . $_[1] . ";\n" . _status(); |
643 |
} |
644 |
|
645 |
sub _eor { |
646 |
return $_[0] . '$a ^= ' . $_[1] . ";\n" . _status(); |
647 |
} |
648 |
|
649 |
sub _bit { |
650 |
return $_[0] |
651 |
. '$p = ($p & ~(N|V)) | (' |
652 |
. $_[1] |
653 |
. ' & (N|V));' . "\n" |
654 |
. 'if (($a & ' |
655 |
. $_[1] |
656 |
. ') == 0) { $p |= Z; } else { $p &= ~Z; }' . "\n"; |
657 |
} |
658 |
|
659 |
sub _asl { |
660 |
return $_[0] |
661 |
. 'my $w = (' |
662 |
. $_[1] |
663 |
. ') << 1; ' . "\n" |
664 |
. 'if ($w & 0x100) { $p |= C; $w &= ~0x100; } else { $p &= ~C; }' |
665 |
. "\n" |
666 |
. _status('$w') |
667 |
. $_[1] |
668 |
. ' = $w;' . "\n"; |
669 |
} |
670 |
|
671 |
sub _lsr { |
672 |
return $_[0] |
673 |
. 'my $w = ' |
674 |
. $_[1] . ";\n" |
675 |
. 'if (($w & 1) != 0) { $p |= C; } else { $p &= ~C; }' . "\n" |
676 |
. '$w >>= 1;' . "\n" |
677 |
. _status('$w') |
678 |
. $_[1] |
679 |
. ' = $w;' . "\n"; |
680 |
} |
681 |
|
682 |
sub _rol { |
683 |
return $_[0] |
684 |
. 'my $w = (' |
685 |
. $_[1] |
686 |
. ' << 1) | ($p & C);' . "\n" |
687 |
. 'if ($w >= 0x100) { $p |= C; $w -= 0x100; } else { $p &= ~C; };' |
688 |
. "\n" |
689 |
. _status('$w') |
690 |
. $_[1] |
691 |
. ' = $w;' . "\n"; |
692 |
} |
693 |
|
694 |
sub _ror { |
695 |
return $_[0] |
696 |
. 'my $w = ' |
697 |
. $_[1] |
698 |
. ' | (($p & C) << 8);' . "\n" |
699 |
. 'if (($w & 1) != 0) { $p |= C; } else { $p &= ~C; }' . "\n" |
700 |
. '$w >>= 1;' . "\n" |
701 |
. _status('$w') |
702 |
. $_[1] |
703 |
. ' = $w;' . "\n"; |
704 |
} |
705 |
|
706 |
sub _sto { |
707 |
return $_[0] . "$_[1] = $_[2];\n"; |
708 |
} |
709 |
|
710 |
sub _lod { |
711 |
return $_[0] . "$_[2] = $_[1];\n" . _status($_[2]); |
712 |
} |
713 |
|
714 |
sub _cmp { |
715 |
return $_[0] |
716 |
. 'my $w = ' |
717 |
. $_[2] . ' - ' |
718 |
. $_[1] . ";\n" |
719 |
. 'if ($w < 0) { $w += 0x100; $p &= ~C; } else { $p |= C; }' |
720 |
. "\n" |
721 |
. _status('$w'); |
722 |
} |
723 |
|
724 |
sub _tsb { |
725 |
return 'croak "TSB not supported\n";' . "\n"; |
726 |
} |
727 |
|
728 |
sub _trb { |
729 |
return 'croak "TRB not supported\n";' . "\n"; |
730 |
} |
731 |
|
732 |
sub _inc { |
733 |
return $_[0] |
734 |
. $_[1] . ' = (' |
735 |
. $_[1] |
736 |
. ' + 1) & 0xFF;' . "\n" |
737 |
. _status($_[1]); |
738 |
} |
739 |
|
740 |
sub _dec { |
741 |
return $_[0] |
742 |
. $_[1] . ' = (' |
743 |
. $_[1] |
744 |
. ' + 0xFF) & 0xFF;' . "\n" |
745 |
. _status($_[1]); |
746 |
} |
747 |
|
748 |
sub _adc { |
749 |
return $_[0] |
750 |
. 'my $w = ' |
751 |
. $_[1] . ";\n" |
752 |
. 'if ($p & D) {' . "\n" |
753 |
. 'my $lo = ($a & 0x0F) + ($w & 0x0F) + ($p & C);' . "\n" |
754 |
. 'if ($lo > 9) { $lo += 6; }' . "\n" |
755 |
. 'my $hi = ($a >> 4) + ( $w >> 4) + ($lo > 15 ? 1 : 0);' . "\n" |
756 |
. '$a = ($lo & 0x0F) | ($hi << 4);' . "\n" |
757 |
. '$p = ($p & ~C) | ($hi > 15 ? C : 0);' . "\n" |
758 |
. '} else {' . "\n" |
759 |
. 'my $lo = $a + $w + ($p & C);' . "\n" |
760 |
. '$p &= ~(N | V | Z | C);' . "\n" |
761 |
. '$p |= (~($a ^ $w) & ($a ^ $lo) & 0x80 ? V : 0) | ($lo & 0x100 ? C : 0);' |
762 |
. "\n" |
763 |
. '$a = $lo & 0xFF;' . "\n" |
764 |
. _status() . '}' . "\n"; |
765 |
} |
766 |
|
767 |
sub _sbc { |
768 |
return $_[0] |
769 |
. 'my $w = ' |
770 |
. $_[1] . ";\n" |
771 |
. 'if ($p & D) {' . "\n" |
772 |
. 'my $lo = ($a & 0x0F) - ($w & 0x0F) - (~$p & C);' . "\n" |
773 |
. 'if ($lo & 0x10) { $lo -= 6; }' . "\n" |
774 |
. 'my $hi = ($a >> 4) - ($w >> 4) - (($lo & 0x10) >> 4);' . "\n" |
775 |
. 'if ($hi & 0x10) { $hi -= 6; }' . "\n" |
776 |
. '$a = ($lo & 0x0F) | ($hi << 4);' . "\n" |
777 |
. '$p = ($p & ~C) | ($hi > 15 ? 0 : C);' . "\n" |
778 |
. '} else {' . "\n" |
779 |
. 'my $lo = $a - $w - (~$p & C);' . "\n" |
780 |
. '$p &= ~(N | V | Z | C);' . "\n" |
781 |
. '$p |= (($a ^ $w) & ($a ^ $lo) & 0x80 ? V : 0) | ($lo & 0x100 ? 0 : C);' |
782 |
. "\n" |
783 |
. '$a = $lo & 0xFF;' . "\n" |
784 |
. _status() . '}' . "\n"; |
785 |
} |
786 |
|
787 |
sub _bra { |
788 |
return $_[0] . '$pc = ' . $_[1] . ";\n"; |
789 |
} |
790 |
|
791 |
sub _bfz { |
792 |
return $_[0] |
793 |
. 'if (($p & ' |
794 |
. $_[2] |
795 |
. ') == 0) { $pc = ' |
796 |
. $_[1] . '; }' . "\n"; |
797 |
} |
798 |
|
799 |
sub _bfnz { |
800 |
return $_[0] |
801 |
. 'if (($p & ' |
802 |
. $_[2] |
803 |
. ') != 0) { $pc = ' |
804 |
. $_[1] . '; }' . "\n"; |
805 |
} |
806 |
|
807 |
sub _jmp_i { |
808 |
my $a = shift; |
809 |
return '$pc = $mem[' . $a |
810 |
. '] | ($mem[' |
811 |
. $a |
812 |
. ' + 1] << 8);' . "\n"; |
813 |
} |
814 |
|
815 |
sub _jmp { |
816 |
return _jmp_i('$pc'); |
817 |
} |
818 |
|
819 |
sub _jmpi { |
820 |
return 'my $w = $mem[$pc] | ($mem[$pc + 1] << 8); ' . _jmp_i('$w'); |
821 |
} |
822 |
|
823 |
sub _jmpix { |
824 |
return 'my $w = ($mem[$pc] | ($mem[$pc + 1] << 8)) + $x; ' |
825 |
. _jmp_i('$w'); |
826 |
} |
827 |
|
828 |
sub _rts { |
829 |
return 'my ($lo, $hi); ' |
830 |
. _pop('$lo') |
831 |
. _pop('$hi') |
832 |
. '$pc = ($lo | ($hi << 8)) + 1;' . "\n"; |
833 |
} |
834 |
|
835 |
1; |
836 |
__END__ |
837 |
|
838 |
=head1 NAME |
839 |
|
840 |
Acme::6502 - Pure Perl 65C02 simulator. |
841 |
|
842 |
=head1 VERSION |
843 |
|
844 |
This document describes Acme::6502 version 0.0.6 |
845 |
|
846 |
=head1 SYNOPSIS |
847 |
|
848 |
use Acme::6502; |
849 |
|
850 |
my $cpu = Acme::6502->new(); |
851 |
|
852 |
# Set start address |
853 |
$cpu->set_pc(0x8000); |
854 |
|
855 |
# Load ROM image |
856 |
$cpu->load_rom('myrom.rom', 0x8000); |
857 |
|
858 |
# Run for 1,000,000 instructions then return |
859 |
$cpu->run(1_000_000); |
860 |
|
861 |
=head1 DESCRIPTION |
862 |
|
863 |
Imagine the nightmare scenario: your boss tells you about a legacy |
864 |
system you have to support. How bad could it be? COBOL? Fortran? Worse: |
865 |
it's an embedded 6502 system run by a family of squirrels (see Dilberts |
866 |
passim). Fortunately there's a pure Perl 6502 emulator that works so |
867 |
well the squirrels will never know the difference. |
868 |
|
869 |
=head1 INTERFACE |
870 |
|
871 |
=over |
872 |
|
873 |
=item C<call_os( $vec_number )> |
874 |
|
875 |
Subclass to provide OS entry points. OS vectors are installed by calling |
876 |
C<make_vector>. When the vector is called C<call_os()> will be called |
877 |
with the vector number. |
878 |
|
879 |
=item C<get_a()> |
880 |
|
881 |
Read the current value of the processor A register (accumulator). |
882 |
|
883 |
=item C<get_p()> |
884 |
|
885 |
Read the current value of the processor status register. |
886 |
|
887 |
=item C<get_pc()> |
888 |
|
889 |
Read the current value of the program counter. |
890 |
|
891 |
=item C<get_s()> |
892 |
|
893 |
Read the current value of the stack pointer. |
894 |
|
895 |
=item C<get_x()> |
896 |
|
897 |
Read the current value of the processor X index register. |
898 |
|
899 |
=item C<get_y()> |
900 |
|
901 |
Read the current value of the processor X index register. |
902 |
|
903 |
=item C<get_xy()> |
904 |
|
905 |
Read the value of X and Y as a sixteen bit number. X forms the lower 8 |
906 |
bits of the value and Y forms the upper 8 bits. |
907 |
|
908 |
=item C<get_state()> |
909 |
|
910 |
Returns an array containing the values of the A, X, Y, S, P and SP. |
911 |
|
912 |
=item C<set_a( $value )> |
913 |
|
914 |
Set the value of the processor A register (accumulator). |
915 |
|
916 |
=item C<set_p( $value )> |
917 |
|
918 |
Set the value of the processor status register. |
919 |
|
920 |
=item C<set_pc( $value )> |
921 |
|
922 |
Set the value of the program counter. |
923 |
|
924 |
=item C<set_s( $value )> |
925 |
|
926 |
Set the value of the stack pointer. |
927 |
|
928 |
=item C<set_x( $value )> |
929 |
|
930 |
Set the value of the X index register. |
931 |
|
932 |
=item C<set_y( $value )> |
933 |
|
934 |
Set the value of the Y index register. |
935 |
|
936 |
=item C<set_xy( $value )> |
937 |
|
938 |
Set the value of the X and Y registers to the specified sixteen bit |
939 |
number. X gets the lower 8 bits, Y gets the upper 8 bits. |
940 |
|
941 |
=item C<set_jumptab( $addr )> |
942 |
|
943 |
Set the address of the block of memory that will be used to hold the |
944 |
thunk blocks that correspond with vectored OS entry points. Each thunk |
945 |
takes four bytes. |
946 |
|
947 |
=item C<load_rom( $filename, $addr )> |
948 |
|
949 |
Load a ROM image at the specified address. |
950 |
|
951 |
=item C<make_vector( $jmp_addr, $vec_addr, $vec_number )> |
952 |
|
953 |
Make a vectored entry point for an emulated OS. C<$jmp_addr> is the |
954 |
address where an indirect JMP instruction (6C) will be placed, |
955 |
C<$vec_addr> is the address of the vector and C<$vec_number> will be |
956 |
passed to C<call_os> when the OS call is made. |
957 |
|
958 |
=item C<poke_code( $addr, @bytes )> |
959 |
|
960 |
Poke code directly at the specified address. |
961 |
|
962 |
=item C<read_8( $addr )> |
963 |
|
964 |
Read a byte at the specified address. |
965 |
|
966 |
=item C<read_16( $addr )> |
967 |
|
968 |
Read a sixteen bit (low, high) word at the specified address. |
969 |
|
970 |
=item C<read_32( $addr )> |
971 |
|
972 |
Read a 32 bit word at the specified address. |
973 |
|
974 |
=item C<read_chunk( $start, $end )> |
975 |
|
976 |
Read a chunk of data from C<$start> to C<$end> - 1 into a string. |
977 |
|
978 |
=item C<read_str( $addr )> |
979 |
|
980 |
Read a carriage return terminated (0x0D) string from the |
981 |
specified address. |
982 |
|
983 |
=item C<run( $count [, $callback ] )> |
984 |
|
985 |
Execute the specified number of instructions and return. Optionally a |
986 |
callback may be provided in which case it will be called before each |
987 |
instruction is executed: |
988 |
|
989 |
my $cb = sub { |
990 |
my ($pc, $inst, $a, $x, $y, $s, $p) = @_; |
991 |
# Maybe output trace info |
992 |
} |
993 |
|
994 |
$cpu->run(100, $cb); |
995 |
|
996 |
=item C<write_8( $addr, $value )> |
997 |
|
998 |
Write the byte at the specified address. |
999 |
|
1000 |
=item C<write_16( $addr, $value )> |
1001 |
|
1002 |
Write a sixteen bit (low, high) value at the specified address. |
1003 |
|
1004 |
=item C<write_32( $addr, $value )> |
1005 |
|
1006 |
Write a 32 bit value at the specified address. |
1007 |
|
1008 |
=item C<write_chunk( $addr, $string )> |
1009 |
|
1010 |
Write a chunk of data to memory. |
1011 |
|
1012 |
=back |
1013 |
|
1014 |
=head1 DIAGNOSTICS |
1015 |
|
1016 |
=over |
1017 |
|
1018 |
=item C<< Bad instruction at %s (%s) >> |
1019 |
|
1020 |
The emulator hit an illegal 6502 instruction. |
1021 |
|
1022 |
=back |
1023 |
|
1024 |
=head1 CONFIGURATION AND ENVIRONMENT |
1025 |
|
1026 |
Acme::6502 requires no configuration files or environment variables. |
1027 |
|
1028 |
=head1 DEPENDENCIES |
1029 |
|
1030 |
C<Acme::6502> needs C<Class::Std>. |
1031 |
|
1032 |
=head1 INCOMPATIBILITIES |
1033 |
|
1034 |
None reported. |
1035 |
|
1036 |
=head1 BUGS AND LIMITATIONS |
1037 |
|
1038 |
Doesn't have support for hardware emulation hooks - so memory mapped I/O |
1039 |
is out of the question until someone fixes it. |
1040 |
|
1041 |
Please report any bugs or feature requests to |
1042 |
C<bug-acme-6502@rt.cpan.org>, or through the web interface at |
1043 |
L<http://rt.cpan.org>. |
1044 |
|
1045 |
=head1 AUTHOR |
1046 |
|
1047 |
Andy Armstrong C<< <andy@hexten.net> >> |
1048 |
|
1049 |
=head1 LICENCE AND COPYRIGHT |
1050 |
|
1051 |
Copyright (c) 2006, Andy Armstrong C<< <andy@hexten.net> >>. All rights reserved. |
1052 |
|
1053 |
This module is free software; you can redistribute it and/or |
1054 |
modify it under the same terms as Perl itself. See L<perlartistic>. |
1055 |
|
1056 |
=head1 DISCLAIMER OF WARRANTY |
1057 |
|
1058 |
BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY |
1059 |
FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN |
1060 |
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES |
1061 |
PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER |
1062 |
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
1063 |
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE |
1064 |
ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH |
1065 |
YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL |
1066 |
NECESSARY SERVICING, REPAIR, OR CORRECTION. |
1067 |
|
1068 |
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
1069 |
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR |
1070 |
REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE |
1071 |
LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, |
1072 |
OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE |
1073 |
THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING |
1074 |
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A |
1075 |
FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF |
1076 |
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF |
1077 |
SUCH DAMAGES. |