// license:BSD-3-Clause // copyright-holders:Aaron Giles /*************************************************************************** atarigen.cpp General functions for Atari games. ***************************************************************************/ #include "emu.h" #include "cpu/m6502/m6502.h" #include "atarigen.h" /*************************************************************************** CONSTANTS ***************************************************************************/ #define SOUND_TIMER_RATE attotime::from_usec(5) #define SOUND_TIMER_BOOST attotime::from_usec(1000) /*************************************************************************** INLINE FUNCTIONS ***************************************************************************/ inline const atarigen_screen_timer *atarigen_state::get_screen_timer(screen_device &screen) { atarigen_state *state = screen.machine().driver_data(); int i; // find the index of the timer that matches the screen for (i = 0; i < ARRAY_LENGTH(state->m_screen_timer); i++) if (state->m_screen_timer[i].screen == &screen) return &state->m_screen_timer[i]; fatalerror("Unexpected: no atarivc_eof_update_timer for screen '%s'\n", screen.tag()); return nullptr; } //************************************************************************** // SOUND COMMUNICATIONS DEVICE //************************************************************************** // device type definition DEFINE_DEVICE_TYPE(ATARI_SOUND_COMM, atari_sound_comm_device, "atarscom", "Atari Sound Communications") //------------------------------------------------- // atari_sound_comm_device - constructor //------------------------------------------------- atari_sound_comm_device::atari_sound_comm_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : device_t(mconfig, ATARI_SOUND_COMM, tag, owner, clock) , m_main_int_cb(*this) , m_sound_cpu(*this, finder_base::DUMMY_TAG) , m_main_to_sound_ready(false) , m_sound_to_main_ready(false) , m_main_to_sound_data(0) , m_sound_to_main_data(0) , m_timed_int(0) , m_ym2151_int(0) { } //------------------------------------------------- // device_start: Start up the device //------------------------------------------------- void atari_sound_comm_device::device_start() { // resolve callbacks m_main_int_cb.resolve_safe(); // register for save states save_item(NAME(m_main_to_sound_ready)); save_item(NAME(m_sound_to_main_ready)); save_item(NAME(m_main_to_sound_data)); save_item(NAME(m_sound_to_main_data)); save_item(NAME(m_timed_int)); save_item(NAME(m_ym2151_int)); } //------------------------------------------------- // device_reset: Handle a device reset by // clearing the interrupt lines and states //------------------------------------------------- void atari_sound_comm_device::device_reset() { // reset the internal interrupts states m_timed_int = m_ym2151_int = 0; // reset the sound I/O states m_main_to_sound_data = m_sound_to_main_data = 0; m_main_to_sound_ready = m_sound_to_main_ready = false; } //------------------------------------------------- // device_timer: Handle device-specific timer // calbacks //------------------------------------------------- void atari_sound_comm_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { switch (id) { case TID_SOUND_RESET: delayed_sound_reset(param); break; case TID_SOUND_WRITE: delayed_sound_write(param); break; case TID_6502_WRITE: delayed_6502_write(param); break; } } //------------------------------------------------- // sound_irq_gen: Generates an IRQ signal to the // 6502 sound processor. //------------------------------------------------- INTERRUPT_GEN_MEMBER(atari_sound_comm_device::sound_irq_gen) { sound_irq(); } void atari_sound_comm_device::sound_irq() { m_timed_int = 1; update_sound_irq(); } //------------------------------------------------- // sound_irq_ack_r: Resets the IRQ signal to the // 6502 sound processor. Both reads and writes // can be used. //------------------------------------------------- u8 atari_sound_comm_device::sound_irq_ack_r() { if (!machine().side_effects_disabled()) { m_timed_int = 0; update_sound_irq(); } return 0; } void atari_sound_comm_device::sound_irq_ack_w(u8 data) { m_timed_int = 0; update_sound_irq(); } //------------------------------------------------- // atarigen_ym2151_irq_gen: Sets the state of the // YM2151's IRQ line. //------------------------------------------------- WRITE_LINE_MEMBER(atari_sound_comm_device::ym2151_irq_gen) { m_ym2151_int = state; update_sound_irq(); } //------------------------------------------------- // sound_reset_w: Write handler which resets the // sound CPU in response. //------------------------------------------------- void atari_sound_comm_device::sound_reset_w(u16 data) { synchronize(TID_SOUND_RESET); } //------------------------------------------------- // main_command_w: Handles communication from the main CPU // to the sound CPU. Two versions are provided, one with the // data byte in the low 8 bits, and one with the data byte in // the upper 8 bits. //------------------------------------------------- void atari_sound_comm_device::main_command_w(u8 data) { synchronize(TID_SOUND_WRITE, data); } //------------------------------------------------- // main_response_r: Handles reading data communicated from the // sound CPU to the main CPU. Two versions are provided, one // with the data byte in the low 8 bits, and one with the data // byte in the upper 8 bits. //------------------------------------------------- u8 atari_sound_comm_device::main_response_r() { if (!machine().side_effects_disabled()) { m_sound_to_main_ready = false; m_main_int_cb(CLEAR_LINE); } return m_sound_to_main_data; } //------------------------------------------------- // sound_response_w: Handles communication from the // sound CPU to the main CPU. //------------------------------------------------- void atari_sound_comm_device::sound_response_w(u8 data) { synchronize(TID_6502_WRITE, data); } //------------------------------------------------- // sound_command_r: Handles reading data // communicated from the main CPU to the sound // CPU. //------------------------------------------------- u8 atari_sound_comm_device::sound_command_r() { if (!machine().side_effects_disabled()) { m_main_to_sound_ready = false; m_sound_cpu->set_input_line(INPUT_LINE_NMI, CLEAR_LINE); } return m_main_to_sound_data; } //------------------------------------------------- // update_sound_irq: Called whenever the IRQ state // changes. An interrupt is generated if either // sound_irq_gen() was called, or if the YM2151 // generated an interrupt via the // ym2151_irq_gen() callback. //------------------------------------------------- void atari_sound_comm_device::update_sound_irq() { if (m_timed_int || m_ym2151_int) m_sound_cpu->set_input_line(M6502_IRQ_LINE, ASSERT_LINE); else m_sound_cpu->set_input_line(M6502_IRQ_LINE, CLEAR_LINE); } //------------------------------------------------- // delayed_sound_reset: Synchronizes the sound // reset command between the two CPUs. //------------------------------------------------- void atari_sound_comm_device::delayed_sound_reset(int param) { // unhalt and reset the sound CPU if (param == 0) { m_sound_cpu->set_input_line(INPUT_LINE_HALT, CLEAR_LINE); m_sound_cpu->pulse_input_line(INPUT_LINE_RESET, attotime::zero); } // reset the sound write state m_sound_to_main_ready = false; m_main_int_cb(CLEAR_LINE); // allocate a high frequency timer until a response is generated // the main CPU is *very* sensistive to the timing of the response machine().scheduler().boost_interleave(SOUND_TIMER_RATE, SOUND_TIMER_BOOST); } //------------------------------------------------- // delayed_sound_write: Synchronizes a data write // from the main CPU to the sound CPU. //------------------------------------------------- void atari_sound_comm_device::delayed_sound_write(int data) { // warn if we missed something if (m_main_to_sound_ready) logerror("Missed command from 680x0\n"); // set up the states and signal an NMI to the sound CPU m_main_to_sound_data = data; m_main_to_sound_ready = true; m_sound_cpu->set_input_line(INPUT_LINE_NMI, ASSERT_LINE); // allocate a high frequency timer until a response is generated // the main CPU is *very* sensistive to the timing of the response machine().scheduler().boost_interleave(SOUND_TIMER_RATE, SOUND_TIMER_BOOST); } //------------------------------------------------- // delayed_6502_write: Synchronizes a data write // from the sound CPU to the main CPU. //------------------------------------------------- void atari_sound_comm_device::delayed_6502_write(int data) { // warn if we missed something if (m_sound_to_main_ready) logerror("Missed result from 6502\n"); // set up the states and signal the sound interrupt to the main CPU m_sound_to_main_data = data; m_sound_to_main_ready = true; m_main_int_cb(ASSERT_LINE); } /*************************************************************************** OVERALL INIT ***************************************************************************/ atarigen_state::atarigen_state(const machine_config &mconfig, device_type type, const char *tag) : driver_device(mconfig, type, tag) , m_scanline_int_state(0) , m_video_int_state(0) , m_xscroll(*this, "xscroll") , m_yscroll(*this, "yscroll") , m_slapstic_num(0) , m_slapstic(nullptr) , m_slapstic_bank(0) , m_slapstic_last_pc(0) , m_slapstic_last_address(0) , m_slapstic_base(0) , m_slapstic_mirror(0) , m_scanlines_per_callback(0) , m_maincpu(*this, "maincpu") , m_gfxdecode(*this, "gfxdecode") , m_screen(*this, "screen") , m_slapstic_device(*this, ":slapstic") { } void atarigen_state::machine_start() { // allocate timers for all screens int i = 0; for (screen_device &screen : screen_device_iterator(*this)) { assert(i <= ARRAY_LENGTH(m_screen_timer)); m_screen_timer[i].screen = &screen; m_screen_timer[i].scanline_interrupt_timer = timer_alloc(TID_SCANLINE_INTERRUPT, (void *)&screen); m_screen_timer[i].scanline_timer = timer_alloc(TID_SCANLINE_TIMER, (void *)&screen); i++; } save_item(NAME(m_scanline_int_state)); save_item(NAME(m_video_int_state)); save_item(NAME(m_slapstic_num)); save_item(NAME(m_slapstic_bank)); save_item(NAME(m_slapstic_last_pc)); save_item(NAME(m_slapstic_last_address)); save_item(NAME(m_scanlines_per_callback)); } void atarigen_state::machine_reset() { // reset the interrupt states m_video_int_state = m_scanline_int_state = 0; // reset the slapstic if (m_slapstic_num != 0) { if (!m_slapstic_device.found()) fatalerror("Slapstic device is missing?\n"); m_slapstic_device->slapstic_reset(); slapstic_update_bank(m_slapstic_device->slapstic_bank()); } } void atarigen_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { switch (id) { case TID_SCANLINE_INTERRUPT: { scanline_int_write_line(1); screen_device *screen = reinterpret_cast(ptr); timer.adjust(screen->frame_period()); break; } case TID_SCANLINE_TIMER: scanline_timer(timer, *reinterpret_cast(ptr), param); break; // unhalt the CPU that was passed as a pointer case TID_UNHALT_CPU: reinterpret_cast(ptr)->execute().set_input_line(INPUT_LINE_HALT, CLEAR_LINE); break; } } void atarigen_state::scanline_update(screen_device &screen, int scanline) { } /*************************************************************************** INTERRUPT HANDLING ***************************************************************************/ //------------------------------------------------- // scanline_int_set: Sets the scanline when the next // scanline interrupt should be generated. //------------------------------------------------- void atarigen_state::scanline_int_set(screen_device &screen, int scanline) { get_screen_timer(screen)->scanline_interrupt_timer->adjust(screen.time_until_pos(scanline)); } //------------------------------------------------- // scanline_int_write_line: Standard write line // callback for the scanline interrupt //------------------------------------------------- WRITE_LINE_MEMBER(atarigen_state::scanline_int_write_line) { m_scanline_int_state = state; update_interrupts(); } //------------------------------------------------- // scanline_int_ack_w: Resets the state of the // scanline interrupt. //------------------------------------------------- void atarigen_state::scanline_int_ack_w(u16 data) { m_scanline_int_state = 0; update_interrupts(); } //------------------------------------------------- // video_int_write_line: Standard write line // callback for the video interrupt. //------------------------------------------------- WRITE_LINE_MEMBER(atarigen_state::video_int_write_line) { if (state) { m_video_int_state = 1; update_interrupts(); } } //------------------------------------------------- // video_int_ack_w: Resets the state of the video // interrupt. //------------------------------------------------- void atarigen_state::video_int_ack_w(u16 data) { m_video_int_state = 0; update_interrupts(); } /*************************************************************************** SLAPSTIC HANDLING ***************************************************************************/ inline void atarigen_state::slapstic_update_bank(int bank) { // if the bank has changed, copy the memory; Pit Fighter needs this if (bank != m_slapstic_bank) { // bank 0 comes from the copy we made earlier if (bank == 0) memcpy(m_slapstic, &m_slapstic_bank0[0], 0x2000); else memcpy(m_slapstic, &m_slapstic[bank * 0x1000], 0x2000); // remember the current bank m_slapstic_bank = bank; } } void atarigen_state::device_post_load() { if (m_slapstic_num != 0) { if (!m_slapstic_device.found()) fatalerror("Slapstic device is missing?\n"); slapstic_update_bank(m_slapstic_device->slapstic_bank()); } } //------------------------------------------------- // slapstic_configure: Installs memory handlers for the // slapstic and sets the chip number. //------------------------------------------------- void atarigen_state::slapstic_configure(cpu_device &device, offs_t base, offs_t mirror, u8 *mem) { if (!m_slapstic_device.found()) fatalerror("Slapstic device is missing\n"); // initialize the slapstic m_slapstic_num = m_slapstic_device->m_chipnum; m_slapstic_device->slapstic_init(); // install the memory handlers address_space &program = device.space(AS_PROGRAM); program.install_readwrite_handler(base, base + 0x7fff, 0, mirror, 0, read16_delegate(*this, FUNC(atarigen_state::slapstic_r)), write16_delegate(*this, FUNC(atarigen_state::slapstic_w))); m_slapstic = (u16 *)mem; // allocate memory for a copy of bank 0 m_slapstic_bank0.resize(0x2000); memcpy(&m_slapstic_bank0[0], m_slapstic, 0x2000); // ensure we recopy memory for the bank m_slapstic_bank = 0xff; // install an opcode base handler if we are a 68000 or variant m_slapstic_base = base; m_slapstic_mirror = mirror; } //------------------------------------------------- // slapstic_w: Assuming that the slapstic sits in // ROM memory space, we just simply tweak the slapstic at this // address and do nothing more. //------------------------------------------------- WRITE16_MEMBER(atarigen_state::slapstic_w) { if (!m_slapstic_device.found()) fatalerror("Slapstic device is missing?\n"); slapstic_update_bank(m_slapstic_device->slapstic_tweak(space, offset)); } //------------------------------------------------- // slapstic_r: Tweaks the slapstic at the appropriate // address and then reads a word from the underlying memory. //------------------------------------------------- READ16_MEMBER(atarigen_state::slapstic_r) { if (!m_slapstic_device.found()) fatalerror("Slapstic device is missing?\n"); // fetch the result from the current bank first int result = m_slapstic[offset & 0xfff]; if (!machine().side_effects_disabled()) { // then determine the new one slapstic_update_bank(m_slapstic_device->slapstic_tweak(space, offset)); } return result; } /*************************************************************************** SCANLINE TIMING ***************************************************************************/ //------------------------------------------------- // scanline_timer_reset: Sets up the scanline timer. //------------------------------------------------- void atarigen_state::scanline_timer_reset(screen_device &screen, int frequency) { // set the scanline callback m_scanlines_per_callback = frequency; // set a timer to go off at scanline 0 if (frequency != 0) get_screen_timer(screen)->scanline_timer->adjust(screen.time_until_pos(0)); } //------------------------------------------------- // scanline_timer: Called once every n scanlines // to generate the periodic callback to the main // system. //------------------------------------------------- void atarigen_state::scanline_timer(emu_timer &timer, screen_device &screen, int scanline) { // callback scanline_update(screen, scanline); // generate another scanline += m_scanlines_per_callback; if (scanline >= screen.height()) scanline = 0; timer.adjust(screen.time_until_pos(scanline), scanline); } /*************************************************************************** VIDEO HELPERS ***************************************************************************/ //------------------------------------------------- // halt_until_hblank_0: Halts CPU 0 until the // next HBLANK. //------------------------------------------------- void atarigen_state::halt_until_hblank_0(device_t &device, screen_device &screen) { // halt the CPU until the next HBLANK int hpos = screen.hpos(); int width = screen.width(); int hblank = width * 9 / 10; // if we're in hblank, set up for the next one if (hpos >= hblank) hblank += width; // halt and set a timer to wake up device.execute().set_input_line(INPUT_LINE_HALT, ASSERT_LINE); timer_set(screen.scan_period() * (hblank - hpos) / width, TID_UNHALT_CPU, 0, (void *)&device); } /*************************************************************************** MISC HELPERS ***************************************************************************/ //------------------------------------------------- // blend_gfx: Takes two GFXElements and blends their // data together to form one. Then frees the second. //------------------------------------------------- void atarigen_state::blend_gfx(int gfx0, int gfx1, int mask0, int mask1) { gfx_element *gx0 = m_gfxdecode->gfx(gfx0); gfx_element *gx1 = m_gfxdecode->gfx(gfx1); // allocate memory for the assembled data u8 *srcdata = auto_alloc_array(machine(), u8, gx0->elements() * gx0->width() * gx0->height()); // loop over elements u8 *dest = srcdata; for (int c = 0; c < gx0->elements(); c++) { const u8 *c0base = gx0->get_data(c); const u8 *c1base = gx1->get_data(c); // loop over height for (int y = 0; y < gx0->height(); y++) { const u8 *c0 = c0base; const u8 *c1 = c1base; for (int x = 0; x < gx0->width(); x++) *dest++ = (*c0++ & mask0) | (*c1++ & mask1); c0base += gx0->rowbytes(); c1base += gx1->rowbytes(); } } // int newdepth = gx0->depth() * gx1->depth(); int granularity = gx0->granularity(); gx0->set_raw_layout(srcdata, gx0->width(), gx0->height(), gx0->elements(), 8 * gx0->width(), 8 * gx0->width() * gx0->height()); gx0->set_granularity(granularity); // free the second graphics element m_gfxdecode->set_gfx(gfx1, nullptr); }