Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alexbevi
GitHub Repository: alexbevi/BizHawk
Path: blob/master/quicknes/nes_emu/Nes_Core.cpp
2 views
1
2
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
3
4
#include "Nes_Core.h"
5
6
#include <string.h>
7
#include "Nes_Mapper.h"
8
#include "Nes_State.h"
9
10
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
11
can redistribute it and/or modify it under the terms of the GNU Lesser
12
General Public License as published by the Free Software Foundation; either
13
version 2.1 of the License, or (at your option) any later version. This
14
module is distributed in the hope that it will be useful, but WITHOUT ANY
15
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
17
more details. You should have received a copy of the GNU Lesser General
18
Public License along with this module; if not, write to the Free Software
19
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
20
21
#include "blargg_source.h"
22
23
extern const char unsupported_mapper [] = "Unsupported mapper";
24
25
bool const wait_states_enabled = true;
26
bool const single_instruction_mode = false; // for debugging irq/nmi timing issues
27
28
const int unmapped_fill = Nes_Cpu::page_wrap_opcode;
29
30
unsigned const low_ram_size = 0x800;
31
unsigned const low_ram_end = 0x2000;
32
unsigned const sram_end = 0x8000;
33
34
Nes_Core::Nes_Core() : ppu( this )
35
{
36
cart = NULL;
37
impl = NULL;
38
mapper = NULL;
39
memset( &nes, 0, sizeof nes );
40
memset( &joypad, 0, sizeof joypad );
41
}
42
43
blargg_err_t Nes_Core::init()
44
{
45
if ( !impl )
46
{
47
CHECK_ALLOC( impl = BLARGG_NEW impl_t );
48
impl->apu.dmc_reader( read_dmc, this );
49
impl->apu.irq_notifier( apu_irq_changed, this );
50
}
51
52
return 0;
53
}
54
55
void Nes_Core::close()
56
{
57
// check that nothing modified unmapped page
58
#ifndef NDEBUG
59
//if ( cart && mem_differs( impl->unmapped_page, unmapped_fill, sizeof impl->unmapped_page ) )
60
// dprintf( "Unmapped code page was written to\n" );
61
#endif
62
63
cart = NULL;
64
delete mapper;
65
mapper = NULL;
66
67
ppu.close_chr();
68
69
disable_rendering();
70
}
71
72
blargg_err_t Nes_Core::open( Nes_Cart const* new_cart )
73
{
74
close();
75
76
RETURN_ERR( init() );
77
78
mapper = Nes_Mapper::create( new_cart, this );
79
if ( !mapper )
80
return unsupported_mapper;
81
82
RETURN_ERR( ppu.open_chr( new_cart->chr(), new_cart->chr_size() ) );
83
84
cart = new_cart;
85
memset( impl->unmapped_page, unmapped_fill, sizeof impl->unmapped_page );
86
reset( true, true );
87
return 0;
88
}
89
90
Nes_Core::~Nes_Core()
91
{
92
close();
93
delete impl;
94
}
95
96
void Nes_Core::save_state( Nes_State_* out ) const
97
{
98
out->clear();
99
100
out->nes = nes;
101
out->nes_valid = true;
102
103
*out->cpu = cpu::r;
104
out->cpu_valid = true;
105
106
*out->joypad = joypad;
107
out->joypad_valid = true;
108
109
impl->apu.save_state( out->apu );
110
out->apu_valid = true;
111
112
ppu.save_state( out );
113
114
memcpy( out->ram, cpu::low_mem, out->ram_size );
115
out->ram_valid = true;
116
117
out->sram_size = 0;
118
if ( sram_present )
119
{
120
out->sram_size = sizeof impl->sram;
121
memcpy( out->sram, impl->sram, out->sram_size );
122
}
123
124
out->mapper->size = 0;
125
mapper->save_state( *out->mapper );
126
out->mapper_valid = true;
127
}
128
129
void Nes_Core::save_state( Nes_State* out ) const
130
{
131
save_state( reinterpret_cast<Nes_State_*>(out) );
132
}
133
134
void Nes_Core::load_state( Nes_State_ const& in )
135
{
136
require( cart );
137
138
disable_rendering();
139
error_count = 0;
140
141
if ( in.nes_valid )
142
nes = in.nes;
143
144
// always use frame count
145
ppu.burst_phase = 0; // avoids shimmer when seeking to same time over and over
146
nes.frame_count = in.nes.frame_count;
147
if ( (frame_count_t) nes.frame_count == invalid_frame_count )
148
nes.frame_count = 0;
149
150
if ( in.cpu_valid )
151
cpu::r = *in.cpu;
152
153
if ( in.joypad_valid )
154
joypad = *in.joypad;
155
156
if ( in.apu_valid )
157
{
158
impl->apu.load_state( *in.apu );
159
// prevent apu from running extra at beginning of frame
160
impl->apu.end_frame( -(int) nes.timestamp / ppu_overclock );
161
}
162
else
163
{
164
impl->apu.reset();
165
}
166
167
ppu.load_state( in );
168
169
if ( in.ram_valid )
170
memcpy( cpu::low_mem, in.ram, in.ram_size );
171
172
sram_present = false;
173
if ( in.sram_size )
174
{
175
sram_present = true;
176
memcpy( impl->sram, in.sram, min( (int) in.sram_size, (int) sizeof impl->sram ) );
177
enable_sram( true ); // mapper can override (read-only, unmapped, etc.)
178
}
179
180
if ( in.mapper_valid ) // restore last since it might reconfigure things
181
mapper->load_state( *in.mapper );
182
}
183
184
void Nes_Core::enable_prg_6000()
185
{
186
sram_writable = 0;
187
sram_readable = 0;
188
lrom_readable = 0x8000;
189
}
190
191
void Nes_Core::enable_sram( bool b, bool read_only )
192
{
193
sram_writable = 0;
194
if ( b )
195
{
196
if ( !sram_present )
197
{
198
sram_present = true;
199
memset( impl->sram, 0xFF, impl->sram_size );
200
}
201
sram_readable = sram_end;
202
if ( !read_only )
203
sram_writable = sram_end;
204
cpu::map_code( 0x6000, impl->sram_size, impl->sram );
205
}
206
else
207
{
208
sram_readable = 0;
209
for ( int i = 0; i < impl->sram_size; i += cpu::page_size )
210
cpu::map_code( 0x6000 + i, cpu::page_size, impl->unmapped_page );
211
}
212
}
213
214
// Unmapped memory
215
216
#if !defined (NDEBUG) && 0
217
static nes_addr_t last_unmapped_addr;
218
#endif
219
220
void Nes_Core::log_unmapped( nes_addr_t addr, int data )
221
{
222
#if !defined (NDEBUG) && 0
223
if ( last_unmapped_addr != addr )
224
{
225
last_unmapped_addr = addr;
226
if ( data < 0 )
227
dprintf( "Read unmapped %04X\n", addr );
228
else
229
dprintf( "Write unmapped %04X <- %02X\n", addr, data );
230
}
231
#endif
232
}
233
234
inline void Nes_Core::cpu_adjust_time( int n )
235
{
236
ppu_2002_time -= n;
237
cpu_time_offset += n;
238
cpu::reduce_limit( n );
239
}
240
241
// I/O and sound
242
243
int Nes_Core::read_dmc( void* data, nes_addr_t addr )
244
{
245
Nes_Core* emu = (Nes_Core*) data;
246
int result = *emu->cpu::get_code( addr );
247
if ( wait_states_enabled )
248
emu->cpu_adjust_time( 4 );
249
return result;
250
}
251
252
void Nes_Core::apu_irq_changed( void* emu )
253
{
254
((Nes_Core*) emu)->irq_changed();
255
}
256
257
void Nes_Core::write_io( nes_addr_t addr, int data )
258
{
259
// sprite dma
260
if ( addr == 0x4014 )
261
{
262
ppu.dma_sprites( clock(), cpu::get_code( data * 0x100 ) );
263
cpu_adjust_time( 513 );
264
return;
265
}
266
267
// joypad strobe
268
if ( addr == 0x4016 )
269
{
270
// if strobe goes low, latch data
271
if ( joypad.w4016 & 1 & ~data )
272
{
273
joypad_read_count++;
274
joypad.joypad_latches [0] = current_joypad [0];
275
joypad.joypad_latches [1] = current_joypad [1];
276
}
277
joypad.w4016 = data;
278
return;
279
}
280
281
// apu
282
if ( unsigned (addr - impl->apu.start_addr) <= impl->apu.end_addr - impl->apu.start_addr )
283
{
284
impl->apu.write_register( clock(), addr, data );
285
if ( wait_states_enabled )
286
{
287
if ( addr == 0x4010 || (addr == 0x4015 && (data & 0x10)) )
288
{
289
impl->apu.run_until( clock() + 1 );
290
event_changed();
291
}
292
}
293
return;
294
}
295
296
#ifndef NDEBUG
297
log_unmapped( addr, data );
298
#endif
299
}
300
301
int Nes_Core::read_io( nes_addr_t addr )
302
{
303
if ( (addr & 0xFFFE) == 0x4016 )
304
{
305
// to do: to aid with recording, doesn't emulate transparent latch,
306
// so a game that held strobe at 1 and read $4016 or $4017 would not get
307
// the current A status as occurs on a NES
308
int32_t result = joypad.joypad_latches [addr & 1];
309
if ( !(joypad.w4016 & 1) )
310
joypad.joypad_latches [addr & 1] = result >> 1; // ASR is intentional
311
return result & 1;
312
}
313
314
if ( addr == Nes_Apu::status_addr )
315
return impl->apu.read_status( clock() );
316
317
#ifndef NDEBUG
318
log_unmapped( addr );
319
#endif
320
321
return addr >> 8; // simulate open bus
322
}
323
324
// CPU
325
326
const int irq_inhibit_mask = 0x04;
327
328
nes_addr_t Nes_Core::read_vector( nes_addr_t addr )
329
{
330
byte const* p = cpu::get_code( addr );
331
return p [1] * 0x100 + p [0];
332
}
333
334
void Nes_Core::reset( bool full_reset, bool erase_battery_ram )
335
{
336
require( cart );
337
338
if ( full_reset )
339
{
340
cpu::reset( impl->unmapped_page );
341
cpu_time_offset = -1;
342
clock_ = 0;
343
344
// Low RAM
345
memset( cpu::low_mem, 0xFF, low_ram_size );
346
cpu::low_mem [8] = 0xf7;
347
cpu::low_mem [9] = 0xef;
348
cpu::low_mem [10] = 0xdf;
349
cpu::low_mem [15] = 0xbf;
350
351
// SRAM
352
lrom_readable = 0;
353
sram_present = true;
354
enable_sram( false );
355
if ( !cart->has_battery_ram() || erase_battery_ram )
356
memset( impl->sram, 0xFF, impl->sram_size );
357
358
joypad.joypad_latches [0] = 0;
359
joypad.joypad_latches [1] = 0;
360
361
nes.frame_count = 0;
362
}
363
364
// to do: emulate partial reset
365
366
ppu.reset( full_reset );
367
impl->apu.reset();
368
369
mapper->reset();
370
371
cpu::r.pc = read_vector( 0xFFFC );
372
cpu::r.sp = 0xfd;
373
cpu::r.a = 0;
374
cpu::r.x = 0;
375
cpu::r.y = 0;
376
cpu::r.status = irq_inhibit_mask;
377
nes.timestamp = 0;
378
error_count = 0;
379
}
380
381
void Nes_Core::vector_interrupt( nes_addr_t vector )
382
{
383
cpu::push_byte( cpu::r.pc >> 8 );
384
cpu::push_byte( cpu::r.pc & 0xFF );
385
cpu::push_byte( cpu::r.status | 0x20 ); // reserved bit is set
386
387
cpu_adjust_time( 7 );
388
cpu::r.status |= irq_inhibit_mask;
389
cpu::r.pc = read_vector( vector );
390
}
391
392
inline nes_time_t Nes_Core::earliest_irq( nes_time_t present )
393
{
394
return min( impl->apu.earliest_irq( present ), mapper->next_irq( present ) );
395
}
396
397
void Nes_Core::irq_changed()
398
{
399
cpu_set_irq_time( earliest_irq( cpu_time() ) );
400
}
401
402
inline nes_time_t Nes_Core::ppu_frame_length( nes_time_t present )
403
{
404
nes_time_t t = ppu.frame_length();
405
if ( t > present )
406
return t;
407
408
ppu.render_bg_until( clock() ); // to do: why this call to clock() rather than using present?
409
return ppu.frame_length();
410
}
411
412
inline nes_time_t Nes_Core::earliest_event( nes_time_t present )
413
{
414
// PPU frame
415
nes_time_t t = ppu_frame_length( present );
416
417
// DMC
418
if ( wait_states_enabled )
419
t = min( t, impl->apu.next_dmc_read_time() + 1 );
420
421
// NMI
422
t = min( t, ppu.nmi_time() );
423
424
if ( single_instruction_mode )
425
t = min( t, present + 1 );
426
427
return t;
428
}
429
430
void Nes_Core::event_changed()
431
{
432
cpu_set_end_time( earliest_event( cpu_time() ) );
433
}
434
435
#undef NES_EMU_CPU_HOOK
436
#ifndef NES_EMU_CPU_HOOK
437
#define NES_EMU_CPU_HOOK( cpu, end_time ) cpu::run( end_time )
438
#endif
439
440
nes_time_t Nes_Core::emulate_frame_()
441
{
442
Nes_Cpu::result_t last_result = cpu::result_cycles;
443
int extra_instructions = 0;
444
while ( true )
445
{
446
// Add DMC wait-states to CPU time
447
if ( wait_states_enabled )
448
{
449
impl->apu.run_until( cpu_time() );
450
clock_ = cpu_time_offset;
451
}
452
453
nes_time_t present = cpu_time();
454
if ( present >= ppu_frame_length( present ) )
455
{
456
if ( ppu.nmi_time() <= present )
457
{
458
// NMI will occur next, so delayed CLI and SEI don't need to be handled.
459
// If NMI will occur normally ($2000.7 and $2002.7 set), let it occur
460
// next frame, otherwise vector it now.
461
462
if ( !(ppu.w2000 & 0x80 & ppu.r2002) )
463
{
464
dprintf( "vectored NMI at end of frame\n" );
465
vector_interrupt( 0xFFFA );
466
present += 7;
467
}
468
return present;
469
}
470
471
if ( extra_instructions > 2 )
472
{
473
check( last_result != cpu::result_sei && last_result != cpu::result_cli );
474
check( ppu.nmi_time() >= 0x10000 || (ppu.w2000 & 0x80 & ppu.r2002) );
475
return present;
476
}
477
478
if ( last_result != cpu::result_cli && last_result != cpu::result_sei &&
479
(ppu.nmi_time() >= 0x10000 || (ppu.w2000 & 0x80 & ppu.r2002)) )
480
return present;
481
482
dprintf( "Executing extra instructions for frame\n" );
483
extra_instructions++; // execute one more instruction
484
}
485
486
// NMI
487
if ( present >= ppu.nmi_time() )
488
{
489
ppu.acknowledge_nmi();
490
vector_interrupt( 0xFFFA );
491
last_result = cpu::result_cycles; // most recent sei/cli won't be delayed now
492
}
493
494
// IRQ
495
nes_time_t irq_time = earliest_irq( present );
496
cpu_set_irq_time( irq_time );
497
if ( present >= irq_time && (!(cpu::r.status & irq_inhibit_mask) ||
498
last_result == cpu::result_sei) )
499
{
500
if ( last_result != cpu::result_cli )
501
{
502
//dprintf( "%6d IRQ vectored\n", present );
503
mapper->run_until( present );
504
vector_interrupt( 0xFFFE );
505
}
506
else
507
{
508
// CLI delays IRQ
509
cpu_set_irq_time( present + 1 );
510
check( false ); // rare event
511
}
512
}
513
514
// CPU
515
nes_time_t end_time = earliest_event( present );
516
if ( extra_instructions )
517
end_time = present + 1;
518
unsigned long cpu_error_count = cpu::error_count();
519
last_result = NES_EMU_CPU_HOOK( cpu, end_time - cpu_time_offset - 1 );
520
cpu_adjust_time( cpu::time() );
521
clock_ = cpu_time_offset;
522
error_count += cpu::error_count() - cpu_error_count;
523
}
524
}
525
526
nes_time_t Nes_Core::emulate_frame()
527
{
528
require( cart );
529
530
joypad_read_count = 0;
531
532
cpu_time_offset = ppu.begin_frame( nes.timestamp ) - 1;
533
ppu_2002_time = 0;
534
clock_ = cpu_time_offset;
535
536
check( cpu_time() == (int) nes.timestamp / ppu_overclock );
537
check( 1 && impl->apu.last_time == cpu_time() );
538
539
// TODO: clean this fucking mess up
540
impl->apu.run_until_( emulate_frame_() );
541
clock_ = cpu_time_offset;
542
impl->apu.run_until_( cpu_time() );
543
check( 2 && clock_ == cpu_time_offset );
544
check( 3 && impl->apu.last_time == cpu_time() );
545
546
nes_time_t ppu_frame_length = ppu.frame_length();
547
nes_time_t length = cpu_time();
548
nes.timestamp = ppu.end_frame( length );
549
mapper->end_frame( length );
550
impl->apu.end_frame( ppu_frame_length );
551
check( 4 && cpu_time() == length );
552
553
check( 5 && impl->apu.last_time == length - ppu_frame_length );
554
555
disable_rendering();
556
nes.frame_count++;
557
558
return ppu_frame_length;
559
}
560
561
void Nes_Core::add_mapper_intercept( nes_addr_t addr, unsigned size, bool read, bool write )
562
{
563
require( addr >= 0x4000 );
564
require( addr + size <= 0x10000 );
565
int end = (addr + size + (page_size - 1)) >> page_bits;
566
for ( int page = addr >> page_bits; page < end; page++ )
567
{
568
data_reader_mapped [page] |= read;
569
data_writer_mapped [page] |= write;
570
}
571
}
572
573
574