trunk/src/mame/drivers/missile.c
| r19700 | r19701 | |
| 370 | 370 | UINT8 m_irq_state; |
| 371 | 371 | UINT8 m_ctrld; |
| 372 | 372 | UINT8 m_flipscreen; |
| 373 | | UINT8 m_madsel_delay; |
| 374 | | UINT16 m_madsel_lastpc; |
| 373 | UINT64 m_madsel_lastcycles; |
| 375 | 374 | |
| 376 | 375 | DECLARE_WRITE8_MEMBER(missile_w); |
| 377 | 376 | DECLARE_READ8_MEMBER(missile_r); |
| r19700 | r19701 | |
| 382 | 381 | virtual void machine_start(); |
| 383 | 382 | virtual void machine_reset(); |
| 384 | 383 | UINT32 screen_update_missile(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect); |
| 384 | |
| 385 | inline int scanline_to_v(int scanline); |
| 386 | inline int v_to_scanline(int v); |
| 387 | inline void schedule_next_irq(int curv); |
| 388 | inline bool get_madsel(); |
| 389 | inline offs_t get_bit3_addr(offs_t pixaddr); |
| 390 | void write_vram(address_space &space, offs_t address, UINT8 data); |
| 391 | UINT8 read_vram(address_space &space, offs_t address); |
| 392 | |
| 385 | 393 | TIMER_CALLBACK_MEMBER(clock_irq); |
| 386 | 394 | TIMER_CALLBACK_MEMBER(adjust_cpu_speed); |
| 387 | 395 | }; |
| r19700 | r19701 | |
| 406 | 414 | * |
| 407 | 415 | *************************************/ |
| 408 | 416 | |
| 409 | | INLINE int scanline_to_v(missile_state *state, int scanline) |
| 417 | int missile_state::scanline_to_v(int scanline) |
| 410 | 418 | { |
| 411 | 419 | /* since the vertical sync counter counts backwards when flipped, |
| 412 | 420 | this function returns the current effective V value, given |
| 413 | 421 | that vpos() only counts forward */ |
| 414 | | return state->m_flipscreen ? (256 - scanline) : scanline; |
| 422 | return m_flipscreen ? (256 - scanline) : scanline; |
| 415 | 423 | } |
| 416 | 424 | |
| 417 | 425 | |
| 418 | | INLINE int v_to_scanline(missile_state *state, int v) |
| 426 | int missile_state::v_to_scanline(int v) |
| 419 | 427 | { |
| 420 | 428 | /* same as a above, but the opposite transformation */ |
| 421 | | return state->m_flipscreen ? (256 - v) : v; |
| 429 | return m_flipscreen ? (256 - v) : v; |
| 422 | 430 | } |
| 423 | 431 | |
| 424 | 432 | |
| 425 | | INLINE void schedule_next_irq(running_machine &machine, int curv) |
| 433 | void missile_state::schedule_next_irq(int curv) |
| 426 | 434 | { |
| 427 | | missile_state *state = machine.driver_data<missile_state>(); |
| 428 | 435 | /* IRQ = /32V, clocked by /16V ^ flip */ |
| 429 | 436 | /* When not flipped, clocks on 0, 64, 128, 192 */ |
| 430 | 437 | /* When flipped, clocks on 16, 80, 144, 208 */ |
| 431 | | if (state->m_flipscreen) |
| 438 | if (m_flipscreen) |
| 432 | 439 | curv = ((curv - 32) & 0xff) | 0x10; |
| 433 | 440 | else |
| 434 | 441 | curv = ((curv + 32) & 0xff) & ~0x10; |
| 435 | 442 | |
| 436 | 443 | /* next one at the start of this scanline */ |
| 437 | | state->m_irq_timer->adjust(machine.primary_screen->time_until_pos(v_to_scanline(state, curv)), curv); |
| 444 | m_irq_timer->adjust(machine().primary_screen->time_until_pos(v_to_scanline(curv)), curv); |
| 438 | 445 | } |
| 439 | 446 | |
| 440 | 447 | |
| r19700 | r19701 | |
| 447 | 454 | m_maincpu->set_input_line(0, m_irq_state ? ASSERT_LINE : CLEAR_LINE); |
| 448 | 455 | |
| 449 | 456 | /* force an update while we're here */ |
| 450 | | machine().primary_screen->update_partial(v_to_scanline(this, curv)); |
| 457 | machine().primary_screen->update_partial(v_to_scanline(curv)); |
| 451 | 458 | |
| 452 | 459 | /* find the next edge */ |
| 453 | | schedule_next_irq(machine(), curv); |
| 460 | schedule_next_irq(curv); |
| 454 | 461 | } |
| 455 | 462 | |
| 456 | 463 | |
| 457 | 464 | CUSTOM_INPUT_MEMBER(missile_state::get_vblank) |
| 458 | 465 | { |
| 459 | | missile_state *state = machine().driver_data<missile_state>(); |
| 460 | | int v = scanline_to_v(state, machine().primary_screen->vpos()); |
| 466 | int v = scanline_to_v(machine().primary_screen->vpos()); |
| 461 | 467 | return v < 24; |
| 462 | 468 | } |
| 463 | 469 | |
| r19700 | r19701 | |
| 481 | 487 | |
| 482 | 488 | /* scanline for the next run */ |
| 483 | 489 | curv ^= 224; |
| 484 | | m_cpu_timer->adjust(machine().primary_screen->time_until_pos(v_to_scanline(this, curv)), curv); |
| 490 | m_cpu_timer->adjust(machine().primary_screen->time_until_pos(v_to_scanline(curv)), curv); |
| 485 | 491 | } |
| 486 | 492 | |
| 487 | 493 | |
| 488 | | DIRECT_UPDATE_MEMBER(missile_state::missile_direct_handler) |
| 489 | | { |
| 490 | | /* offset accounts for lack of A15 decoding */ |
| 491 | | int offset = address & 0x8000; |
| 492 | | address &= 0x7fff; |
| 493 | | |
| 494 | | /* RAM? */ |
| 495 | | if (address < 0x4000) |
| 496 | | { |
| 497 | | direct.explicit_configure(0x0000 | offset, 0x3fff | offset, 0x3fff, m_videoram); |
| 498 | | return ~0; |
| 499 | | } |
| 500 | | |
| 501 | | /* ROM? */ |
| 502 | | else if (address >= 0x5000) |
| 503 | | { |
| 504 | | direct.explicit_configure(0x5000 | offset, 0x7fff | offset, 0x7fff, direct.space().machine().root_device().memregion("maincpu")->base() + 0x5000); |
| 505 | | return ~0; |
| 506 | | } |
| 507 | | |
| 508 | | /* anything else falls through */ |
| 509 | | return address; |
| 510 | | } |
| 511 | | |
| 512 | | |
| 513 | 494 | void missile_state::machine_start() |
| 514 | 495 | { |
| 515 | 496 | |
| r19700 | r19701 | |
| 517 | 498 | m_writeprom = memregion("proms")->base(); |
| 518 | 499 | m_flipscreen = 0; |
| 519 | 500 | |
| 520 | | /* set up an opcode base handler since we use mapped handlers for RAM */ |
| 521 | | address_space &space = m_maincpu->space(AS_PROGRAM); |
| 522 | | space.set_direct_update_handler(direct_update_delegate(FUNC(missile_state::missile_direct_handler), this)); |
| 523 | | |
| 524 | 501 | /* create a timer to speed/slow the CPU */ |
| 525 | 502 | m_cpu_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(missile_state::adjust_cpu_speed),this)); |
| 526 | | m_cpu_timer->adjust(machine().primary_screen->time_until_pos(v_to_scanline(this, 0), 0)); |
| 503 | m_cpu_timer->adjust(machine().primary_screen->time_until_pos(v_to_scanline(0), 0)); |
| 527 | 504 | |
| 528 | 505 | /* create a timer for IRQs and set up the first callback */ |
| 529 | 506 | m_irq_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(missile_state::clock_irq),this)); |
| 530 | 507 | m_irq_state = 0; |
| 531 | | schedule_next_irq(machine(), -32); |
| 508 | schedule_next_irq(-32); |
| 532 | 509 | |
| 533 | 510 | /* setup for save states */ |
| 534 | 511 | save_item(NAME(m_irq_state)); |
| 535 | 512 | save_item(NAME(m_ctrld)); |
| 536 | 513 | save_item(NAME(m_flipscreen)); |
| 537 | | save_item(NAME(m_madsel_delay)); |
| 538 | | save_item(NAME(m_madsel_lastpc)); |
| 514 | save_item(NAME(m_madsel_lastcycles)); |
| 539 | 515 | } |
| 540 | 516 | |
| 541 | 517 | |
| r19700 | r19701 | |
| 543 | 519 | { |
| 544 | 520 | m_maincpu->set_input_line(0, CLEAR_LINE); |
| 545 | 521 | m_irq_state = 0; |
| 522 | m_madsel_lastcycles = 0; |
| 546 | 523 | } |
| 547 | 524 | |
| 548 | 525 | |
| 549 | 526 | |
| 550 | 527 | /************************************* |
| 551 | 528 | * |
| 552 | | * VRAM access override |
| 529 | * VRAM access |
| 553 | 530 | * |
| 554 | 531 | *************************************/ |
| 555 | 532 | |
| 556 | | INLINE int get_madsel(address_space &space) |
| 533 | bool missile_state::get_madsel() |
| 557 | 534 | { |
| 558 | | missile_state *state = space.machine().driver_data<missile_state>(); |
| 559 | | UINT16 pc = space.device().safe_pcbase(); |
| 535 | /* the MADSEL signal disables standard address decoding and routes |
| 536 | writes to video RAM; it goes high 5 cycles after an opcode |
| 537 | fetch where the low 5 bits are 0x01 and the IRQ signal is clear. |
| 538 | */ |
| 539 | bool madsel = false; |
| 560 | 540 | |
| 561 | | /* if we're at a different instruction than last time, reset our delay counter */ |
| 562 | | if (pc != state->m_madsel_lastpc) |
| 563 | | state->m_madsel_delay = 0; |
| 564 | | |
| 565 | | /* MADSEL signal disables standard address decoding and routes |
| 566 | | writes to video RAM; it is enabled if the IRQ signal is clear |
| 567 | | and the low 5 bits of the fetched opcode are 0x01 */ |
| 568 | | if (!state->m_irq_state && (space.direct().read_decrypted_byte(pc) & 0x1f) == 0x01) |
| 541 | if (m_madsel_lastcycles) |
| 569 | 542 | { |
| 570 | | /* the MADSEL signal goes high 5 cycles after the opcode is identified; |
| 571 | | this effectively skips the indirect memory read. Since this is difficult |
| 572 | | to do in MAME, we just ignore the first two positive hits on MADSEL |
| 573 | | and only return TRUE on the third or later */ |
| 574 | | state->m_madsel_lastpc = pc; |
| 575 | | return (++state->m_madsel_delay >= 4); |
| 543 | madsel = (m_maincpu->total_cycles() - m_madsel_lastcycles) == 5; |
| 544 | |
| 545 | /* reset the count until next time */ |
| 546 | if (madsel) |
| 547 | m_madsel_lastcycles = 0; |
| 576 | 548 | } |
| 577 | | state->m_madsel_delay = 0; |
| 578 | | return 0; |
| 549 | |
| 550 | return madsel; |
| 579 | 551 | } |
| 580 | 552 | |
| 581 | | |
| 582 | | INLINE offs_t get_bit3_addr(offs_t pixaddr) |
| 553 | offs_t missile_state::get_bit3_addr(offs_t pixaddr) |
| 583 | 554 | { |
| 584 | 555 | /* the 3rd bit of video RAM is scattered about various areas |
| 585 | 556 | we take a 16-bit pixel address here and convert it into |
| r19700 | r19701 | |
| 591 | 562 | } |
| 592 | 563 | |
| 593 | 564 | |
| 594 | | static void write_vram(address_space &space, offs_t address, UINT8 data) |
| 565 | void missile_state::write_vram(address_space &space, offs_t address, UINT8 data) |
| 595 | 566 | { |
| 596 | | missile_state *state = space.machine().driver_data<missile_state>(); |
| 597 | | UINT8 *videoram = state->m_videoram; |
| 598 | 567 | static const UINT8 data_lookup[4] = { 0x00, 0x0f, 0xf0, 0xff }; |
| 599 | 568 | offs_t vramaddr; |
| 600 | 569 | UINT8 vramdata; |
| r19700 | r19701 | |
| 605 | 574 | /* this should only be called if MADSEL == 1 */ |
| 606 | 575 | vramaddr = address >> 2; |
| 607 | 576 | vramdata = data_lookup[data >> 6]; |
| 608 | | vrammask = state->m_writeprom[(address & 7) | 0x10]; |
| 609 | | videoram[vramaddr] = (videoram[vramaddr] & vrammask) | (vramdata & ~vrammask); |
| 577 | vrammask = m_writeprom[(address & 7) | 0x10]; |
| 578 | m_videoram[vramaddr] = (m_videoram[vramaddr] & vrammask) | (vramdata & ~vrammask); |
| 610 | 579 | |
| 611 | 580 | /* 3-bit VRAM writes use an extra clock to write the 3rd bit elsewhere */ |
| 612 | 581 | /* on the schematics, this is the MUSHROOM == 1 case */ |
| r19700 | r19701 | |
| 614 | 583 | { |
| 615 | 584 | vramaddr = get_bit3_addr(address); |
| 616 | 585 | vramdata = -((data >> 5) & 1); |
| 617 | | vrammask = state->m_writeprom[(address & 7) | 0x18]; |
| 618 | | videoram[vramaddr] = (videoram[vramaddr] & vrammask) | (vramdata & ~vrammask); |
| 586 | vrammask = m_writeprom[(address & 7) | 0x18]; |
| 587 | m_videoram[vramaddr] = (m_videoram[vramaddr] & vrammask) | (vramdata & ~vrammask); |
| 619 | 588 | |
| 620 | 589 | /* account for the extra clock cycle */ |
| 621 | 590 | space.device().execute().adjust_icount(-1); |
| r19700 | r19701 | |
| 623 | 592 | } |
| 624 | 593 | |
| 625 | 594 | |
| 626 | | static UINT8 read_vram(address_space &space, offs_t address) |
| 595 | UINT8 missile_state::read_vram(address_space &space, offs_t address) |
| 627 | 596 | { |
| 628 | | missile_state *state = space.machine().driver_data<missile_state>(); |
| 629 | | UINT8 *videoram = state->m_videoram; |
| 630 | 597 | offs_t vramaddr; |
| 631 | 598 | UINT8 vramdata; |
| 632 | 599 | UINT8 vrammask; |
| r19700 | r19701 | |
| 637 | 604 | /* this should only be called if MADSEL == 1 */ |
| 638 | 605 | vramaddr = address >> 2; |
| 639 | 606 | vrammask = 0x11 << (address & 3); |
| 640 | | vramdata = videoram[vramaddr] & vrammask; |
| 607 | vramdata = m_videoram[vramaddr] & vrammask; |
| 641 | 608 | if ((vramdata & 0xf0) == 0) |
| 642 | 609 | result &= ~0x80; |
| 643 | 610 | if ((vramdata & 0x0f) == 0) |
| r19700 | r19701 | |
| 649 | 616 | { |
| 650 | 617 | vramaddr = get_bit3_addr(address); |
| 651 | 618 | vrammask = 1 << (address & 7); |
| 652 | | vramdata = videoram[vramaddr] & vrammask; |
| 619 | vramdata = m_videoram[vramaddr] & vrammask; |
| 653 | 620 | if (vramdata == 0) |
| 654 | 621 | result &= ~0x20; |
| 655 | 622 | |
| r19700 | r19701 | |
| 713 | 680 | { |
| 714 | 681 | UINT8 *videoram = m_videoram; |
| 715 | 682 | |
| 716 | | /* if we're in MADSEL mode, write to video RAM */ |
| 717 | | if (get_madsel(space)) |
| 683 | /* if this is a MADSEL cycle, write to video RAM */ |
| 684 | if (get_madsel()) |
| 718 | 685 | { |
| 719 | 686 | write_vram(space, offset, data); |
| 720 | 687 | return; |
| r19700 | r19701 | |
| 775 | 742 | UINT8 *videoram = m_videoram; |
| 776 | 743 | UINT8 result = 0xff; |
| 777 | 744 | |
| 778 | | /* if we're in MADSEL mode, read from video RAM */ |
| 779 | | if (get_madsel(space)) |
| 745 | /* if this is a MADSEL cycle, read from video RAM */ |
| 746 | if (get_madsel()) |
| 780 | 747 | return read_vram(space, offset); |
| 781 | 748 | |
| 782 | 749 | /* otherwise, strip A15 and handle manually */ |
| r19700 | r19701 | |
| 822 | 789 | /* anything else */ |
| 823 | 790 | else |
| 824 | 791 | logerror("%04X:Unknown read from %04X\n", space.device().safe_pc(), offset); |
| 792 | |
| 793 | |
| 794 | /* update the MADSEL state */ |
| 795 | if (!m_irq_state && ((result & 0x1f) == 0x01) && m_maincpu->get_sync()) |
| 796 | m_madsel_lastcycles = m_maincpu->total_cycles(); |
| 797 | |
| 825 | 798 | return result; |
| 826 | 799 | } |
| 827 | 800 | |