// license:BSD-3-Clause // copyright-holders:Robbbert /************************************************************************************************* The Aussie Byte II Single-Board Computer, created by SME Systems, Melbourne, Australia. Also known as the Knight 2000 Microcomputer. Status: Boots up from floppy. Output to serial terminal and to 6545 are working. Serial keyboard works. Developed in conjunction with members of the MSPP. Written in July, 2015. ToDo: - CRT8002 attributes controller - Graphics - Hard drive controllers and drives - Test Centronics printer - PIO connections Note of MAME restrictions: - Votrax doesn't sound anything like the real thing - WD1001/WD1002 device is not emulated - CRT8002 device is not emulated **************************************************************************************************/ /*********************************************************** Includes ************************************************************/ #include "emu.h" #include "includes/aussiebyte.h" #include "screen.h" #include "speaker.h" /*********************************************************** Address Maps ************************************************************/ void aussiebyte_state::aussiebyte_map(address_map &map) { map(0x0000, 0x3fff).bankr("bankr0").bankw("bankw0"); map(0x4000, 0x7fff).bankrw("bank1"); map(0x8000, 0xbfff).bankrw("bank2"); map(0xc000, 0xffff).ram().region("mram", 0x0000); } void aussiebyte_state::aussiebyte_io(address_map &map) { map.global_mask(0xff); map.unmap_value_high(); map(0x00, 0x03).rw("sio1", FUNC(z80sio_device::ba_cd_r), FUNC(z80sio_device::ba_cd_w)); map(0x04, 0x07).rw(m_pio1, FUNC(z80pio_device::read), FUNC(z80pio_device::write)); map(0x08, 0x0b).rw(m_ctc, FUNC(z80ctc_device::read), FUNC(z80ctc_device::write)); map(0x0c, 0x0f).noprw(); // winchester interface map(0x10, 0x13).rw(m_fdc, FUNC(wd2797_device::read), FUNC(wd2797_device::write)); map(0x14, 0x14).rw(m_dma, FUNC(z80dma_device::bus_r), FUNC(z80dma_device::bus_w)); map(0x15, 0x15).w(FUNC(aussiebyte_state::port15_w)); // boot rom disable map(0x16, 0x16).w(FUNC(aussiebyte_state::port16_w)); // fdd select map(0x17, 0x17).w(FUNC(aussiebyte_state::port17_w)); // DMA mux map(0x18, 0x18).w(FUNC(aussiebyte_state::port18_w)); // fdc select map(0x19, 0x19).r(FUNC(aussiebyte_state::port19_r)); // info port map(0x1a, 0x1a).w(FUNC(aussiebyte_state::port1a_w)); // membank map(0x1b, 0x1b).w(FUNC(aussiebyte_state::port1b_w)); // winchester control map(0x1c, 0x1f).w(FUNC(aussiebyte_state::port1c_w)); // gpebh select map(0x20, 0x23).rw(m_pio2, FUNC(z80pio_device::read), FUNC(z80pio_device::write)); map(0x24, 0x27).rw("sio2", FUNC(z80sio_device::ba_cd_r), FUNC(z80sio_device::ba_cd_w)); map(0x28, 0x28).r(FUNC(aussiebyte_state::port28_r)).w(m_votrax, FUNC(votrax_sc01_device::write)); map(0x2c, 0x2c).w(m_votrax, FUNC(votrax_sc01_device::inflection_w)); map(0x30, 0x30).w(FUNC(aussiebyte_state::address_w)); map(0x31, 0x31).r(m_crtc, FUNC(mc6845_device::status_r)); map(0x32, 0x32).w(FUNC(aussiebyte_state::register_w)); map(0x33, 0x33).r(FUNC(aussiebyte_state::port33_r)); map(0x34, 0x34).w(FUNC(aussiebyte_state::port34_w)); // video control map(0x35, 0x35).w(FUNC(aussiebyte_state::port35_w)); // data to vram and aram map(0x36, 0x36).r(FUNC(aussiebyte_state::port36_r)); // data from vram and aram map(0x37, 0x37).r(FUNC(aussiebyte_state::port37_r)); // read dispen flag map(0x40, 0x4f).rw(FUNC(aussiebyte_state::rtc_r), FUNC(aussiebyte_state::rtc_w)); } /*********************************************************** Keyboard ************************************************************/ static INPUT_PORTS_START( aussiebyte ) INPUT_PORTS_END /*********************************************************** I/O Ports ************************************************************/ WRITE8_MEMBER( aussiebyte_state::port15_w ) { membank("bankr0")->set_entry(m_port15); // point at ram m_port15 = true; } /* FDD select 0 Drive Select bit O 1 Drive Select bit 1 2 Drive Select bit 2 3 Drive Select bit 3 - These bits connect to a 74LS145 binary to BCD converter. - Drives 0 to 3 are 5.25 inch, 4 to 7 are 8 inch, 9 and 0 are not used. - Currently we only support drive 0. 4 Side Select to Disk Drives. 5 Disable 5.25 inch floppy spindle motors. 6 Unused. 7 Enable write precompensation on WD2797 controller. */ WRITE8_MEMBER( aussiebyte_state::port16_w ) { floppy_image_device *m_floppy = nullptr; if ((data & 15) == 0) m_floppy = m_floppy0->get_device(); else if ((data & 15) == 1) m_floppy = m_floppy1->get_device(); m_fdc->set_floppy(m_floppy); if (m_floppy) { m_floppy->mon_w(BIT(data, 5)); m_floppy->ss_w(BIT(data, 4)); } } /* DMA select 0 - FDC 1 - SIO Ch A 2 - SIO Ch B 3 - Winchester bus 4 - SIO Ch C 5 - SIO Ch D 6 - Ext ready 1 7 - Ext ready 2 */ WRITE8_MEMBER( aussiebyte_state::port17_w ) { m_port17 = data & 7; m_dma->rdy_w(BIT(m_port17_rdy, data)); } /* FDC params 2 EXC: WD2797 clock frequency. H = 5.25"; L = 8" 3 WIEN: WD2797 Double density select. */ WRITE8_MEMBER( aussiebyte_state::port18_w ) { m_fdc->set_unscaled_clock(BIT(data, 2) ? 1e6 : 2e6); m_fdc->dden_w(BIT(data, 3)); } READ8_MEMBER( aussiebyte_state::port19_r ) { return m_port19; } // Memory banking WRITE8_MEMBER( aussiebyte_state::port1a_w ) { data &= 7; switch (data) { case 0: case 1: case 2: case 3: case 4: m_port1a = data*3+1; if (m_port15) membank("bankr0")->set_entry(data*3+1); membank("bankw0")->set_entry(data*3+1); membank("bank1")->set_entry(data*3+2); membank("bank2")->set_entry(data*3+3); break; case 5: m_port1a = 1; if (m_port15) membank("bankr0")->set_entry(1); membank("bankw0")->set_entry(1); membank("bank1")->set_entry(2); membank("bank2")->set_entry(13); break; case 6: m_port1a = 14; if (m_port15) membank("bankr0")->set_entry(14); membank("bankw0")->set_entry(14); membank("bank1")->set_entry(15); //membank("bank2")->set_entry(0); // open bus break; case 7: m_port1a = 1; if (m_port15) membank("bankr0")->set_entry(1); membank("bankw0")->set_entry(1); membank("bank1")->set_entry(4); membank("bank2")->set_entry(13); break; } } // Winchester control WRITE8_MEMBER( aussiebyte_state::port1b_w ) { } // GPEHB control WRITE8_MEMBER( aussiebyte_state::port1c_w ) { } WRITE8_MEMBER( aussiebyte_state::port20_w ) { m_speaker->level_w(BIT(data, 7)); m_rtc->cs_w(BIT(data, 0)); m_rtc->hold_w(BIT(data, 0)); } READ8_MEMBER( aussiebyte_state::port28_r ) { return m_port28; } /*********************************************************** RTC ************************************************************/ READ8_MEMBER( aussiebyte_state::rtc_r ) { m_rtc->read_w(1); m_rtc->address_w(offset); uint8_t data = m_rtc->data_r(space,0); m_rtc->read_w(0); return data; } WRITE8_MEMBER( aussiebyte_state::rtc_w ) { m_rtc->address_w(offset); m_rtc->data_w(space,0,data); m_rtc->write_w(1); m_rtc->write_w(0); } /*********************************************************** DMA ************************************************************/ READ8_MEMBER( aussiebyte_state::memory_read_byte ) { address_space& prog_space = m_maincpu->space(AS_PROGRAM); return prog_space.read_byte(offset); } WRITE8_MEMBER( aussiebyte_state::memory_write_byte ) { address_space& prog_space = m_maincpu->space(AS_PROGRAM); prog_space.write_byte(offset, data); } READ8_MEMBER( aussiebyte_state::io_read_byte ) { address_space& prog_space = m_maincpu->space(AS_IO); return prog_space.read_byte(offset); } WRITE8_MEMBER( aussiebyte_state::io_write_byte ) { address_space& prog_space = m_maincpu->space(AS_IO); prog_space.write_byte(offset, data); } WRITE_LINE_MEMBER( aussiebyte_state::busreq_w ) { // since our Z80 has no support for BUSACK, we assume it is granted immediately m_maincpu->set_input_line(Z80_INPUT_LINE_BUSRQ, state); m_dma->bai_w(state); // tell dma that bus has been granted } /*********************************************************** DMA selector ************************************************************/ WRITE_LINE_MEMBER( aussiebyte_state::sio1_rdya_w ) { m_port17_rdy = (m_port17_rdy & 0xfd) | (uint8_t)(state << 1); if (m_port17 == 1) m_dma->rdy_w(state); } WRITE_LINE_MEMBER( aussiebyte_state::sio1_rdyb_w ) { m_port17_rdy = (m_port17_rdy & 0xfb) | (uint8_t)(state << 2); if (m_port17 == 2) m_dma->rdy_w(state); } WRITE_LINE_MEMBER( aussiebyte_state::sio2_rdya_w ) { m_port17_rdy = (m_port17_rdy & 0xef) | (uint8_t)(state << 4); if (m_port17 == 4) m_dma->rdy_w(state); } WRITE_LINE_MEMBER( aussiebyte_state::sio2_rdyb_w ) { m_port17_rdy = (m_port17_rdy & 0xdf) | (uint8_t)(state << 5); if (m_port17 == 5) m_dma->rdy_w(state); } /*********************************************************** Video ************************************************************/ /* F4 Character Displayer */ static const gfx_layout crt8002_charlayout = { 8, 12, /* 7 x 11 characters */ 128, /* 128 characters */ 1, /* 1 bits per pixel */ { 0 }, /* no bitplanes */ /* x offsets */ { 0, 1, 2, 3, 4, 5, 6, 7 }, /* y offsets */ { 0*8, 1*8, 2*8, 3*8, 4*8, 5*8, 6*8, 7*8, 8*8, 9*8, 10*8, 11*8 }, 8*16 /* every char takes 16 bytes */ }; static GFXDECODE_START( gfx_crt8002 ) GFXDECODE_ENTRY( "chargen", 0x0000, crt8002_charlayout, 0, 1 ) GFXDECODE_END /*************************************************************** Daisy Chain ****************************************************************/ static const z80_daisy_config daisy_chain_intf[] = { { "dma" }, { "pio2" }, { "sio1" }, { "sio2" }, { "pio1" }, { "ctc" }, { nullptr } }; /*********************************************************** CTC ************************************************************/ // baud rate generator. All inputs are 1.2288MHz. WRITE_LINE_MEMBER( aussiebyte_state::ctc_z2_w ) { m_ctc->trg3(1); m_ctc->trg3(0); } /*********************************************************** Centronics ack ************************************************************/ WRITE_LINE_MEMBER( aussiebyte_state::write_centronics_busy ) { m_centronics_busy = state; } /*********************************************************** Speech ack ************************************************************/ WRITE_LINE_MEMBER( aussiebyte_state::votrax_w ) { m_port28 = state; } /*********************************************************** Floppy Disk ************************************************************/ WRITE_LINE_MEMBER( aussiebyte_state::fdc_intrq_w ) { uint8_t data = (m_port19 & 0xbf) | (state ? 0x40 : 0); m_port19 = data; } WRITE_LINE_MEMBER( aussiebyte_state::fdc_drq_w ) { uint8_t data = (m_port19 & 0x7f) | (state ? 0x80 : 0); m_port19 = data; state ^= 1; // inverter on pin38 of fdc m_port17_rdy = (m_port17_rdy & 0xfe) | (uint8_t)state; if (m_port17 == 0) m_dma->rdy_w(state); } static void aussiebyte_floppies(device_slot_interface &device) { device.option_add("525qd", FLOPPY_525_QD); } /*********************************************************** Quickload This loads a .COM file to address 0x100 then jumps there. Sometimes .COM has been renamed to .CPM to prevent windows going ballistic. These can be loaded as well. ************************************************************/ QUICKLOAD_LOAD_MEMBER( aussiebyte_state, aussiebyte ) { address_space& prog_space = m_maincpu->space(AS_PROGRAM); if (quickload_size >= 0xfd00) return image_init_result::FAIL; /* RAM must be banked in */ m_port15 = true; // disable boot rom m_port1a = 4; membank("bankr0")->set_entry(m_port1a); /* enable correct program bank */ membank("bankw0")->set_entry(m_port1a); /* Avoid loading a program if CP/M-80 is not in memory */ if ((prog_space.read_byte(0) != 0xc3) || (prog_space.read_byte(5) != 0xc3)) { machine_reset(); return image_init_result::FAIL; } /* Load image to the TPA (Transient Program Area) */ for (uint16_t i = 0; i < quickload_size; i++) { uint8_t data; if (image.fread( &data, 1) != 1) return image_init_result::FAIL; prog_space.write_byte(i+0x100, data); } /* clear out command tail */ prog_space.write_byte(0x80, 0); prog_space.write_byte(0x81, 0); /* Roughly set SP basing on the BDOS position */ m_maincpu->set_state_int(Z80_SP, 256 * prog_space.read_byte(7) - 0x400); m_maincpu->set_pc(0x100); // start program return image_init_result::PASS; } /*********************************************************** Machine Driver ************************************************************/ void aussiebyte_state::machine_reset() { m_port15 = false; m_port17 = 0; m_port17_rdy = 0; m_port1a = 1; m_alpha_address = 0; m_graph_address = 0; membank("bankr0")->set_entry(16); // point at rom membank("bankw0")->set_entry(1); // always write to ram membank("bank1")->set_entry(2); membank("bank2")->set_entry(3); m_maincpu->reset(); } MACHINE_CONFIG_START(aussiebyte_state::aussiebyte) /* basic machine hardware */ Z80(config, m_maincpu, 16_MHz_XTAL / 4); m_maincpu->set_addrmap(AS_PROGRAM, &aussiebyte_state::aussiebyte_map); m_maincpu->set_addrmap(AS_IO, &aussiebyte_state::aussiebyte_io); m_maincpu->set_daisy_config(daisy_chain_intf); /* video hardware */ screen_device &screen(SCREEN(config, "screen", SCREEN_TYPE_RASTER)); screen.set_raw(16_MHz_XTAL, 952, 0, 640, 336, 0, 288); screen.set_screen_update("crtc", FUNC(sy6545_1_device::screen_update)); GFXDECODE(config, "gfxdecode", "palette", gfx_crt8002); MCFG_PALETTE_ADD_MONOCHROME("palette") /* sound hardware */ SPEAKER(config, "mono").front_center(); SPEAKER_SOUND(config, m_speaker).add_route(ALL_OUTPUTS, "mono", 0.50); VOTRAX_SC01(config, m_votrax, 720000); // 720kHz? needs verify m_votrax->ar_callback().set(FUNC(aussiebyte_state::votrax_w)); m_votrax->add_route(ALL_OUTPUTS, "mono", 1.00); /* devices */ CENTRONICS(config, m_centronics, centronics_devices, "printer"); m_centronics->set_data_input_buffer("cent_data_in"); m_centronics->busy_handler().set(FUNC(aussiebyte_state::write_centronics_busy)); INPUT_BUFFER(config, "cent_data_in"); MCFG_CENTRONICS_OUTPUT_LATCH_ADD("cent_data_out", "centronics") Z80CTC(config, m_ctc, 16_MHz_XTAL / 4); m_ctc->intr_callback().set_inputline(m_maincpu, INPUT_LINE_IRQ0); m_ctc->set_clk<0>(4.9152_MHz_XTAL / 4); m_ctc->set_clk<1>(4.9152_MHz_XTAL / 4); m_ctc->set_clk<2>(4.9152_MHz_XTAL / 4); m_ctc->zc_callback<0>().set("sio1", FUNC(z80sio_device::rxca_w)); m_ctc->zc_callback<0>().append("sio1", FUNC(z80sio_device::txca_w)); m_ctc->zc_callback<1>().set("sio1", FUNC(z80sio_device::rxtxcb_w)); m_ctc->zc_callback<1>().append("sio2", FUNC(z80sio_device::rxca_w)); m_ctc->zc_callback<1>().append("sio2", FUNC(z80sio_device::txca_w)); m_ctc->zc_callback<2>().set(FUNC(aussiebyte_state::ctc_z2_w)); // SIO2 Ch B, CTC Ch 3 m_ctc->zc_callback<2>().append("sio2", FUNC(z80sio_device::rxtxcb_w)); Z80DMA(config, m_dma, 16_MHz_XTAL / 4); m_dma->out_int_callback().set_inputline(m_maincpu, INPUT_LINE_IRQ0); m_dma->out_busreq_callback().set(FUNC(aussiebyte_state::busreq_w)); // BAO, not used m_dma->in_mreq_callback().set(FUNC(aussiebyte_state::memory_read_byte)); m_dma->out_mreq_callback().set(FUNC(aussiebyte_state::memory_write_byte)); m_dma->in_iorq_callback().set(FUNC(aussiebyte_state::io_read_byte)); m_dma->out_iorq_callback().set(FUNC(aussiebyte_state::io_write_byte)); Z80PIO(config, m_pio1, 16_MHz_XTAL / 4); m_pio1->out_int_callback().set_inputline(m_maincpu, INPUT_LINE_IRQ0); m_pio1->out_pa_callback().set("cent_data_out", FUNC(output_latch_device::bus_w)); m_pio1->in_pb_callback().set("cent_data_in", FUNC(input_buffer_device::bus_r)); m_pio1->out_ardy_callback().set(m_centronics, FUNC(centronics_device::write_strobe)).invert(); Z80PIO(config, m_pio2, 16_MHz_XTAL / 4); m_pio2->out_int_callback().set_inputline(m_maincpu, INPUT_LINE_IRQ0); m_pio2->out_pa_callback().set(FUNC(aussiebyte_state::port20_w)); z80sio_device& sio1(Z80SIO(config, "sio1", 16_MHz_XTAL / 4)); sio1.out_int_callback().set_inputline(m_maincpu, INPUT_LINE_IRQ0); sio1.out_wrdya_callback().set(FUNC(aussiebyte_state::sio1_rdya_w)); sio1.out_wrdyb_callback().set(FUNC(aussiebyte_state::sio1_rdyb_w)); z80sio_device& sio2(Z80SIO(config, "sio2", 16_MHz_XTAL / 4)); sio2.out_int_callback().set_inputline(m_maincpu, INPUT_LINE_IRQ0); sio2.out_wrdya_callback().set(FUNC(aussiebyte_state::sio2_rdya_w)); sio2.out_wrdyb_callback().set(FUNC(aussiebyte_state::sio2_rdyb_w)); sio2.out_txda_callback().set("rs232", FUNC(rs232_port_device::write_txd)); sio2.out_dtra_callback().set("rs232", FUNC(rs232_port_device::write_dtr)); sio2.out_rtsa_callback().set("rs232", FUNC(rs232_port_device::write_rts)); RS232_PORT(config, m_rs232, default_rs232_devices, "keyboard"); m_rs232->rxd_handler().set("sio2", FUNC(z80sio_device::rxa_w)); WD2797(config, m_fdc, 16_MHz_XTAL / 16); m_fdc->intrq_wr_callback().set(FUNC(aussiebyte_state::fdc_intrq_w)); m_fdc->drq_wr_callback().set(FUNC(aussiebyte_state::fdc_drq_w)); MCFG_FLOPPY_DRIVE_ADD("fdc:0", aussiebyte_floppies, "525qd", floppy_image_device::default_floppy_formats) MCFG_FLOPPY_DRIVE_SOUND(true) MCFG_FLOPPY_DRIVE_ADD("fdc:1", aussiebyte_floppies, "525qd", floppy_image_device::default_floppy_formats) MCFG_FLOPPY_DRIVE_SOUND(true) /* devices */ SY6545_1(config, m_crtc, 16_MHz_XTAL / 8); m_crtc->set_screen("screen"); m_crtc->set_show_border_area(false); m_crtc->set_char_width(8); m_crtc->set_update_row_callback(FUNC(aussiebyte_state::crtc_update_row), this); m_crtc->set_on_update_addr_change_callback(FUNC(aussiebyte_state::crtc_update_addr), this); MSM5832(config, m_rtc, 32.768_kHz_XTAL); /* quickload */ MCFG_QUICKLOAD_ADD("quickload", aussiebyte_state, aussiebyte, "com,cpm", 3) MACHINE_CONFIG_END void aussiebyte_state::machine_start() { // Main ram is divided into 16k blocks (0-15). The boot rom is block number 16. // For convenience, bank 0 is permanently assigned to C000-FFFF uint8_t *main = memregion("roms")->base(); uint8_t *ram = memregion("mram")->base(); membank("bankr0")->configure_entries(0, 16, &ram[0x0000], 0x4000); membank("bankw0")->configure_entries(0, 16, &ram[0x0000], 0x4000); membank("bank1")->configure_entries(0, 16, &ram[0x0000], 0x4000); membank("bank2")->configure_entries(0, 16, &ram[0x0000], 0x4000); membank("bankr0")->configure_entry(16, &main[0x0000]); } /*********************************************************** Game driver ************************************************************/ ROM_START(aussieby) ROM_REGION(0x4000, "roms", 0) // Size of bank 16 ROM_LOAD( "knight_boot_0000.u27", 0x0000, 0x1000, CRC(1f200437) SHA1(80d1d208088b325c16a6824e2da605fb2b00c2ce) ) ROM_REGION(0x800, "chargen", 0) ROM_LOAD( "8002.bin", 0x0000, 0x0800, CRC(fdd6eb13) SHA1(a094d416e66bdab916e72238112a6265a75ca690) ) ROM_REGION(0x40000, "mram", ROMREGION_ERASE00) // main ram, 256k dynamic ROM_REGION(0x10000, "vram", ROMREGION_ERASEFF) // video ram, 64k dynamic ROM_REGION(0x00800, "aram", ROMREGION_ERASEFF) // attribute ram, 2k static ROM_END // YEAR NAME PARENT COMPAT MACHINE INPUT CLASS INIT COMPANY FULLNAME FLAGS COMP( 1984, aussieby, 0, 0, aussiebyte, aussiebyte, aussiebyte_state, empty_init, "SME Systems", "Aussie Byte II", MACHINE_IMPERFECT_GRAPHICS )