// license:BSD-3-Clause // copyright-holders:Miodrag Milanovic,Robbbert /*************************************************************************** JR-100 National / Panasonic 2010-08-23 Initial driver version CPU: User manual for JR100U states it is a MN1800 (==MC6802), but the photo shows it to be a Fujitsu MB8861H, which is a MC6800 with extra instructions. The manual also states the clock to be 890kHz but in fact it is 894kHz. MB14392 custom LSI generates the various clock signals for the computer. List of extra instructions: 0x71 NIM And Immediate with memory data & M -> M Index, 8,3 71,data,index V is reset; if result = 0, Z is set, N is reset; else Z is reset, N is set 0x72 OIM Or Immediate with memory data | M -> M Index, 8,3 72,data,index V is reset; if result = 0, Z is set, N is reset; else Z is reset, N is set 0x75 XIM Xor Immediate with memory data ^ M -> M Index, 8,3 75,data,index if result = 0, Z is set, N is reset; else Z is reset, N is set 0x7B TMM Test under mask with memory (data & M) ^ data Index, 7,3 7B,data,index flags not stated 0xEC ADX Add Index Register X + data -> X Immed,4,2 "adds 1 byte to the index register"? flags not stated 0xFC ADX Add Index Register X + (M) -> X Extend,7,3 "adds 2 bytes to the index register"? flags not stated. One page says result goes to M. There is also an MB8861 equivalent of the MC6802 which is called MB8871. Behaviour of undefined instructions is not the same as the Motorola parts. Variants: MB8861N : 1MHz MB8861E : 1.3MHz MB8861H : 2MHz Similar to MC6875, the MB8867 could be used as a clock oscillator and divide by 8 circuit to drive the CPU directly. CC02 joystick input register (active high) bit 7,6: undefined bit 5: 0 bit 4: button bit 3: down bit 2: up bit 1: left bit 0: right TODO: - Cassette loading doesn't work. Save is verified good. - Need software. ****************************************************************************/ #include "emu.h" #include "cpu/m6800/m6800.h" #include "imagedev/cassette.h" #include "imagedev/snapquik.h" #include "machine/6522via.h" #include "machine/timer.h" #include "sound/spkrdev.h" #include "emupal.h" #include "screen.h" #include "speaker.h" class jr100_state : public driver_device { public: jr100_state(const machine_config &mconfig, device_type type, const char *tag) : driver_device(mconfig, type, tag) , m_ram(*this, "ram") , m_pcg(*this, "pcg") , m_vram(*this, "vram") , m_rom(*this, "rom") , m_via(*this, "via") , m_cassette(*this, "cassette") , m_speaker(*this, "speaker") , m_region_maincpu(*this, "maincpu") , m_io_keyboard(*this, "LINE%u", 0) , m_maincpu(*this, "maincpu") { } void jr100(machine_config &config); private: uint8_t m_keyboard_line; bool m_use_pcg; bool m_pb7; virtual void machine_start() override; virtual void machine_reset() override; uint32_t screen_update_jr100(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect); TIMER_CALLBACK_MEMBER(sound_tick); DECLARE_READ8_MEMBER(pb_r); DECLARE_WRITE8_MEMBER(pa_w); DECLARE_WRITE8_MEMBER(pb_w); DECLARE_WRITE_LINE_MEMBER(cb2_w); uint32_t readByLittleEndian(uint8_t *buf,int pos); DECLARE_QUICKLOAD_LOAD_MEMBER(quickload_cb); void mem_map(address_map &map); required_shared_ptr m_ram; required_shared_ptr m_pcg; required_shared_ptr m_vram; required_shared_ptr m_rom; required_device m_via; required_device m_cassette; required_device m_speaker; required_memory_region m_region_maincpu; required_ioport_array<9> m_io_keyboard; required_device m_maincpu; emu_timer *m_sound_timer; }; void jr100_state::mem_map(address_map &map) { map.unmap_value_high(); map(0x0000, 0x3fff).ram().share("ram"); //map(0x4000, 0x7fff).ram(); expansion ram //map(0x8000, 0xbfff).rom(); expansion rom map(0xc000, 0xc0ff).ram().share("pcg").region("maincpu", 0xc000); map(0xc100, 0xc3ff).ram().share("vram"); map(0xc800, 0xc80f).m(m_via, FUNC(via6522_device::map)); //map(0xcc00, 0xcfff).; expansion i/o //map(0xd000, 0xd7ff).rom(); expansion rom for printer control //map(0xd800, 0xdfff).rom(); expansion rom map(0xe000, 0xffff).rom().share("rom"); } /* Input ports */ INPUT_PORTS_START( jr100 ) PORT_START("LINE0") PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("Ctrl") PORT_CODE(KEYCODE_LCONTROL) PORT_CODE(KEYCODE_RCONTROL) PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("Shift") PORT_CODE(KEYCODE_LSHIFT) PORT_CODE(KEYCODE_RSHIFT) PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_Z) // Z PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_X) // X PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_C) // C PORT_BIT(0xE0, IP_ACTIVE_LOW, IPT_UNUSED) PORT_START("LINE1") PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_A) // A PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_S) // S PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_D) // D PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_F) // F PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_G) // G PORT_BIT(0xE0, IP_ACTIVE_LOW, IPT_UNUSED) PORT_START("LINE2") PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_Q) // Q PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_W) // W PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_E) // E PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_R) // R PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_T) // T PORT_BIT(0xE0, IP_ACTIVE_LOW, IPT_UNUSED) PORT_START("LINE3") PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_1) // 1 PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_2) // 2 PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_3) // 3 PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_4) // 4 PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_5) // 5 PORT_BIT(0xE0, IP_ACTIVE_LOW, IPT_UNUSED) PORT_START("LINE4") PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_6) // 6 PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_7) // 7 PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_8) // 8 PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_9) // 9 PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_0) // 0 PORT_BIT(0xE0, IP_ACTIVE_LOW, IPT_UNUSED) PORT_START("LINE5") PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_Y) // Y PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_U) // U PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_I) // I PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_O) // O PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_P) // P PORT_BIT(0xE0, IP_ACTIVE_LOW, IPT_UNUSED) PORT_START("LINE6") PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_H) // H PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_J) // J PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_K) // K PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_L) // L PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_COLON) // ; PORT_BIT(0xE0, IP_ACTIVE_LOW, IPT_UNUSED) PORT_START("LINE7") PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_V) // V PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_B) // B PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_N) // N PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_M) // M PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_COMMA) // , PORT_BIT(0xE0, IP_ACTIVE_LOW, IPT_UNUSED) PORT_START("LINE8") PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_STOP) // . PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_SPACE) // space PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_QUOTE) // : PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_ENTER) // enter PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_MINUS) // - PORT_BIT(0xE0, IP_ACTIVE_LOW, IPT_UNUSED) INPUT_PORTS_END void jr100_state::machine_start() { if (!m_sound_timer) m_sound_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(jr100_state::sound_tick), this)); } void jr100_state::machine_reset() { attotime timer_period = attotime::from_hz(XTAL(14'318'181) / 16); m_sound_timer->adjust(timer_period, 0, timer_period); } uint32_t jr100_state::screen_update_jr100(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect) { bool attr; u8 y,ra,gfx,chr; u16 x,sy=0,ma=0; for (y = 0; y < 24; y++) { for (ra = 0; ra < 8; ra++) { uint16_t *p = &bitmap.pix16(sy++); for (x = ma; x < ma + 32; x++) { chr = m_vram[x]; attr = BIT(chr, 7); chr &= 0x7f; // ATTR is inverted for normal char or use PCG in case of CMODE1 if (m_use_pcg && attr && (chr < 32)) gfx = m_pcg[(chr<<3) | ra]; else gfx = m_rom[(chr<<3) | ra] ^ (attr ? 0xff : 0); *p++ = BIT(gfx, 7); *p++ = BIT(gfx, 6); *p++ = BIT(gfx, 5); *p++ = BIT(gfx, 4); *p++ = BIT(gfx, 3); *p++ = BIT(gfx, 2); *p++ = BIT(gfx, 1); *p++ = BIT(gfx, 0); } } ma += 32; } return 0; } static const gfx_layout tilesrom_layout = { 8,8, 128, 1, { 0 }, { 0, 1, 2, 3, 4, 5, 6, 7, 8 }, { 0*8, 1*8, 2*8, 3*8, 4*8, 5*8, 6*8, 7*8 }, 8*8 }; static const gfx_layout tilesram_layout = { 8,8, 32, 1, { 0 }, { 0, 1, 2, 3, 4, 5, 6, 7, 8 }, { 0*8, 1*8, 2*8, 3*8, 4*8, 5*8, 6*8, 7*8 }, 8*8 }; static GFXDECODE_START( gfx_jr100 ) GFXDECODE_ENTRY( "maincpu", 0xe000, tilesrom_layout, 0, 1 ) // inside rom GFXDECODE_ENTRY( "maincpu", 0xc000, tilesram_layout, 0, 1 ) // user defined GFXDECODE_END READ8_MEMBER(jr100_state::pb_r) { uint8_t data = 0x1f; if (m_keyboard_line < 9) data = m_io_keyboard[m_keyboard_line]->read() & 0xbf; data |= (m_pb7 ? 0x40 : 0); return data; } WRITE8_MEMBER(jr100_state::pa_w) { m_keyboard_line = data & 0x0f; } WRITE8_MEMBER(jr100_state::pb_w) { m_use_pcg = BIT(data, 5); m_pb7 = BIT(data, 7); m_speaker->level_w(m_pb7); m_via->write_pb6(m_pb7); } WRITE_LINE_MEMBER(jr100_state::cb2_w) { m_cassette->output(state ? -1.0 : +1.0); } // not working; code doesn't seem right TIMER_CALLBACK_MEMBER(jr100_state::sound_tick) { double level = (m_cassette->input()); if (level > 0.0) { m_via->write_ca1(0); m_via->write_cb1(0); } else { m_via->write_ca1(1); m_via->write_cb1(1); } } uint32_t jr100_state::readByLittleEndian(uint8_t *buf,int pos) { return buf[pos] + (buf[pos+1] << 8) + (buf[pos+2] << 16) + (buf[pos+3] << 24); } QUICKLOAD_LOAD_MEMBER(jr100_state::quickload_cb) { int quick_length; uint8_t buf[0x10000]; int read_; quick_length = image.length(); if (quick_length >= 0xffff) return image_init_result::FAIL; read_ = image.fread(buf, quick_length); if (read_ != quick_length) return image_init_result::FAIL; if (buf[0]!=0x50 || buf[1]!=0x52 || buf[2]!=0x4F || buf[3]!=0x47) // this is not PRG return image_init_result::FAIL; int pos = 4; if (readByLittleEndian(buf,pos)!=1) // not version 1 of PRG file return image_init_result::FAIL; pos += 4; uint32_t len =readByLittleEndian(buf,pos); pos+= 4; pos += len; // skip name uint32_t start_address = readByLittleEndian(buf,pos); pos+= 4; uint32_t code_length = readByLittleEndian(buf,pos); pos+= 4; uint32_t flag = readByLittleEndian(buf,pos); pos+= 4; uint32_t end_address = start_address + code_length - 1; // copy code memcpy(m_ram + start_address,buf + pos,code_length); if (flag == 0) { m_ram[end_address + 1] = 0xdf; m_ram[end_address + 2] = 0xdf; m_ram[end_address + 3] = 0xdf; m_ram[6 ] = (end_address >> 8 & 0xFF); m_ram[7 ] = (end_address & 0xFF); m_ram[8 ] = ((end_address + 1) >> 8 & 0xFF); m_ram[9 ] = ((end_address + 1) & 0xFF); m_ram[10] = ((end_address + 2) >> 8 & 0xFF); m_ram[11] = ((end_address + 2) & 0xFF); m_ram[12] = ((end_address + 3) >> 8 & 0xFF); m_ram[13] = ((end_address + 3) & 0xFF); } return image_init_result::PASS; } void jr100_state::jr100(machine_config &config) { /* basic machine hardware */ M6800(config, m_maincpu, XTAL(14'318'181) / 16); //actually MB8861 m_maincpu->set_addrmap(AS_PROGRAM, &jr100_state::mem_map); /* video hardware */ screen_device &screen(SCREEN(config, "screen", SCREEN_TYPE_RASTER)); screen.set_refresh_hz(60); screen.set_vblank_time(ATTOSECONDS_IN_USEC(2500)); /* not accurate */ screen.set_size(256, 192); /* border size not accurate */ screen.set_visarea(0, 256 - 1, 0, 192 - 1); screen.set_screen_update(FUNC(jr100_state::screen_update_jr100)); screen.set_palette("palette"); GFXDECODE(config, "gfxdecode", "palette", gfx_jr100); PALETTE(config, "palette", palette_device::MONOCHROME); VIA6522(config, m_via, XTAL(14'318'181) / 32); // this divider produces the correct cassette save frequencies m_via->readpb_handler().set(FUNC(jr100_state::pb_r)); m_via->writepa_handler().set(FUNC(jr100_state::pa_w)); m_via->writepb_handler().set(FUNC(jr100_state::pb_w)); m_via->cb2_handler().set(FUNC(jr100_state::cb2_w)); m_via->irq_handler().set_inputline(m_maincpu, M6800_IRQ_LINE); SPEAKER(config, "mono").front_center(); SPEAKER_SOUND(config, m_speaker).add_route(ALL_OUTPUTS, "mono", 1.00); CASSETTE(config, m_cassette, 0); m_cassette->set_default_state(CASSETTE_STOPPED | CASSETTE_SPEAKER_ENABLED | CASSETTE_MOTOR_ENABLED); m_cassette->add_route(ALL_OUTPUTS, "mono", 0.05); /* quickload */ QUICKLOAD(config, "quickload", "prg", attotime::from_seconds(2)).set_load_callback(FUNC(jr100_state::quickload_cb)); } /* ROM definition */ ROM_START( jr100 ) ROM_REGION( 0x10000, "maincpu", 0 ) ROM_LOAD( "jr100.ic5", 0xe000, 0x2000, CRC(951d08a1) SHA1(edae3daaa94924e444bbe485ac2bcd5cb5b22ca2)) ROM_END ROM_START( jr100u ) ROM_REGION( 0x10000, "maincpu", 0 ) ROM_LOAD( "jr100u.ic5", 0xe000, 0x2000, CRC(f589dd8d) SHA1(78a51f2ae055bf4dc1b0887a6277f5dbbd8ba512)) ROM_END /* Driver */ // YEAR NAME PARENT COMPAT MACHINE INPUT CLASS INIT COMPANY FULLNAME FLAGS COMP( 1981, jr100, 0, 0, jr100, jr100, jr100_state, empty_init, "National", "JR-100", 0 ) COMP( 1981, jr100u, jr100, 0, jr100, jr100, jr100_state, empty_init, "Panasonic", "JR-100U", 0 )