// license:BSD-3-Clause // copyright-holders:Aaron Giles /*************************************************************************** atarigen.c 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, uint32_t clock) : device_t(mconfig, ATARI_SOUND_COMM, tag, owner, clock), m_sound_cpu_tag(nullptr), m_main_int_cb(*this), m_sound_cpu(nullptr), 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() { // find the sound CPU if (m_sound_cpu_tag == nullptr) throw emu_fatalerror("No sound CPU specified!"); m_sound_cpu = siblingdevice(m_sound_cpu_tag); if (m_sound_cpu == nullptr) throw emu_fatalerror("Sound CPU '%s' not found!", m_sound_cpu_tag); // 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) { 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. //------------------------------------------------- READ8_MEMBER(atari_sound_comm_device::sound_irq_ack_r) { m_timed_int = 0; update_sound_irq(); return 0; } WRITE8_MEMBER(atari_sound_comm_device::sound_irq_ack_w) { 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. //------------------------------------------------- WRITE16_MEMBER(atari_sound_comm_device::sound_reset_w) { 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. //------------------------------------------------- WRITE8_MEMBER(atari_sound_comm_device::main_command_w) { 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. //------------------------------------------------- READ8_MEMBER(atari_sound_comm_device::main_response_r) { 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. //------------------------------------------------- WRITE8_MEMBER(atari_sound_comm_device::sound_response_w) { synchronize(TID_6502_WRITE, data); } //------------------------------------------------- // sound_command_r: Handles reading data // communicated from the main CPU to the sound // CPU. //------------------------------------------------- READ8_MEMBER(atari_sound_comm_device::sound_command_r) { 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 68010\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_palette(*this, "palette"), m_generic_paletteram_16(*this, "paletteram"), 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_gen(*m_maincpu); 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_gen: Standard interrupt routine // which sets the scanline interrupt state. //------------------------------------------------- INTERRUPT_GEN_MEMBER(atarigen_state::scanline_int_gen) { m_scanline_int_state = 1; update_interrupts(); } //------------------------------------------------- // scanline_int_ack_w: Resets the state of the // scanline interrupt. //------------------------------------------------- WRITE16_MEMBER(atarigen_state::scanline_int_ack_w) { 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. //------------------------------------------------- WRITE16_MEMBER(atarigen_state::video_int_ack_w) { 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, uint8_t *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(FUNC(atarigen_state::slapstic_r), this), write16_delegate(FUNC(atarigen_state::slapstic_w), this)); m_slapstic = (uint16_t *)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]; // 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); uint8_t *srcdata, *dest; int c, x, y; // allocate memory for the assembled data srcdata = auto_alloc_array(machine(), uint8_t, gx0->elements() * gx0->width() * gx0->height()); // loop over elements dest = srcdata; for (c = 0; c < gx0->elements(); c++) { const uint8_t *c0base = gx0->get_data(c); const uint8_t *c1base = gx1->get_data(c); // loop over height for (y = 0; y < gx0->height(); y++) { const uint8_t *c0 = c0base; const uint8_t *c1 = c1base; for (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); }