// license:BSD-3-Clause // copyright-holders:Roberto Lavarone /*********************************************************************** machine/z80ne.c Functions to emulate general aspects of the machine (RAM, ROM, interrupts, I/O ports) ***********************************************************************/ /* Core includes */ #include "emu.h" #include "includes/z80ne.h" //#define VERBOSE 1 #include "logmacro.h" /* timer to read cassette waveforms */ cassette_image_device* z80ne_state::cassette_device_image() { if (m_lx385_ctrl & 0x08) return m_cassette2; else return m_cassette1; } TIMER_CALLBACK_MEMBER(z80ne_state::z80ne_cassette_tc) { uint8_t cass_ws = 0; m_cass_data.input.length++; cass_ws = ((cassette_device_image())->input() > +0.02) ? 1 : 0; if ((cass_ws ^ m_cass_data.input.level) & cass_ws) { m_cass_data.input.level = cass_ws; m_cass_data.input.bit = ((m_cass_data.input.length < m_cass_data.wave_filter) || (m_cass_data.input.length > 0x20)) ? 1 : 0; m_cass_data.input.length = 0; m_uart->write_si(m_cass_data.input.bit); } m_cass_data.input.level = cass_ws; /* saving a tape - convert the serial stream from the uart */ m_cass_data.output.length--; if (!(m_cass_data.output.length)) { if (m_cass_data.output.level) m_cass_data.output.level = 0; else { m_cass_data.output.level=1; cass_ws = m_uart->so_r(); m_cass_data.wave_length = cass_ws ? m_cass_data.wave_short : m_cass_data.wave_long; } cassette_device_image()->output(m_cass_data.output.level ? -1.0 : +1.0); m_cass_data.output.length = m_cass_data.wave_length; } } void z80ne_state::save_state_vars() { save_item(NAME(m_lx383_scan_counter)); save_item(NAME(m_lx383_key)); save_item(NAME(m_lx383_downsampler)); save_item(NAME(m_lx385_ctrl)); } void z80ne_state::init_z80ne() { save_state_vars(); } void z80netf_state::driver_init() { save_state_vars(); /* first two entries point to rom on reset */ u8 *r = m_ram->pointer(); m_bank1->configure_entry(0, r); /* RAM at 0x0000-0x03FF */ m_bank1->configure_entries(1, 3, m_rom+0x4400, 0x0400); /* ep390, ep1390, ep2390 at 0x0000-0x03FF */ m_bank1->configure_entry(4, m_rom+0x4000); /* ep382 at 0x0000-0x03FF */ m_bank1->configure_entry(5, m_rom); /* ep548 at 0x0000-0x03FF */ m_bank2->configure_entry(0, r+0x0400); /* RAM at 0x0400 */ m_bank2->configure_entry(1, m_rom+0x0400); /* ep548 at 0x0400-0x3FFF */ m_bank3->configure_entry(0, r+0x4000); /* RAM at 0x8000 */ m_bank3->configure_entry(1, m_rom+0x4000); /* ep382 at 0x8000 */ m_bank4->configure_entry(0, r+0x5000); /* RAM at 0xF000 */ m_bank4->configure_entries(1, 3, m_rom+0x4400, 0x0400); /* ep390, ep1390, ep2390 at 0xF000 */ } TIMER_CALLBACK_MEMBER(z80ne_state::z80ne_kbd_scan) { /* * NE555 is connected to a 74LS93 binary counter * 74LS93 output: * QA-QC: column index for LEDs and keyboard * QD: keyboard row select * * Port F0 input bit assignment: * 0 QA bits 0..3 of row counter * 1 QB * 2 QC * 3 QD * 4 Control button pressed, active high * 5 Always low * 6 Always low * 7 Selected button pressed, active low * * */ uint16_t key_bits; uint8_t ctrl; //, rst; uint8_t i; /* 4-bit counter */ --m_lx383_scan_counter; m_lx383_scan_counter &= 0x0f; if ( --m_lx383_downsampler == 0 ) { m_lx383_downsampler = LX383_DOWNSAMPLING; key_bits = (m_io_row1->read() << 8) | m_io_row0->read(); // rst = m_io_rst->read(); ctrl = m_io_ctrl->read(); for ( i = 0; i>= 1; } } } void z80ne_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { switch (id) { case 0: m_maincpu->pulse_input_line(INPUT_LINE_NMI, attotime::zero); break; default: printf("Invalid timer %d encountered\n",id); } } void z80net_state::reset_lx387() { m_lx387_kr2376->set_input_pin( kr2376_device::KR2376_DSII, 0); m_lx387_kr2376->set_input_pin( kr2376_device::KR2376_PII, 0); } void z80netf_state::reset_lx390_banking() { switch (m_io_config->read() & 0x07) { case 0x01: /* EP382 Hex Monitor */ if (VERBOSE) logerror("reset_lx390_banking: banking ep382\n"); m_bank1->set_entry(4); /* ep382 at 0x0000 for 3 cycles, then RAM */ m_bank2->set_entry(0); /* RAM at 0x0400 */ m_bank3->set_entry(1); /* ep382 at 0x8000 */ m_bank4->set_entry(0); /* RAM at 0xF000 */ break; case 0x02: /* EP548 16k BASIC */ if (VERBOSE) logerror("reset_lx390_banking: banking ep548\n"); m_bank1->set_entry(5); /* ep548 at 0x0000-0x03FF */ m_bank2->set_entry(1); /* ep548 at 0x0400-0x3FFF */ m_bank3->set_entry(0); /* RAM at 0x8000 */ m_bank4->set_entry(0); /* RAM at 0xF000 */ break; case 0x03: /* EP390 Boot Loader for 5.5k floppy BASIC */ if (VERBOSE) logerror("reset_lx390_banking: banking ep390\n"); m_bank1->set_entry(1); /* ep390 at 0x0000-0 x03FF for 3 cycles, then RAM */ m_bank2->set_entry(0); /* RAM at 0x0400-0x3FFF */ m_bank3->set_entry(0); /* RAM at 0x8000 */ m_bank4->set_entry(1); /* ep390 at 0xF000 */ break; case 0x04: /* EP1390 Boot Loader for NE DOS 1.0/1.5 */ if (VERBOSE) logerror("reset_lx390_banking: banking ep1390\n"); m_bank1->set_entry(2); /* ep1390 at 0x0000-0x03FF for 3 cycles, then RAM */ m_bank2->set_entry(0); /* RAM at 0x0400-0x3FFF */ m_bank3->set_entry(0); /* RAM at 0x8000 */ m_bank4->set_entry(2); /* ep1390 at 0xF000 */ break; case 0x05: /* EP2390 Boot Loader for NE DOS G.1 */ if (VERBOSE) logerror("reset_lx390_banking: banking ep2390\n"); m_bank1->set_entry(3); /* ep2390 at 0x0000-0x03FF for 3 cycles, then RAM */ m_bank2->set_entry(0); /* RAM at 0x0400-0x3FFF */ m_bank3->set_entry(0); /* RAM at 0x8000 */ m_bank4->set_entry(3); /* ep2390 at 0xF000 */ break; } /* TODO: in real hardware the ENH bus line is pulled down * until a I/O read is performed on a address with A0 address bit low and A1 or A2 address bit high */ } void z80ne_state::base_reset() { for (int i = 0; i < LX383_KEYS; i++) m_lx383_key[i] = 0xf0 | i; m_lx383_scan_counter = 0x0f; m_lx383_downsampler = LX383_DOWNSAMPLING; /* Initialize cassette interface */ switch(m_io_lx_385->read() & 0x07) { case 0x01: m_cass_data.speed = TAPE_300BPS; m_cass_data.wave_filter = LX385_TAPE_SAMPLE_FREQ / 1600; m_cass_data.wave_short = LX385_TAPE_SAMPLE_FREQ / (2400 * 2); m_cass_data.wave_long = LX385_TAPE_SAMPLE_FREQ / (1200 * 2); break; case 0x02: m_cass_data.speed = TAPE_600BPS; m_cass_data.wave_filter = LX385_TAPE_SAMPLE_FREQ / 3200; m_cass_data.wave_short = LX385_TAPE_SAMPLE_FREQ / (4800 * 2); m_cass_data.wave_long = LX385_TAPE_SAMPLE_FREQ / (2400 * 2); break; case 0x04: m_cass_data.speed = TAPE_1200BPS; m_cass_data.wave_filter = LX385_TAPE_SAMPLE_FREQ / 6400; m_cass_data.wave_short = LX385_TAPE_SAMPLE_FREQ / (9600 * 2); m_cass_data.wave_long = LX385_TAPE_SAMPLE_FREQ / (4800 * 2); } m_cass_data.wave_length = m_cass_data.wave_short; m_cass_data.output.length = m_cass_data.wave_length; m_cass_data.output.level = 1; m_cass_data.input.length = 0; m_cass_data.input.bit = 1; m_uart->write_cs(0); m_uart->write_nb1(1); m_uart->write_nb2(1); m_uart->write_tsb(1); m_uart->write_eps(1); m_uart->write_np(m_io_lx_385->read() & 0x80 ? 1 : 0); m_uart->write_cs(1); m_uart_clock->set_unscaled_clock(m_cass_data.speed * 16); lx385_ctrl_w(0); } void z80ne_state::machine_reset() { base_reset(); address_space &program = m_maincpu->space(AS_PROGRAM); program.install_rom(0x0000, 0x03ff, m_rom); // do it here for F3 m_rom_shadow_tap = program.install_read_tap(0x8000, 0x83ff, "rom_shadow_r",[this](offs_t offset, u8 &data, u8 mem_mask) { if (!machine().side_effects_disabled()) { // delete this tap m_rom_shadow_tap->remove(); // reinstall ram over the rom shadow m_maincpu->space(AS_PROGRAM).install_ram(0x0000, 0x03ff, m_mram); } // return the original data return data; }); } void z80net_state::machine_reset() { reset_lx387(); z80ne_state::machine_reset(); } void z80netb_state::machine_reset() { base_reset(); reset_lx387(); } void z80netf_state::machine_reset() { reset_lx390_banking(); base_reset(); reset_lx387(); // basic roms are exempt from memory tap if ((m_io_config->read() & 0x07) != 2) { address_space &program = m_maincpu->space(AS_PROGRAM); m_rom_shadow_tap = program.install_read_tap(0x8000, 0xf3ff, "rom_shadow_r",[this](offs_t offset, u8 &data, u8 mem_mask) { if (!machine().side_effects_disabled()) { // delete this tap m_rom_shadow_tap->remove(); // reinstall ram over the rom shadow m_bank1->set_entry(0); } // return the original data return data; }); } } INPUT_CHANGED_MEMBER(z80ne_state::z80ne_reset) { uint8_t rst = m_io_rst->read(); if ( ! BIT(rst, 0)) machine().schedule_soft_reset(); } INPUT_CHANGED_MEMBER(z80net_state::z80net_nmi) { uint8_t nmi = m_io_lx387_brk->read(); if ( ! BIT(nmi, 0)) m_maincpu->pulse_input_line(INPUT_LINE_NMI, attotime::zero); } void z80ne_state::machine_start() { m_timer_nmi = timer_alloc(0); m_lx383_digits.resolve(); m_lx385_ctrl = 0x1f; m_cassette_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(z80ne_state::z80ne_cassette_tc), this)); m_kbd_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(z80ne_state::z80ne_kbd_scan), this)); m_kbd_timer->adjust(attotime::from_hz(1000), 0, attotime::from_hz(1000)); } void z80netf_state::machine_start() { z80ne_state::machine_start(); m_drv_led.resolve(); } /****************************************************************************** Drivers ******************************************************************************/ /* LX.383 - LX.384 HEX keyboard and display */ uint8_t z80ne_state::lx383_r() { /* * Keyboard scanning * * IC14 NE555 astable oscillator * IC13 74LS93 binary counter * IC5 74LS240 tri-state buffer * * l'oscillatore NE555 alimenta il clock del contatore 74LS93 * D0 - Q(A) --\ * D1 - Q(B) |-- column * D2 - Q(C) --/ * D3 - Q(D) row * D4 - CTRL * D5 - 0 * D6 - 0 * D7 - ~KEY Pressed */ return m_lx383_key[m_lx383_scan_counter]; } void z80ne_state::lx383_w(offs_t offset, uint8_t data) { /* * First 8 locations (F0-F7) are mapped to a dual-port 8-byte RAM * The 1KHz NE-555 astable oscillator circuit drive * a 4-bit 74LS93 binary counter. * The 3 least significant bits of the counter are connected * both to the read address of the dual-port ram and to * a 74LS156 3 to 8 binary decoder driving the cathode * of 8 7-segments LEDS. * The data output of the dual-port ram drive the anodes * of the LEDS through 74LS07 buffers. * LED segments - dual-port RAM bit: * A 0x01 * B 0x02 * C 0x04 * D 0x08 * E 0x10 * F 0x20 * G 0x40 * P 0x80 (represented by DP in original schematics) * * A write in the range F0-FF starts a 74LS90 counter * that trigger the NMI line of the CPU after 2 instruction * fetch cycles for single step execution. */ if ( offset < 8 ) m_lx383_digits[offset] = data ^ 0xff; else { // after writing to port 0xF8 and the first ~M1 cycles strike a NMI for single step execution m_timer_nmi->adjust(m_maincpu->cycles_to_attotime(1)); } } /* LX.385 Cassette tape interface */ /* * NE555 is connected to a 74LS93 binary counter * 74LS93 output: * QA-QC: column index for LEDs and keyboard * QD: keyboard row select * * Port EE: UART Data Read/Write * Port EF: Status/Control * read, UART status bits read * 0 OR Overrun * 1 FE Framing Error * 2 PE Parity Error * 3 TBMT Transmitter Buffer Empty * 4 DAV Data Available * 5 EOC End Of Character * 6 1 * 7 1 * write, UART control bits / Tape Unit select / Modulation control * 0 bit1=0, bit0=0 UART Reset pulse * 1 bit1=0, bit0=1 UART RDAV (Reset Data Available) pulse * 2 Tape modulation enable * 3 *TAPEA Enable (active low) (at reset: low) * 4 *TAPEB Enable (active low) (at reset: low) * Cassette is connected to the uart data input and output via the cassette * interface hardware. * * The cassette interface hardware converts square-wave pulses into bits which the uart receives. * * 1. the cassette format: "frequency shift" is converted into the uart data format "non-return to zero" 2. on cassette a 1 data bit is stored as 8 2400 Hz pulses and a 0 data bit as 4 1200 Hz pulses - At 1200 baud, a logic 1 is 1 cycle of 1200 Hz and a logic 0 is 1/2 cycle of 600 Hz. - At 300 baud, a logic 1 is 8 cycles of 2400 Hz and a logic 0 is 4 cycles of 1200 Hz. Attenuation is applied to the signal and the square wave edges are rounded. A manchester encoder is used. A flip-flop synchronises input data on the positive-edge of the clock pulse. The UART is a RCA CDP1854 CMOS device with pin 2 jumpered to GND to select the AY-3-1015 compatibility mode. The jumper at P4 can be switched to place 12 V on pin 2 for an old PMOS UART. * */ uint8_t z80ne_state::lx385_ctrl_r() { /* set unused bits high */ uint8_t data = 0xc0; m_uart->write_swe(0); data |= (m_uart->or_r( ) ? 0x01 : 0); data |= (m_uart->fe_r( ) ? 0x02 : 0); data |= (m_uart->pe_r( ) ? 0x04 : 0); data |= (m_uart->tbmt_r() ? 0x08 : 0); data |= (m_uart->dav_r( ) ? 0x10 : 0); data |= (m_uart->eoc_r( ) ? 0x20 : 0); m_uart->write_swe(1); return data; } #define LX385_CASSETTE_MOTOR_MASK ((1<<3)|(1<<4)) void z80ne_state::lx385_ctrl_w(uint8_t data) { /* Translate data to control signals * 0 bit1=0, bit0=0 UART Reset pulse * 1 bit1=0, bit0=1 UART RDAV (Reset Data Available) pulse * 2 UART Tx Clock Enable (active high) * 3 *TAPEA Enable (active low) (at reset: low) * 4 *TAPEB Enable (active low) (at reset: low) */ uint8_t uart_reset, uart_rdav; uint8_t motor_a, motor_b; uint8_t changed_bits = (m_lx385_ctrl ^ data) & 0x1C; m_lx385_ctrl = data; uart_reset = ((data & 0x03) == 0x00); uart_rdav = ((data & 0x03) == 0x01); motor_a = ((data & 0x08) == 0x00); motor_b = ((data & 0x10) == 0x00); /* UART Reset and RDAV */ if (uart_reset) { m_uart->write_xr(1); m_uart->write_xr(0); } if (uart_rdav) { m_uart->write_rdav(1); m_uart->write_rdav(0); } if (!changed_bits) return; /* motors */ if(changed_bits & 0x18) { m_cassette1->change_state( (motor_a) ? CASSETTE_MOTOR_ENABLED : CASSETTE_MOTOR_DISABLED,CASSETTE_MASK_MOTOR); m_cassette2->change_state( (motor_b) ? CASSETTE_MOTOR_ENABLED : CASSETTE_MOTOR_DISABLED,CASSETTE_MASK_MOTOR); if (motor_a || motor_b) m_cassette_timer->adjust(attotime::zero, 0, attotime::from_hz(LX385_TAPE_SAMPLE_FREQ)); else m_cassette_timer->adjust(attotime::zero); } } WRITE_LINE_MEMBER(z80ne_state::lx385_uart_tx_clock_w) { if (BIT(m_lx385_ctrl, 2)) m_uart->write_tcp(state); } READ_LINE_MEMBER(z80net_state::lx387_shift_r) { return BIT(m_io_modifiers->read(), 0) || BIT(m_io_modifiers->read(), 2); } READ_LINE_MEMBER(z80net_state::lx387_control_r) { return BIT(m_io_modifiers->read(), 1); } uint8_t z80net_state::lx388_mc6847_videoram_r(offs_t offset) { if (offset == ~0) return 0xff; int d6 = BIT(m_videoram[offset], 6); int d7 = BIT(m_videoram[offset], 7); m_vdg->inv_w(d6 && d7); m_vdg->as_w(!d6 && d7); m_vdg->intext_w(!d6 && d7); return m_videoram[offset]; } uint8_t z80net_state::lx387_data_r() { uint8_t data = m_lx387_kr2376->data_r() & 0x7f; data |= m_lx387_kr2376->get_output_pin(kr2376_device::KR2376_SO) << 7; return data; } uint8_t z80net_state::lx388_read_field_sync() { return m_vdg->fs_r() << 7; } /* * DRQ INTRQ IC9B.10 IC8B.*Q * 0 0 1 0 * 0 1 0 x * 1 0 0 x * 1 1 0 x * */ void z80netf_state::lx390_motor_w(uint8_t data) { /* Selection of drive and parameters A write also causes the selected drive motor to turn on for about 3 seconds. When the motor turns off, the drive is deselected. d7 Unused (trs80: 1=MFM, 0=FM) d6 (trs80: 1=Wait) d5 0=Side 0, 1=Side 1 (trs80: 1=Write Precompensation enabled) d4 Unused (trs80: 0=Side 0, 1=Side 1) d3 1=select drive 3 d2 1=select drive 2 d1 1=select drive 1 d0 1=select drive 0 */ floppy_image_device *floppy = nullptr; for (u8 f = 0; f < 4; f++) if (BIT(data, f)) floppy = m_floppy[f]->get_device(); m_wd1771->set_floppy(floppy); if (floppy) { floppy->ss_w(BIT(data, 5)); floppy->mon_w(0); } m_wd17xx_state.head = (data & 32) ? 1 : 0; m_wd17xx_state.drive = data & 0x0F; /* no drive selected, turn off all leds */ if (!m_wd17xx_state.drive) { m_drv_led[0] = 0; m_drv_led[1] = 0; } } uint8_t z80netf_state::lx390_fdc_r(offs_t offset) { uint8_t d; switch(offset) { case 0: d = m_wd1771->status_r() ^ 0xff; LOG("lx390_fdc_r, WD17xx status: %02x\n", d); break; case 1: d = m_wd1771->track_r() ^ 0xff; LOG("lx390_fdc_r, WD17xx track: %02x\n", d); break; case 2: d = m_wd1771->sector_r() ^ 0xff; LOG("lx390_fdc_r, WD17xx sector: %02x\n", d); break; case 3: d = m_wd1771->data_r() ^ 0xff; LOG("lx390_fdc_r, WD17xx data3: %02x\n", d); break; case 6: d = 0xff; m_bank1->set_entry(0); break; case 7: d = m_wd1771->data_r() ^ 0xff; LOG("lx390_fdc_r, WD17xx data7, force: %02x\n", d); break; default: d = 0x00; } return d; } void z80netf_state::lx390_fdc_w(offs_t offset, uint8_t data) { uint8_t d = data; switch(offset) { case 0: LOG("lx390_fdc_w, WD17xx command: %02x\n", d); m_wd1771->cmd_w(d ^ 0xff); if (m_wd17xx_state.drive & 1) m_drv_led[0] = 2; else if (m_wd17xx_state.drive & 2) m_drv_led[1] = 2; break; case 1: LOG("lx390_fdc_w, WD17xx track: %02x\n", d); m_wd1771->track_w(d ^ 0xff); break; case 2: LOG("lx390_fdc_w, WD17xx sector: %02x\n", d); m_wd1771->sector_w(d ^ 0xff); break; case 3: m_wd1771->data_w(d ^ 0xff); LOG("lx390_fdc_w, WD17xx data3: %02x\n", d); break; case 6: LOG("lx390_fdc_w, motor_w: %02x\n", d); lx390_motor_w(d); break; case 7: LOG("lx390_fdc_w, WD17xx data7, force: %02x\n", d); m_wd1771->data_w(d ^ 0xff); break; } }