sega/sega_ferie.cpp: Add new not working systems (#11862)

New systems added as NOT WORKING
---------------------------------------
Ferie Kitten (1994, Sega)
Ferie Puppy (1995, Sega)
Ferie World Travel (1995, Sega)
This commit is contained in:
qufb 2024-11-16 18:52:41 +00:00 committed by GitHub
parent 5e63771946
commit 6908e70132
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 1154 additions and 5 deletions

View File

@ -3028,6 +3028,8 @@ if CPUS["Z80"] or CPUS["KC80"] or CPUS["Z80N"] then
files {
MAME_DIR .. "src/devices/cpu/z80/z80.cpp",
MAME_DIR .. "src/devices/cpu/z80/z80.h",
MAME_DIR .. "src/devices/cpu/z80/t6a84.cpp",
MAME_DIR .. "src/devices/cpu/z80/t6a84.h",
MAME_DIR .. "src/devices/cpu/z80/tmpz84c011.cpp",
MAME_DIR .. "src/devices/cpu/z80/tmpz84c011.h",
MAME_DIR .. "src/devices/cpu/z80/tmpz84c015.cpp",

View File

@ -0,0 +1,221 @@
// license:BSD-3-Clause
// copyright-holders:QUFB
/***************************************************************************
Toshiba T6A84, TLCS-Z80 ASSP Family
***************************************************************************/
#include "emu.h"
#include "t6a84.h"
#define LOG_PAGE_R (1U << 1)
#define LOG_PAGE_W (1U << 2)
#define LOG_MEM (1U << 3)
//#define VERBOSE (LOG_PAGE_R | LOG_PAGE_W | LOG_MEM)
#include "logmacro.h"
DEFINE_DEVICE_TYPE(T6A84, t6a84_device, "t6a84", "Toshiba T6A84")
void t6a84_device::internal_io_map(address_map &map) const
{
map.global_mask(0xff);
map(0xfc, 0xfc).rw(FUNC(t6a84_device::stack_page_r), FUNC(t6a84_device::stack_page_w));
map(0xfd, 0xfd).rw(FUNC(t6a84_device::data_page_r), FUNC(t6a84_device::data_page_w));
map(0xfe, 0xfe).rw(FUNC(t6a84_device::code_page_r), FUNC(t6a84_device::code_page_w));
map(0xff, 0xff).rw(FUNC(t6a84_device::vector_page_r), FUNC(t6a84_device::vector_page_w));
}
t6a84_device::t6a84_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
t6a84_device(mconfig, T6A84, tag, owner, clock, address_map_constructor(FUNC(t6a84_device::internal_io_map), this))
{ }
t6a84_device::t6a84_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, address_map_constructor io_map)
: z80_device(mconfig, type, tag, owner, clock)
, m_program_space_config("program", ENDIANNESS_LITTLE, 8, 20, 0, 16, 0)
, m_data_space_config("data", ENDIANNESS_LITTLE, 8, 20, 0, 16, 0)
, m_stack_space_config("stack", ENDIANNESS_LITTLE, 8, 20, 0, 16, 0)
, m_io_space_config("io", ENDIANNESS_LITTLE, 8, 16, 0, io_map)
, m_code_page(0)
, m_delay_code_page(0)
, m_is_delay_code_page_set(false)
, m_prev_code_page(0)
, m_data_page(8)
, m_stack_page(8)
, m_vector_page(0)
{
// Interrupt vectors need to be fetched and executed from their corresponding page.
// For simplicity, we switch pages via callbacks, instead of using a dedicated address space.
irqfetch_cb().set([this](int state) {
LOGMASKED(LOG_PAGE_W, "IRQ FETCH %02x => %02x\n", m_code_page, m_vector_page);
m_prev_code_page = m_code_page;
m_code_page = m_vector_page;
});
reti_cb().set([this](int state) {
LOGMASKED(LOG_PAGE_W, "IRQ RET %02x => %02x\n", m_code_page, m_prev_code_page);
m_code_page = m_prev_code_page;
});
branch_cb().set([this](int state) {
LOGMASKED(LOG_PAGE_W, "BRANCH %02x => %02x\n", m_code_page, m_prev_code_page);
/*
When setting a code page, it only becomes effective after jumping to a far address in that page.
Any instructions fetched and executed before that jump still use the previous code page.
This can be seen in Sega Ferie Kitten, when test program at page 7 gets mapped, as we are still
executing on page 0, but we expect to start executing that program when jumping to RST0:
ROM_00::1ea9 3e 07 LD A,0x7
ROM_00::1eab d3 fe OUT (DAT_io_00fe),A
ROM_00::1ead c3 00 00 JP RST0
*/
if (!machine().side_effects_disabled() && m_is_delay_code_page_set) {
m_code_page = m_delay_code_page;
m_is_delay_code_page_set = false;
}
});
}
void t6a84_device::device_start()
{
z80_device::device_start();
space(AS_PROGRAM).cache(m_args);
space(AS_DATA).specific(m_data);
space(AS_STACK).specific(m_stack);
save_item(NAME(m_code_page));
save_item(NAME(m_delay_code_page));
save_item(NAME(m_is_delay_code_page_set));
save_item(NAME(m_prev_code_page));
save_item(NAME(m_data_page));
save_item(NAME(m_stack_page));
save_item(NAME(m_vector_page));
}
void t6a84_device::device_reset()
{
m_code_page = 0;
m_delay_code_page = 0;
m_is_delay_code_page_set = false;
m_prev_code_page = 0;
m_data_page = 8;
m_stack_page = 8;
m_vector_page = 0;
z80_device::device_reset();
}
device_memory_interface::space_config_vector t6a84_device::memory_space_config() const
{
auto r = z80_device::memory_space_config();
r.emplace_back(AS_DATA, &m_data_space_config);
r.emplace_back(AS_STACK, &m_stack_space_config);
for (auto it = r.begin(); it != r.end(); ++it) {
if ((*it).first == AS_IO) {
(*it).second = &m_io_space_config;
} else if ((*it).first == AS_PROGRAM) {
(*it).second = &m_program_space_config;
}
}
return r;
}
bool t6a84_device::memory_translate(int spacenum, int intention, offs_t &address, address_space *&target_space)
{
if (spacenum == AS_PROGRAM) {
address = code_address(address);
} else if (spacenum == AS_DATA) {
address = data_address(address);
} else if (spacenum == AS_STACK) {
address = stack_address(address);
}
target_space = &space(spacenum);
return true;
}
uint32_t t6a84_device::code_address(uint16_t address)
{
const uint32_t page_address = PAGE_SIZE * m_code_page + address;
LOGMASKED(LOG_MEM, "CODE @ %06x => %06x\n", address, page_address);
return page_address;
}
uint32_t t6a84_device::data_address(uint16_t address)
{
const uint32_t page_address = PAGE_SIZE * m_data_page + address;
LOGMASKED(LOG_MEM, "DATA @ %06x => %06x\n", address, page_address);
return page_address;
}
uint32_t t6a84_device::stack_address(uint16_t address)
{
const uint32_t page_address = PAGE_SIZE * m_stack_page + address;
LOGMASKED(LOG_MEM, "STACK @ %06x => %06x\n", address, page_address);
return page_address;
}
uint8_t t6a84_device::stack_read(uint16_t addr)
{
return m_stack.read_byte(translate_memory_address(addr));
}
void t6a84_device::stack_write(uint16_t addr, uint8_t value)
{
m_stack.write_byte(translate_memory_address((uint32_t) addr), value);
}
uint8_t t6a84_device::data_page_r()
{
LOGMASKED(LOG_PAGE_R, "data_page_r: %02x @ %06x\n", m_data_page, pc());
return m_data_page;
}
uint8_t t6a84_device::stack_page_r()
{
LOGMASKED(LOG_PAGE_R, "stack_page_r: %02x @ %06x\n", m_stack_page, pc());
return m_stack_page;
}
uint8_t t6a84_device::code_page_r()
{
LOGMASKED(LOG_PAGE_R, "code_page_r: %02x @ %06x\n", m_code_page, pc());
return m_code_page;
}
uint8_t t6a84_device::vector_page_r()
{
LOGMASKED(LOG_PAGE_R, "vector_page_r: %02x @ %06x\n", m_vector_page, pc());
return m_vector_page;
}
void t6a84_device::data_page_w(uint8_t page)
{
LOGMASKED(LOG_PAGE_W, "data_page_w: %02x @ %06x\n", page, pc());
m_data_page = page;
}
void t6a84_device::stack_page_w(uint8_t page)
{
LOGMASKED(LOG_PAGE_W, "stack_page_w: %02x @ %06x\n", page, pc());
m_stack_page = page;
}
void t6a84_device::code_page_w(uint8_t page)
{
LOGMASKED(LOG_PAGE_W, "code_page_w: %02x @ %06x\n", page, pc());
m_delay_code_page = page;
m_is_delay_code_page_set = true;
}
void t6a84_device::vector_page_w(uint8_t page)
{
LOGMASKED(LOG_PAGE_W, "vector_page_w: %02x @ %06x\n", page, pc());
m_vector_page = page;
}

View File

@ -0,0 +1,91 @@
// license:BSD-3-Clause
// copyright-holders:QUFB
/***************************************************************************
Toshiba T6A84, TLCS-Z80 ASSP Family
Unknown specs. Disassembled code suggests that this processor uses
separate code and data address spaces. Mapped pages on each space are
configured by writing to I/O ports. Values 0 to 7 map the corresponding
0x10000 sized page from ROM offset 0 to 0x7ffff, while value 8 seems to
map full contents of RAM.
Pinout: https://patents.google.com/patent/CN2280961Y/en
***************************************************************************/
#ifndef MAME_CPU_Z80_T6A84_H
#define MAME_CPU_Z80_T6A84_H
#pragma once
#include "z80.h"
/***************************************************************************
TYPE DEFINITIONS
***************************************************************************/
class t6a84_device : public z80_device
{
public:
enum address_spaces : uint8_t
{
AS_STACK = AS_OPCODES + 1
};
t6a84_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
uint32_t code_address(uint16_t address);
uint32_t data_address(uint16_t address);
uint32_t stack_address(uint16_t address);
protected:
static inline constexpr uint32_t PAGE_SIZE = 0x10000;
t6a84_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, address_map_constructor io_map);
// device-level overrides
virtual void device_start() override;
virtual void device_reset() override;
// z80 overrides
virtual uint8_t stack_read(uint16_t addr) override;
virtual void stack_write(uint16_t addr, uint8_t value) override;
uint8_t code_page_r();
uint8_t data_page_r();
uint8_t stack_page_r();
uint8_t vector_page_r();
void code_page_w(uint8_t page);
void data_page_w(uint8_t page);
void stack_page_w(uint8_t page);
void vector_page_w(uint8_t page);
void internal_io_map(address_map &map) const;
virtual space_config_vector memory_space_config() const override;
virtual bool memory_translate(int spacenum, int intention, offs_t &address, address_space *&target_space) override;
const address_space_config m_program_space_config;
const address_space_config m_data_space_config;
const address_space_config m_stack_space_config;
const address_space_config m_io_space_config;
memory_access<16, 0, 0, ENDIANNESS_LITTLE>::specific m_stack;
private:
uint8_t m_code_page;
uint8_t m_delay_code_page;
bool m_is_delay_code_page_set;
uint8_t m_prev_code_page;
uint8_t m_data_page;
uint8_t m_stack_page;
uint8_t m_vector_page;
};
// device type definition
DECLARE_DEVICE_TYPE(T6A84, t6a84_device)
#endif // MAME_CPU_Z80_T6A84_H

View File

@ -836,6 +836,9 @@ z80_device::z80_device(const machine_config &mconfig, device_type type, const ch
m_nomreq_cb(*this),
m_halt_cb(*this),
m_busack_cb(*this),
m_branch_cb(*this),
m_irqfetch_cb(*this),
m_reti_cb(*this),
m_m1_cycles(4),
m_memrq_cycles(3),
m_iorq_cycles(4)

View File

@ -43,6 +43,12 @@ public:
auto halt_cb() { return m_halt_cb.bind(); }
auto busack_cb() { return m_busack_cb.bind(); }
// Extra callbacks that do not map to any documented signals.
// Used by derived classes to customise instruction behaviour.
auto branch_cb() { return m_branch_cb.bind(); }
auto irqfetch_cb() { return m_irqfetch_cb.bind(); }
auto reti_cb() { return m_reti_cb.bind(); }
protected:
z80_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock);
@ -115,6 +121,8 @@ protected:
virtual u8 data_read(u16 addr);
virtual void data_write(u16 addr, u8 value);
virtual u8 stack_read(u16 addr) { return data_read(addr); }
virtual void stack_write(u16 addr, u8 value) { return data_write(addr, value); }
virtual u8 opcode_read();
virtual u8 arg_read();
@ -133,6 +141,12 @@ protected:
devcb_write_line m_halt_cb;
devcb_write_line m_busack_cb;
// Extra callbacks that do not map to any documented signals.
// Used by derived classes to customise instruction behaviour.
devcb_write_line m_branch_cb;
devcb_write_line m_irqfetch_cb;
devcb_write_line m_reti_cb;
PAIR16 m_prvpc;
PAIR16 m_pc;
PAIR16 m_sp;

View File

@ -40,9 +40,9 @@ macro wm16
macro wm16_sp
SP--;
m_memrq_cycles !! data_write(SP, TDAT_H);
m_memrq_cycles !! stack_write(SP, TDAT_H);
SP--;
m_memrq_cycles !! data_write(SP, TDAT_L);
m_memrq_cycles !! stack_write(SP, TDAT_L);
macro rop
m_m1_cycles-2 !! TDAT8 = opcode_read();
@ -73,9 +73,9 @@ macro eay
m_ea = (u16)(IY + (s8)TDAT8); WZ = m_ea;
macro pop
m_memrq_cycles !! TDAT_L = data_read(SP);
m_memrq_cycles !! TDAT_L = stack_read(SP);
SP++;
m_memrq_cycles !! TDAT_H = data_read(SP);
m_memrq_cycles !! TDAT_H = stack_read(SP);
SP++;
macro push
@ -85,11 +85,13 @@ macro push
macro jp
call arg16
PC = TDAT; WZ = PC;
m_branch_cb(true);
macro jp_cond
if (TDAT8) {
call arg16
PC = TDAT; WZ = PC;
m_branch_cb(true);
} else {
// implicit do PC += 2
call arg16
@ -101,6 +103,7 @@ macro jr
TADR = PC-1;
5 * call nomreq_addr
PC += (s8)TDAT8; WZ = PC;
m_branch_cb(true);
macro r800:jr
call arg
@ -163,7 +166,9 @@ macro z80n:retn
macro reti
call pop
PC = TDAT; WZ = PC; m_iff1 = m_iff2; daisy_call_reti_device();
PC = TDAT; WZ = PC; m_iff1 = m_iff2;
m_reti_cb(true);
daisy_call_reti_device();
macro ld_r_a
call nomreq_ir 1
@ -534,6 +539,7 @@ macro take_interrupt
m_r++;
{
// fetch the IRQ vector
m_irqfetch_cb(true);
device_z80daisy_interface *intf = daisy_get_irq_device();
m_tmp_irq_vector = (intf != nullptr) ? intf->z80daisy_irq_ack() : standard_irq_callback(0, m_pc.w);
LOGMASKED(LOG_INT, "single INT m_tmp_irq_vector $%02x\n", m_tmp_irq_vector);

108
src/mame/layout/ferie.lay Normal file
View File

@ -0,0 +1,108 @@
<?xml version="1.0"?>
<!--
license:CC0-1.0
-->
<mamelayout version="2">
<element name="pencursor">
<image state="1">
<data><![CDATA[
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1" height="1" viewBox="0 0 1 1">
<g fill="none" stroke="#6666ff" stroke-width="0.06" stroke-opacity="1">
<circle cx="0.5" cy="0.5" r="0.1" />
<circle cx="0.5" cy="0.5" r="0.47" />
<line x1="0.03" y1="0.5" x2="0.4" y2="0.5" />
<line x1="0.6" y1="0.5" x2="0.97" y2="0.5" />
<line x1="0.5" y1="0.03" x2="0.5" y2="0.4" />
<line x1="0.5" y1="0.6" x2="0.5" y2="0.97" />
<line />
</g>
</svg>
]]></data>
</image>
</element>
<!--
Panel labels:
| メニュー | ひらがな | すうじ | | ▲ |
| えいご | カタカナ | スタソプ | もどる | ▼ |
Panel labels (translated):
| Menu | Hiragana | Numeral | OK | ▲ |
| English | Katakana | Stamp | Back | ▼ |
-->
<element name="panel">
<!-- background -->
<rect><color red="0.06" green="0.12" blue="0.24" /><bounds x="0" y="0" width="15" height="3" /></rect>
<!-- borders -->
<rect><color red="1" green="1" blue="1" /><bounds x="0" y="1.475" width="15" height="0.05" /></rect>
<rect><color red="1" green="1" blue="1" /><bounds x="2.975" y="0.125" width="0.05" height="2.75" /></rect>
<rect><color red="1" green="1" blue="1" /><bounds x="5.975" y="0.125" width="0.05" height="2.75" /></rect>
<rect><color red="1" green="1" blue="1" /><bounds x="8.975" y="0.125" width="0.05" height="2.75" /></rect>
<rect><color red="1" green="1" blue="1" /><bounds x="11.975" y="0.125" width="0.05" height="2.75" /></rect>
<!-- 1st row -->
<text string="Menu"><color red="1" green="1" blue="1" /><bounds x="0" y="0.375" width="3" height="0.75" /></text>
<text string="Hiragana"><color red="1" green="1" blue="1" /><bounds x="3" y="0.375" width="3" height="0.75" /></text>
<text string="Numeral"><color red="1" green="1" blue="1" /><bounds x="6" y="0.375" width="3" height="0.75" /></text>
<text string="O.K"><color red="1" green="1" blue="1" /><bounds x="9" y="0.375" width="3" height="0.75" /></text>
<text string="▲"><color red="1" green="1" blue="1" /><bounds x="12" y="0.375" width="3" height="0.75" /></text>
<!-- 2nd row -->
<text string="English"><color red="1" green="1" blue="1" /><bounds x="0" y="1.875" width="3" height="0.75" /></text>
<text string="Katakana"><color red="1" green="1" blue="1" /><bounds x="3" y="1.875" width="3" height="0.75" /></text>
<text string="Stamp"><color red="1" green="1" blue="1" /><bounds x="6" y="1.875" width="3" height="0.75" /></text>
<text string="Back"><color red="1" green="1" blue="1" /><bounds x="9" y="1.875" width="3" height="0.75" /></text>
<text string="▼"><color red="1" green="1" blue="1" /><bounds x="12" y="1.875" width="3" height="0.75" /></text>
</element>
<view name="Default View">
<screen index="0">
<bounds x="0" y="0" width="15" height="8" />
</screen>
<element id="panel" ref="panel">
<bounds x="0" y="8" width="15" height="3" />
</element>
<element id="pencursor" ref="pencursor">
<!-- will be positioned by script -->
<bounds x="0" y="8" width="0.2" height="0.2" />
<color alpha="0.7" />
</element>
</view>
<script><![CDATA[
file:set_resolve_tags_callback(
function ()
local panel = file.views['Default View'].items['panel']
-- recompute target pen cursor size and area when necessary
local curxoffs, curyoffs, curxscale, curyscale, curwidth, curheight
file.views['Default View']:set_recomputed_callback(
function ()
local bounds = panel.bounds
curwidth = bounds.width / 16
curheight = bounds.height * 5 / 16
curxoffs = bounds.x0 - (curwidth * 0.5)
curyoffs = bounds.y0 - (curheight * 0.5)
curxscale = bounds.width / 255
curyscale = bounds.height / 255
end)
-- animate the position of the pen cursor
local penctrl = file.device:ioport('BUTTONS')
local penx = file.device:ioport('PEN_X')
local peny = file.device:ioport('PEN_Y_RESCALE')
file.views['Default View'].items['pencursor']:set_element_state_callback(
function ()
return (penctrl:read() & 0x80) >> 7
end)
file.views['Default View'].items['pencursor']:set_bounds_callback(
function ()
local x = curxoffs + (penx:read() * curxscale)
local y = curyoffs + (peny:read() * curyscale)
return emu.render_bounds(x, y, x + curwidth, y + curheight)
end)
end)
]]></script>
</mamelayout>

View File

@ -40104,6 +40104,11 @@ beena //
tvochken //
carbeena //
@source:sega/sega_ferie.cpp
ferieki //
feriepu //
feriewt //
@source:sega/sega_sawatte.cpp
sawatte //

View File

@ -0,0 +1,699 @@
// license:BSD-3-Clause
// copyright-holders:QUFB
/******************************************************************************
Sega Ferie (c) 1994 Sega
TODO:
- Fix input press state, e.g. World Travel test program tablet check not passed, unresponsive menu lists
- LCD effects, in particular when to clear/show dots, e.g. Kitten test program credits are jumbled
- Most tablet commands aren't parsed, e.g. pen callibration positions
- Disk beeper (used in Test Program)
Hardware
--------
Ferie Kitten (first model, just named "Ferie"):
- PCB Revision: SB1P01B, 171-6896, 837-11123
- IC1 (CPU): Toshiba T6A84
- IC2 (Mask ROM): Sega MPR-17062-T
- IC3 (Static RAM): Sony CXK58257AM-10L
- IC4 (Touchpad Driver): Sega 315-5832
Ferie Puppy:
- PCB Revision: MB-TPD338-1.0
- U1 (CPU): Chip-on-board
- U2 (ROM): Chip-on-board
- U3 (RAM): Chip-on-board
- U4 (Touchpad Driver): Sega 315-5832
- LCD Driver: Toshiba T6A04
Ferie World Travel:
- PCB Revision: SB1P02A
- U1 (CPU): Toshiba T6A84
- U2 (Mask ROM): Sega MPR-18080A
- U3 (Static RAM): Sony CXK58257AM-10L
- U4 (Touchpad Driver): Sega 315-5889
Mask ROM pinouts are identical to MPR-18201-S: https://www.smspower.org/Development/MaskROMs
Test Program
------------
Ferie Kitten stores it in page 7 (ROM offset 0x70000). Currently unknown how to
normally load this program. It requires writing "TEST" at RAM address 0x70.
To force loading after reset, run "do pc = 0x1ea9" in the debugger.
*******************************************************************************/
#include "emu.h"
#include "cpu/z80/t6a84.h"
#include "machine/ram.h"
#include "machine/timer.h"
#include "crsshair.h"
#include "emupal.h"
#include "screen.h"
#include "ferie.lh"
#define LOG_MEM (1U << 1)
#define LOG_LCD (1U << 2)
#define LOG_TABLET (1U << 3)
//#define VERBOSE (LOG_MEM | LOG_LCD | LOG_TABLET)
#include "logmacro.h"
namespace {
class sega_ferie_state : public driver_device
{
public:
sega_ferie_state(const machine_config &mconfig, device_type type, const char *tag)
: driver_device(mconfig, type, tag)
, m_maincpu(*this, "maincpu")
, m_palette(*this, "palette")
, m_screen(*this, "screen")
, m_ram(*this, RAM_TAG)
, m_io_buttons(*this, "BUTTONS")
, m_io_pen_x(*this, "PEN_X")
, m_io_pen_y(*this, "PEN_Y")
, m_io_pen_y_rescale(*this, "PEN_Y_RESCALE")
{ }
void sega_ferie(machine_config &config);
DECLARE_CROSSHAIR_MAPPER_MEMBER(pen_y_mapper);
ioport_value pen_y_rescale_r();
ioport_value pen_target_r();
private:
static inline constexpr uint16_t LCD_W = 120;
static inline constexpr uint16_t LCD_H = 64;
static inline constexpr uint8_t RST1 = 0x08;
static inline constexpr uint8_t RST3 = 0x18;
static inline constexpr uint8_t HIGH_BATTERY = 0x40;
// Data is sent in sequences of writes to I/O port 0xe5,
// filling lines of dots in a configurable direction. An internal index
// is increment after each line, and gets reset when it reaches a given threshold.
enum lcd_fill_pattern : uint8_t
{
FILL_8_DOTS_Y, // Column-wise
FILL_8_DOTS_X // Row-wise
};
/*
Commands for parsing and configuring tablet variables. Both
data for requests and replies is shifted one bit at a time:
ROM_00::086a 4f LD C,data
ROM_00::086b 06 08 LD i,0x8
ROM_00::086d cd 91 11 CALL set_data_page_8
ROM_00::0870 36 00 LD (reply),0x0
LAB_ROM_00__0872
ROM_00::0872 00 NOP
ROM_00::0873 00 NOP
ROM_00::0874 00 NOP
ROM_00::0875 00 NOP
ROM_00::0876 79 LD data,C
ROM_00::0877 e6 01 AND 0x1
ROM_00::0879 d3 f6 OUT (TABLET_CTRL),data
ROM_00::087b 00 NOP
; ...
ROM_00::0887 00 NOP
ROM_00::0888 79 LD data,C
ROM_00::0889 e6 01 AND 0x1
ROM_00::088b f6 02 OR 0x2
ROM_00::088d d3 f6 OUT (TABLET_CTRL),data
ROM_00::088f cb 39 SRL C
ROM_00::0891 db f7 IN data,(TABLET_DATA)
ROM_00::0893 07 RLCA
ROM_00::0894 cb 1e RR (reply)
ROM_00::0896 10 da DJNZ LAB_ROM_00__0872
*/
enum tablet_command_request : uint8_t
{
READ = 0,
EFFECTIVE_PEN_X_Y = 0x0a,
RAW_PEN_X_Y = 0x15, // Only used for callibration?
};
// Replies are read via one or more sequences of READ commands.
enum tablet_command_reply_state : uint16_t
{
REPLY_0x06 = 0,
EFFECTIVE_PEN_STATUS = 0x0a00,
EFFECTIVE_PEN_X = 0x0a01,
EFFECTIVE_PEN_Y = 0x0a02,
RAW_PEN_STATUS = 0x1500,
RAW_PEN_X = 0x1501,
RAW_PEN_Y = 0x1502,
};
enum input_state : uint8_t
{
INPUT_RELEASE = 0,
INPUT_PRESS = 0x10,
INPUT_HOLD = 0x11,
};
enum pen_target : uint8_t
{
PEN_TARGET_LCD = 0,
PEN_TARGET_PANEL = 1,
};
virtual void machine_start() override;
virtual void machine_reset() override;
void ferie_io_map(address_map &map);
TIMER_DEVICE_CALLBACK_MEMBER(irq);
void update_rtc();
void update_crosshair(screen_device &screen);
void palette(palette_device &palette) const;
uint32_t screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);
void lcd_ctrl_w(uint8_t data);
void lcd_data_w(uint8_t data);
uint8_t get_lcd_dots(uint16_t x, uint16_t y);
uint8_t code_r(offs_t offset);
uint8_t data_r(offs_t offset);
void data_w(offs_t offset, uint8_t data);
uint8_t stack_r(offs_t offset);
void stack_w(offs_t offset, uint8_t data);
uint8_t get_page_data(uint32_t address);
void tablet_ctrl_w(uint8_t data);
uint8_t tablet_data_r();
static constexpr float rescale(float x, float min_x, float max_x, float a, float b)
{
// Rescaling (min-max normalization) from [min_x..max_x] to [a..b].
return a + (((x - min_x) * (b - a)) / (max_x - min_x));
}
required_device<t6a84_device> m_maincpu;
optional_device<palette_device> m_palette;
required_device<screen_device> m_screen;
required_device<ram_device> m_ram;
required_ioport m_io_buttons;
required_ioport m_io_pen_x;
required_ioport m_io_pen_y;
required_ioport m_io_pen_y_rescale;
uint8_t *m_rom;
uint8_t m_irq_ctrl;
uint8_t m_irq_status;
uint8_t m_irq_vector;
uint8_t m_rtc;
std::unique_ptr<uint8_t[]> m_lcd_dots;
uint8_t m_lcd_x;
uint8_t m_lcd_y;
uint8_t m_lcd_i;
uint8_t m_lcd_mode;
uint8_t m_tablet_cmd;
uint8_t m_tablet_cmd_prev;
uint8_t m_tablet_data_i;
uint16_t m_tablet_reply_state;
bool m_is_tablet_cmd_bit_set;
uint8_t m_button_state;
uint8_t m_pen_state;
uint8_t m_pen_target;
};
void sega_ferie_state::ferie_io_map(address_map &map)
{
map.global_mask(0xff);
map(0xe4, 0xe4).lr8(NAME([]() { return 0; })).w(FUNC(sega_ferie_state::lcd_ctrl_w));
map(0xe5, 0xe5).w(FUNC(sega_ferie_state::lcd_data_w));
map(0xeb, 0xeb).lrw8(NAME([] () { return 0; /* TODO: On/Off button state */ }), NAME([this] (uint8_t data) { m_irq_ctrl = data; }));
map(0xf4, 0xf4).lr8(NAME([this] () { return m_rtc; }));
map(0xf5, 0xf5).lr8(NAME([this] () { return m_io_buttons->read() & 0x3f; }));
map(0xf6, 0xf6).w(FUNC(sega_ferie_state::tablet_ctrl_w));
map(0xf7, 0xf7).r(FUNC(sega_ferie_state::tablet_data_r));
}
void sega_ferie_state::machine_start()
{
uint8_t *rom = memregion("mask_rom")->base();
m_rom = reinterpret_cast<uint8_t *>(rom);
uint8_t *ram = m_ram->pointer();
m_maincpu->space(AS_PROGRAM).install_rom(0x00000, 0x7ffff, rom);
m_maincpu->space(AS_PROGRAM).install_ram(0x80000, 0x87fff, ram);
m_maincpu->space(AS_DATA).install_ram(0x00000, 0x7ffff, rom);
m_maincpu->space(AS_DATA).install_ram(0x80000, 0x87fff, ram);
m_maincpu->space(t6a84_device::AS_STACK).install_ram(0x00000, 0x7ffff, rom);
m_maincpu->space(t6a84_device::AS_STACK).install_ram(0x80000, 0x87fff, ram);
m_maincpu->space(AS_PROGRAM).install_read_handler(0x0000, 0xffff,
read8sm_delegate(*this, FUNC(sega_ferie_state::code_r)));
m_maincpu->space(AS_DATA).install_readwrite_handler(0x0000, 0xffff,
read8sm_delegate(*this, FUNC(sega_ferie_state::data_r)),
write8sm_delegate(*this, FUNC(sega_ferie_state::data_w)));
m_maincpu->space(t6a84_device::AS_STACK).install_readwrite_handler(0x0000, 0xffff,
read8sm_delegate(*this, FUNC(sega_ferie_state::stack_r)),
write8sm_delegate(*this, FUNC(sega_ferie_state::stack_w)));
save_item(NAME(m_irq_ctrl));
save_item(NAME(m_irq_status));
save_item(NAME(m_irq_vector));
save_item(NAME(m_rtc));
m_lcd_dots = make_unique_clear<uint8_t[]>(LCD_W * LCD_H);
save_pointer(NAME(m_lcd_dots), LCD_W * LCD_H);
save_item(NAME(m_lcd_mode));
save_item(NAME(m_lcd_x));
save_item(NAME(m_lcd_y));
save_item(NAME(m_lcd_i));
save_item(NAME(m_tablet_cmd));
save_item(NAME(m_tablet_cmd_prev));
save_item(NAME(m_tablet_data_i));
save_item(NAME(m_tablet_reply_state));
save_item(NAME(m_is_tablet_cmd_bit_set));
save_item(NAME(m_button_state));
save_item(NAME(m_pen_state));
save_item(NAME(m_pen_target));
}
void sega_ferie_state::machine_reset()
{
m_irq_ctrl = 0;
m_irq_status = 0;
m_irq_vector = RST3;
m_rtc = 0;
memset(m_lcd_dots.get(), 0, LCD_W * LCD_H);
m_lcd_mode = FILL_8_DOTS_Y;
m_lcd_x = 0;
m_lcd_y = 0;
m_lcd_i = 0;
m_tablet_cmd = READ;
m_tablet_cmd_prev = READ;
m_tablet_data_i = 0;
m_tablet_reply_state = REPLY_0x06;
m_is_tablet_cmd_bit_set = false;
m_button_state = INPUT_RELEASE;
m_pen_state = INPUT_RELEASE;
m_pen_target = PEN_TARGET_LCD;
}
CROSSHAIR_MAPPER_MEMBER(sega_ferie_state::pen_y_mapper)
{
// Parameter `linear_value` is ignored, since we will read the input port directly
// for adjustments, just need to return that value in the expected range [0.0f..1.0f].
return (float) pen_y_rescale_r() / 0xff;
}
ioport_value sega_ferie_state::pen_y_rescale_r()
{
/*
There are two distinct areas that can be interacted with the pen:
- LCD screen visible area: 8 tiles = 64 pixels;
- Bottom panel: 3 tiles = 24 pixels;
In order to transparently map coordinates between each area, we split
the value across these areas, but rescaled to the input port's full range.
*/
const int16_t io_pen_y_min = m_io_pen_y->field(0xff)->minval();
const int16_t io_pen_y_max = m_io_pen_y->field(0xff)->maxval();
const int16_t screen_y_max = io_pen_y_max * 0.75f;
int16_t adjusted_value = m_io_pen_y->read();
if (adjusted_value > screen_y_max) {
adjusted_value = rescale(adjusted_value, screen_y_max, io_pen_y_max, io_pen_y_min, io_pen_y_max);
m_pen_target = PEN_TARGET_PANEL;
} else {
adjusted_value = rescale(adjusted_value, io_pen_y_min, screen_y_max, io_pen_y_min, io_pen_y_max);
m_pen_target = PEN_TARGET_LCD;
}
return adjusted_value;
}
ioport_value sega_ferie_state::pen_target_r()
{
return m_pen_target;
}
TIMER_DEVICE_CALLBACK_MEMBER(sega_ferie_state::irq)
{
bool is_button_pressed = (m_io_buttons->read() & 0x7f) != 0;
if (is_button_pressed && m_button_state == INPUT_RELEASE) {
m_button_state = INPUT_PRESS;
}
if (m_button_state == INPUT_PRESS) {
m_irq_vector = (m_irq_ctrl == 0xf7) ? RST1 : RST3;
m_button_state = INPUT_HOLD;
}
if (!is_button_pressed && m_button_state == INPUT_HOLD) {
m_irq_vector = RST3;
m_button_state = INPUT_RELEASE;
}
m_maincpu->set_input_line_vector(0, 0xc7 | m_irq_vector);
m_maincpu->set_input_line(INPUT_LINE_IRQ0, m_irq_status ? ASSERT_LINE : CLEAR_LINE);
m_irq_status ^= 1;
}
uint8_t sega_ferie_state::code_r(offs_t offset)
{
return get_page_data(m_maincpu->code_address(offset));
}
uint8_t sega_ferie_state::data_r(offs_t offset)
{
return get_page_data(m_maincpu->data_address(offset));
}
void sega_ferie_state::data_w(offs_t offset, uint8_t data)
{
LOGMASKED(LOG_MEM, "DATA w [%06x] = %02x\n", offset, data);
if (offset > 0x7fff) {
osd_printf_warning("DATA w OOB @ %06x\n", m_maincpu->pc());
} else {
m_ram->pointer()[offset] = data;
}
}
uint8_t sega_ferie_state::stack_r(offs_t offset)
{
return get_page_data(m_maincpu->stack_address(offset));
}
void sega_ferie_state::stack_w(offs_t offset, uint8_t data)
{
LOGMASKED(LOG_MEM, "STACK w [%06x] = %02x\n", offset, data);
if (offset > 0x7fff) {
osd_printf_warning("STACK w OOB @ %06x\n", m_maincpu->pc());
} else {
m_ram->pointer()[offset] = data;
}
}
uint8_t sega_ferie_state::get_page_data(uint32_t address)
{
uint8_t data = 0;
if (((int32_t) address) - 0x80000 > 0x7fff) {
osd_printf_warning("get_page_data OOB @ %06x\n", m_maincpu->pc());
} else {
data = (address < 0x80000) ? m_rom[address] : m_ram->pointer()[address - 0x80000];
}
LOGMASKED(LOG_MEM, "get_page_data [%06x] = %02x\n", address, data);
return data;
}
uint8_t sega_ferie_state::tablet_data_r()
{
if (machine().side_effects_disabled() || !m_is_tablet_cmd_bit_set) {
return HIGH_BATTERY;
}
if (m_tablet_data_i == 0) {
if (m_tablet_reply_state == REPLY_0x06) {
switch (m_tablet_cmd_prev) {
case EFFECTIVE_PEN_X_Y:
m_tablet_reply_state = EFFECTIVE_PEN_STATUS;
LOGMASKED(LOG_TABLET, "tablet_data_r EFFECTIVE_PEN_X_Y -> EFFECTIVE_PEN_STATUS\n");
break;
case RAW_PEN_X_Y:
m_tablet_reply_state = RAW_PEN_STATUS;
LOGMASKED(LOG_TABLET, "tablet_data_r RAW_PEN_X_Y -> RAW_PEN_STATUS\n");
break;
}
}
if (BIT(m_io_buttons->read(), 6)) {
if (m_pen_state == INPUT_RELEASE) {
m_pen_state = INPUT_PRESS;
LOGMASKED(LOG_TABLET, "tablet_data_r INPUT_RELEASE -> INPUT_PRESS\n");
} else if (m_pen_state == INPUT_PRESS) {
m_pen_state = INPUT_HOLD;
LOGMASKED(LOG_TABLET, "tablet_data_r INPUT_PRESS -> INPUT_HOLD\n");
}
} else if (m_pen_state != INPUT_RELEASE) {
m_pen_state = INPUT_RELEASE;
LOGMASKED(LOG_TABLET, "tablet_data_r INPUT_RELEASE\n");
}
}
int16_t io_pen_x_min = m_io_pen_x->field(0xff)->minval();
int16_t io_pen_x_max = m_io_pen_x->field(0xff)->maxval();
int16_t io_pen_y_min = m_io_pen_y->field(0xff)->minval();
int16_t io_pen_y_max = m_io_pen_y->field(0xff)->maxval();
int16_t io_pen_x_pos = m_io_pen_x->read();
int16_t io_pen_y_pos = pen_y_rescale_r();
uint8_t reply;
switch (m_tablet_reply_state) {
case EFFECTIVE_PEN_STATUS:
// Fallthrough
case RAW_PEN_STATUS:
reply = m_pen_state;
break;
case EFFECTIVE_PEN_X:
reply = rescale(io_pen_x_pos, io_pen_x_min, io_pen_x_max, 0x00, 0x80);
break;
case EFFECTIVE_PEN_Y:
reply = (m_pen_target == PEN_TARGET_LCD)
? rescale(io_pen_y_pos, io_pen_y_min, io_pen_y_max, 0x00, 0x40)
: rescale(io_pen_y_pos, io_pen_y_min, io_pen_y_max, 0x40, 0x40 + 3 * 8);
break;
case RAW_PEN_X:
reply = io_pen_x_pos;
break;
case RAW_PEN_Y:
reply = io_pen_y_pos;
break;
default:
reply = 0x06;
}
uint8_t data = BIT(reply, m_tablet_data_i) << 7;
LOGMASKED(LOG_TABLET, "tablet_data_r i = %02x\n", m_tablet_data_i);
m_tablet_data_i++;
if (m_tablet_data_i == 8) {
LOGMASKED(LOG_TABLET, "tablet_data_r cmd = %02x, rpy = %02x @ %06x\n", m_tablet_cmd, reply, m_maincpu->pc());
m_tablet_data_i = 0;
if (m_tablet_cmd != READ) {
m_tablet_cmd_prev = m_tablet_cmd;
m_tablet_reply_state = REPLY_0x06;
}
m_tablet_cmd = READ;
switch (m_tablet_reply_state) {
case EFFECTIVE_PEN_STATUS:
if (m_pen_state == INPUT_HOLD) {
m_tablet_reply_state = EFFECTIVE_PEN_X;
LOGMASKED(LOG_TABLET, "tablet_data_r EFFECTIVE_PEN_STATUS -> EFFECTIVE_PEN_X\n");
}
break;
case EFFECTIVE_PEN_X:
m_tablet_reply_state = EFFECTIVE_PEN_Y;
LOGMASKED(LOG_TABLET, "tablet_data_r EFFECTIVE_PEN_X -> EFFECTIVE_PEN_Y\n");
break;
case RAW_PEN_STATUS:
if (m_pen_state == INPUT_HOLD) {
m_tablet_reply_state = RAW_PEN_X;
LOGMASKED(LOG_TABLET, "tablet_data_r RAW_PEN_STATUS -> RAW_PEN_X\n");
}
break;
case RAW_PEN_X:
m_tablet_reply_state = RAW_PEN_Y;
LOGMASKED(LOG_TABLET, "tablet_data_r RAW_PEN_X -> RAW_PEN_Y\n");
break;
default:
m_tablet_reply_state = REPLY_0x06;
}
}
m_is_tablet_cmd_bit_set = false;
return data | HIGH_BATTERY;
}
void sega_ferie_state::tablet_ctrl_w(uint8_t data)
{
if ((data & 2) != 0) {
LOGMASKED(LOG_TABLET, "tablet_ctrl_w i = %02x\n", m_tablet_data_i);
m_tablet_cmd |= ((data & 1) << m_tablet_data_i);
m_is_tablet_cmd_bit_set = true;
}
}
void sega_ferie_state::update_rtc()
{
system_time systime;
machine().current_datetime(systime);
m_rtc = systime.time;
}
void sega_ferie_state::update_crosshair(screen_device &screen)
{
// Either screen crosshair or layout view's cursor should be visible at a time.
machine().crosshair().get_crosshair(0).set_screen(m_pen_target ? CROSSHAIR_SCREEN_NONE : &screen);
}
void sega_ferie_state::palette(palette_device &palette) const
{
palette.set_pen_color(0, rgb_t(188, 190, 144)); // Background
palette.set_pen_color(1, rgb_t(176, 180, 134)); // LCD pixel off
palette.set_pen_color(2, rgb_t( 38, 60, 80)); // LCD pixel on
}
uint32_t sega_ferie_state::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
update_rtc();
update_crosshair(screen);
// Render LCD
for (size_t y = cliprect.min_y; y <= cliprect.max_y; y++) {
for (size_t x = cliprect.min_x; x <= cliprect.max_x / 8; x++) {
uint8_t data = get_lcd_dots(x, y);
for (size_t bit_i = 0; bit_i < 8; bit_i++) {
// TODO: Adjust based on configured contrast
bitmap.pix(y, (8 * x) + bit_i) = BIT(data, 7 - bit_i) + 1;
}
}
}
return 0;
}
uint8_t sega_ferie_state::get_lcd_dots(uint16_t x, uint16_t y)
{
return m_lcd_dots[y * LCD_W + x];
}
void sega_ferie_state::lcd_ctrl_w(uint8_t data)
{
LOGMASKED(LOG_LCD, "lcd_ctrl_w @ %06x = %02x\n", m_maincpu->pc(), data);
if ((data & 0x20) != 0) {
m_lcd_x = data & 0x1f;
} else if ((data & 0x80) != 0) {
m_lcd_y = data & 0x7f;
m_lcd_i = 0;
} else if (data == 0x7) {
LOGMASKED(LOG_LCD, "lcd_ctrl_w FILL_8_DOTS_X\n");
m_lcd_mode = FILL_8_DOTS_X;
} else if (data == 0x3) {
LOGMASKED(LOG_LCD, "lcd_ctrl_w FILL_8_DOTS_Y\n");
m_lcd_mode = FILL_8_DOTS_Y;
}
}
void sega_ferie_state::lcd_data_w(uint8_t data)
{
const uint16_t offset = (m_lcd_mode == FILL_8_DOTS_Y)
? (m_lcd_y + m_lcd_i) * LCD_W + m_lcd_x
: m_lcd_y * LCD_W + (m_lcd_x + m_lcd_i);
LOGMASKED(LOG_LCD, "lcd_data_w[y=%02x,x=%02x,i=%02x => %08x] = %02x\n", m_lcd_y, m_lcd_x, m_lcd_i, offset, data);
if (offset > LCD_W * LCD_H - 1) {
osd_printf_warning("lcd_data_w OOB @ %06x\n", m_maincpu->pc());
} else {
m_lcd_dots[offset] = data;
}
m_lcd_i += 1;
if (m_lcd_mode == FILL_8_DOTS_Y) {
if (m_lcd_i == 0x8) {
m_lcd_i = 0;
}
} else if (m_lcd_mode == FILL_8_DOTS_X) {
if (m_lcd_i == 0xf) {
m_lcd_i = 0;
m_lcd_y += 1;
}
}
}
static INPUT_PORTS_START( sega_ferie )
PORT_START("BUTTONS")
PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_JOYSTICKLEFT_RIGHT ) PORT_8WAY PORT_PLAYER(1)
PORT_BIT( 0x02, IP_ACTIVE_HIGH, IPT_JOYSTICKLEFT_LEFT ) PORT_8WAY PORT_PLAYER(1)
PORT_BIT( 0x04, IP_ACTIVE_HIGH, IPT_JOYSTICKLEFT_DOWN ) PORT_8WAY PORT_PLAYER(1)
PORT_BIT( 0x08, IP_ACTIVE_HIGH, IPT_JOYSTICKLEFT_UP ) PORT_8WAY PORT_PLAYER(1)
PORT_BIT( 0x10, IP_ACTIVE_HIGH, IPT_BUTTON1 ) PORT_PLAYER(1) PORT_NAME("Return")
PORT_BIT( 0x20, IP_ACTIVE_HIGH, IPT_BUTTON2 ) PORT_PLAYER(1) PORT_NAME("On/Off")
PORT_BIT( 0x40, IP_ACTIVE_HIGH, IPT_BUTTON3 ) PORT_PLAYER(1) PORT_NAME("Pen Down")
PORT_BIT( 0x80, IP_ACTIVE_HIGH, IPT_CUSTOM ) PORT_READ_LINE_MEMBER(FUNC(sega_ferie_state::pen_target_r))
PORT_START("PEN_X")
PORT_BIT( 0xff, 0x80, IPT_LIGHTGUN_X ) PORT_CROSSHAIR(X, 1.0, 0.0, 0) PORT_SENSITIVITY(30) PORT_KEYDELTA(10) PORT_MINMAX(0, 255) PORT_PLAYER(1) PORT_NAME("Pen X")
PORT_START("PEN_Y")
PORT_BIT( 0xff, 0x80, IPT_LIGHTGUN_Y ) PORT_CROSSHAIR(Y, 1.0, 0.0, 0) PORT_SENSITIVITY(30) PORT_KEYDELTA(10) PORT_MINMAX(0, 255) PORT_PLAYER(1) PORT_NAME("Pen Y") PORT_CROSSHAIR_MAPPER_MEMBER(FUNC(sega_ferie_state::pen_y_mapper))
PORT_START("PEN_Y_RESCALE")
PORT_BIT( 0xff, IP_ACTIVE_HIGH, IPT_CUSTOM ) PORT_CUSTOM_MEMBER(FUNC(sega_ferie_state::pen_y_rescale_r))
INPUT_PORTS_END
void sega_ferie_state::sega_ferie(machine_config &config)
{
T6A84(config, m_maincpu, XTAL(4'000'000)); // High frequency external crystal, connected to HXIN/HXOUT pins
m_maincpu->set_addrmap(AS_IO, &sega_ferie_state::ferie_io_map);
RAM(config, RAM_TAG).set_default_size("32K").set_default_value(0x00);
// FIXME: Guessed timings
TIMER(config, "irq").configure_periodic(FUNC(sega_ferie_state::irq), attotime::from_hz(200));
SCREEN(config, m_screen, SCREEN_TYPE_LCD);
m_screen->set_refresh_hz(60);
m_screen->set_vblank_time(ATTOSECONDS_IN_USEC(0));
m_screen->set_size(LCD_W, LCD_H);
m_screen->set_visarea(0, LCD_W - 1, 0, LCD_H - 1);
m_screen->set_screen_update(FUNC(sega_ferie_state::screen_update));
m_screen->set_palette(m_palette);
PALETTE(config, m_palette, FUNC(sega_ferie_state::palette), 3);
config.set_default_layout(layout_ferie);
}
ROM_START( ferieki )
ROM_REGION16_LE( 0x80000, "mask_rom", 0 )
ROM_LOAD( "mpr-17062-t.ic2", 0x00000, 0x80000, CRC(c693c9f6) SHA1(491e23902f5ef0dea9156f244f5aa2a21ab68505) )
ROM_END
ROM_START( feriepu )
ROM_REGION16_LE( 0x80000, "mask_rom", 0 )
ROM_LOAD( "rom.u2", 0x00000, 0x80000, CRC(895bfe47) SHA1(28ca98471a4a4b5084884b39eccbc74bff1cf4c6) )
ROM_END
ROM_START( feriewt )
ROM_REGION16_LE( 0x80000, "mask_rom", 0 )
ROM_LOAD( "mpr-18080a.u2", 0x00000, 0x80000, CRC(117aea09) SHA1(73ff933ba2bacd485cbc0580b023341fffac692f) )
ROM_END
} // anonymous namespace
// year, name, parent, compat, machine, input, class, init, company, fullname, flags
CONS( 1994, ferieki, 0, 0, sega_ferie, sega_ferie, sega_ferie_state, empty_init, "Sega", "Ferie Kitten", MACHINE_NOT_WORKING | MACHINE_NO_SOUND )
CONS( 1995, feriepu, 0, 0, sega_ferie, sega_ferie, sega_ferie_state, empty_init, "Sega", "Ferie Puppy", MACHINE_NOT_WORKING | MACHINE_NO_SOUND )
CONS( 1995, feriewt, 0, 0, sega_ferie, sega_ferie, sega_ferie_state, empty_init, "Sega", "Ferie World Travel", MACHINE_NOT_WORKING | MACHINE_NO_SOUND )