// license:BSD-3-Clause
// copyright-holders:David Haywood, Roberto Fresca
/*************************************************************************
Cuatro en Linea.
System I.
1991, Compumatic
Driver by David Haywood & Roberto Fresca.
**************************************************************************
1x Z84C00HB6 CPU @ 8 MHz for program.
1x Z84C00AB6 CPU @ 4 MHz for sound.
1x AY-3-8910 A
1x UMC UM487F (HCGA Controller)
2x NEC D41464C (64K x 4-bit Dynamic NMOS RAM) for VRAM.
1x UMC UM6264A (8K x 8-bit CMOS SRAM).
2x 27512 EPROMS.
1x X24C16P Serial EEPROM.
1x GAL16V8AS
1x ES2 CM3080 (unknown DIP-18 IC)
1x ES2 9046 (unknown PLCC-84 IC)
1x 8952 CM 32 (unknown DIP-40 IC)
1x 16.0000 MHz crystal. ; Divided by 2 (through CM3080) for main CPU Z84C00HB6.
1x 8.000 MHz crystal. ; Divided by 2 for audio CPU Z84C00AB6.
1x 14.31818 MHz crystal ; For HCGA controller.
CN1: 1 x 8 connector.
CN2: 1 x 8 connector.
CN3: 2 x 5 connector.
CN4: 2 x 5 connector.
CN5: 2 x 5 connector.
CN6: 2 x 28 Jamma connector.
CN7: 1 x 20 connector.
CN8: 1 x 4 connector.
CN9: 1 x 4 connector.
CN10: DB9 video out connector.
CN11 1 x 2 bridge connector.
**************************************************************************
UM487F HCGA Controller notes...
The fact that there is a 14.318 MHz crystal tied to pin 65, just point
that the video controller is working in CGA mode. MGA mode needs a
16.257 MHz crystal instead, and tied to pin 64 (currently tied to GND).
Also a signal of 8Mhz (shared with the program CPU is entering from the
pin 1 (CLK) needed for clock the UM6845 mode.
UM487F Access:
Offsets are for sure the CGA mode. MGA mode has different ones.
3D4h: -W CRTC index register.
3D5h: RW CRTC data register.
3D8h: -W Mode control register.
3D9h: -W Color select register.
3DAh: R- Status register.
3BFh: -W Config register.
Mode CTRL (3D8h): 0x6A / 0x62
----- bits -----
7 6 5 4 3 2 1 0 For CGA Mode.
- x x - x - x -
| | | | | | | |
| | | | | | | '-- 40*25 text.
| | | | | | '---- Graphics.
| | | | | '------ Color Mode.
| | | | '-------- Enable Video.
| | | '----------- 320x200 Graphics.
| | '------------- Enable Blink.
| '--------------- Enable Change Mode.
'----------------- (not for CGA)
Color Sel (3D9h): 00
Index register (3D4h): 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
Data register (3D5h): 38 28 2D 0A 7F 06 64 70 02 01 06 07 10 00 00 00
Config Register (3BFh): 0x40
(bit 6 active means CGA Mode)
So... Screen size is set to 320x200.
but...
The embedded CRT controller is set to:
Screen Total: 0x38+1 * 0x7F+1 = (57 * 128) chars.
Screen Visible: 0x28 * 0x64 = (40 * 100) chars.
NOTE: All (registers and offsets) match the CGA ISA card.
Maybe we can find a workaround to hook the controller
without the ISA bus.
**************************************************************************
Custom IC's...
8952 CM 32 pinouts and peripheral circuitry:
8952 CM 32
.------v------.
74HC244 (IC9), PIN 08 --|01 40|-- 74HC244 (IC9), PIN 17
74HC244 (IC9), PIN 06 --|02 39|-- 74HC244 (IC9), PIN 15
74HC244 (IC9), PIN 04 --|03 38|-- 74HC244 (IC9), PIN 13
74HC244 (IC9), PIN 02 --|04 37|-- 74HC244 (IC9), PIN 11
74HC244 (IC10), PIN 08 --|05 36|-- 74HC244 (IC10), PIN 17
74HC244 (IC10), PIN 06 --|06 35|-- 74HC244 (IC10), PIN 15
74HC244 (IC10), PIN 04 --|07 8 34|-- 74HC244 (IC10), PIN 13
74HC244 (IC10), PIN 02 --|08 9 33|-- 74HC244 (IC10), PIN 11
| 5 |
/--|09 2 32|--\
To CN1 (through IC15) | --|10 31|-- | To CN1 (through IC15)
| --|11 30|-- |
\--|12 C 29|--/
| M |
/--|13 28|--\
To CN2 (through IC14) | --|14 3 27|-- | To CN2 (through IC14)
| --|15 2 26|-- |
\--|16 25|--/
| |
To CN3 and ES2 9046 --|17 24|--\
To CN3 and ES2 9046 --|18 23|-- > bridge to GND
To CN3 --|19 22|--/
| |
GND --|20 21|-- VCC
'-------------'
74HC244 (IC9) 74HC244 (IC10)
.---v---. .---v---.
GAL (PIN 17) --|01 20|-- VCC GAL (PIN 16) --|01 20|-- VCC
8952 (PIN 04) --|02 19|-- GAL (PIN 17) 8952 (PIN 08) --|02 19|-- GAL (PIN 16)
MAIN Z80 (D7) --|03 18|-- MAIN Z80 (D0) MAIN Z80 (D7) --|03 18|-- MAIN Z80 (D0)
8952 (PIN 03) --|04 17|-- 8952 (PIN 40) 8952 (PIN 07) --|04 17|-- 8952 (PIN 36)
MAIN Z80 (D6) --|05 16|-- MAIN Z80 (D1) MAIN Z80 (D6) --|05 16|-- MAIN Z80 (D1)
8952 (PIN 02) --|06 15|-- 8952 (PIN 39) 8952 (PIN 06) --|06 15|-- 8952 (PIN 35)
MAIN Z80 (D5) --|07 14|-- MAIN Z80 (D2) MAIN Z80 (D5) --|07 14|-- MAIN Z80 (D2)
8952 (PIN 01) --|08 13|-- 8952 (PIN 38) 8952 (PIN 05) --|08 13|-- 8952 (PIN 34)
MAIN Z80 (D4) --|09 12|-- MAIN Z80 (D3) MAIN Z80 (D4) --|09 12|-- MAIN Z80 (D3)
GND --|10 11|-- 8952 (PIN 37) GND --|10 11|-- 8952 (PIN 33)
'-------' '-------'
ES2 CM3080 pinouts and peripheral circuitry:
CM3080
.---v---.
VCC --|01 18|-- VCC .--------.
N/C --|02 17|----------------+ 16 MHz |
N/C --|03 16|----------------+ Xtal |
N/C --|04 15|-- CLK OUT --. '--------'
GND --|05 14|-- N/C |
GND --|06 13|-- N/C '--+-- (8MHz) UM487F (PIN 01, CLK)
MAIN Z80 (/M1) --|07 12|-- GND +-- (8MHz) MAIN Z80 (PIN 06, CLK)
GAL (PIN 11) --|08 11|-- VCC
GND --|09 10|-- MAIN Z80 (/INT)
'-------'
Notes:
- Looks like the GAL is switching the different '8952 CM 32' outputs
through the 74HC244 drivers to the Z80 data bus.
- CN1, CN2 & CN3 are blind connectors.
- 8952 pinouts to CN1 & CN2, are also passing through locations
IC14 & IC15 (both are unpopulated from factory).
- GAL is GAL16V8 at location IC4.
- CM3080 pins 16 & 17 have a 1 Megohm resistor in parallel before connect the
16 MHz. crystal.
**************************************************************************
TODO:
- Proper UM487F device emulation.
- Interlaced video mode.
- Sound.
- More work...
*************************************************************************/
#include "emu.h"
#include "bus/isa/cga.h"
#include "bus/isa/isa.h"
#include "cpu/z80/z80.h"
#include "sound/ay8910.h"
#include "video/cgapal.h"
#include "video/mc6845.h"
#include "speaker.h"
#define MAIN_CLOCK XTAL(16'000'000)
#define SEC_CLOCK XTAL(8'000'000)
#define HCGA_CLOCK XTAL(14'318'181)
#define PRG_CPU_CLOCK MAIN_CLOCK /2 /* 8 MHz. (measured) */
#define SND_CPU_CLOCK SEC_CLOCK /2 /* 4 MHz. (measured) */
#define SND_AY_CLOCK SEC_CLOCK /4 /* 2 MHz. (measured) */
#define CRTC_CLOCK SEC_CLOCK /2 /* 8 MHz. (measured) */
class _4enlinea_state : public driver_device
{
public:
_4enlinea_state(const machine_config &mconfig, device_type type, const char *tag)
: driver_device(mconfig, type, tag),
m_ay(*this, "aysnd"),
m_maincpu(*this, "maincpu")
{ }
void _4enlinea(machine_config &config);
private:
required_device<ay8910_device> m_ay;
DECLARE_READ8_MEMBER(serial_r);
DECLARE_READ8_MEMBER(serial_status_r);
DECLARE_WRITE8_MEMBER(serial_w);
DECLARE_WRITE8_MEMBER(serial_status_w);
DECLARE_READ8_MEMBER(hack_r);
INTERRUPT_GEN_MEMBER(_4enlinea_irq);
INTERRUPT_GEN_MEMBER(_4enlinea_audio_irq);
uint8_t m_irq_count;
uint8_t m_serial_flags;
uint8_t m_serial_data[2];
virtual void machine_start() override;
virtual void machine_reset() override;
required_device<cpu_device> m_maincpu;
void audio_map(address_map &map);
void audio_portmap(address_map &map);
void main_map(address_map &map);
void main_portmap(address_map &map);
};
/***********************************
* Video Hardware *
***********************************/
// TODO: this is actually UM487F
class isa8_cga_4enlinea_device : public isa8_cga_device
{
public:
// construction/destruction
isa8_cga_4enlinea_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
DECLARE_READ8_MEMBER( _4enlinea_io_read );
DECLARE_WRITE8_MEMBER( _4enlinea_mode_control_w );
virtual void device_start() override;
virtual const tiny_rom_entry *device_rom_region() const override;
};
const tiny_rom_entry *isa8_cga_4enlinea_device::device_rom_region() const
{
return nullptr;
}
DEFINE_DEVICE_TYPE(ISA8_CGA_4ENLINEA, isa8_cga_4enlinea_device, "4enlinea_cga", "ISA8 CGA - 4enlinea")
isa8_cga_4enlinea_device::isa8_cga_4enlinea_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
isa8_cga_device( mconfig, ISA8_CGA_4ENLINEA, tag, owner, clock)
{
}
READ8_MEMBER( isa8_cga_4enlinea_device::_4enlinea_io_read )
{
uint8_t data;
switch (offset)
{
case 0xa:
data = isa8_cga_device::io_read(space, offset);
data|= (data & 8) << 4;
break;
default:
data = isa8_cga_device::io_read(space, offset);
break;
}
return data;
}
WRITE8_MEMBER( isa8_cga_4enlinea_device::_4enlinea_mode_control_w )
{
// TODO
}
void isa8_cga_4enlinea_device::device_start()
{
if (m_palette != nullptr && !m_palette->started())
throw device_missing_dependencies();
set_isa_device();
m_vram_size = 0x4000;
m_vram.resize(m_vram_size);
//m_isa->install_device(0x3bf, 0x3bf, 0, 0, nullptr, write8_delegate( FUNC(isa8_cga_4enlinea_device::_4enlinea_mode_control_w), this ) );
m_isa->install_device(0x3d0, 0x3df, read8_delegate( FUNC(isa8_cga_4enlinea_device::_4enlinea_io_read), this ), write8_delegate( FUNC(isa8_cga_device::io_write), this ) );
m_isa->install_bank(0x8000, 0xbfff, "bank1", &m_vram[0]);
/* Initialise the cga palette */
int i;
for ( i = 0; i < CGA_PALETTE_SETS * 16; i++ )
{
m_palette->set_pen_color( i, cga_palette[i][0], cga_palette[i][1], cga_palette[i][2] );
}
i = 0x8000;
for ( int r = 0; r < 32; r++ )
{
for ( int g = 0; g < 32; g++ )
{
for ( int b = 0; b < 32; b++ )
{
m_palette->set_pen_color( i, r << 3, g << 3, b << 3 );
i++;
}
}
}
// m_chr_gen_base = memregion(subtag("gfx1"))->base();
// m_chr_gen = m_chr_gen_base + m_chr_gen_offset[1];
}
READ8_MEMBER(_4enlinea_state::serial_r)
{
if(offset == 0)
{
m_maincpu->set_input_line(INPUT_LINE_NMI,CLEAR_LINE);
m_serial_flags |= 0x20;
}
return m_serial_data[offset];
}
/***********************************
* Memory Map Information *
***********************************/
void _4enlinea_state::main_map(address_map &map)
{
map(0x0000, 0x7fff).rom();
// AM_RANGE(0x8000, 0xbfff) AM_RAM // CGA VRAM
map(0xc000, 0xdfff).ram();
map(0xe000, 0xe001).r(FUNC(_4enlinea_state::serial_r));
}
void _4enlinea_state::main_portmap(address_map &map)
{
map.global_mask(0x3ff);
// AM_RANGE(0x3d4, 0x3df) CGA regs
map(0x3bf, 0x3bf).nopw(); // CGA mode control, TODO
}
READ8_MEMBER(_4enlinea_state::serial_status_r)
{
return m_serial_flags;
}
WRITE8_MEMBER(_4enlinea_state::serial_status_w)
{
m_serial_flags = data; // probably just clears
}
/* TODO: do this really routes to 0xe000-0xe001 of Main CPU? */
WRITE8_MEMBER(_4enlinea_state::serial_w)
{
m_serial_data[offset] = data;
if(offset == 0)
m_maincpu->set_input_line(INPUT_LINE_NMI,ASSERT_LINE);
}
READ8_MEMBER(_4enlinea_state::hack_r)
{
return machine().rand();
}
void _4enlinea_state::audio_map(address_map &map)
{
map(0x0000, 0x7fff).rom();
map(0xf800, 0xfbff).ram();
map(0xfc24, 0xfc24).r(FUNC(_4enlinea_state::hack_r));
map(0xfc28, 0xfc28).r(FUNC(_4enlinea_state::hack_r));
map(0xfc30, 0xfc31).w(FUNC(_4enlinea_state::serial_w));
map(0xfc32, 0xfc32).rw(FUNC(_4enlinea_state::serial_status_r), FUNC(_4enlinea_state::serial_status_w));
map(0xfc48, 0xfc49).rw(m_ay, FUNC(ay8910_device::data_r), FUNC(ay8910_device::address_data_w));
}
void _4enlinea_state::audio_portmap(address_map &map)
{
map.global_mask(0xff);
}
/***********************************
* Input Ports *
***********************************/
static INPUT_PORTS_START( 4enlinea )
/* Player 1 & 2 ports are tied to both AY-3-8910 ports.
Coin 1 & 2 are tied to the big ES2 9046 CPLD/FPGA,
so... It's a mystery to figure out.
*/
PORT_START("IN-P1")
PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_START1 )
PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_JOYSTICK_UP ) PORT_8WAY PORT_PLAYER(1)
PORT_BIT( 0x04, IP_ACTIVE_LOW, IPT_JOYSTICK_DOWN ) PORT_8WAY PORT_PLAYER(1)
PORT_BIT( 0x08, IP_ACTIVE_LOW, IPT_JOYSTICK_LEFT ) PORT_8WAY PORT_PLAYER(1)
PORT_BIT( 0x10, IP_ACTIVE_LOW, IPT_JOYSTICK_RIGHT ) PORT_8WAY PORT_PLAYER(1)
PORT_BIT( 0x20, IP_ACTIVE_LOW, IPT_BUTTON1 ) PORT_PLAYER(1)
PORT_BIT( 0x40, IP_ACTIVE_LOW, IPT_BUTTON2 ) PORT_PLAYER(1)
PORT_BIT( 0x80, IP_ACTIVE_LOW, IPT_UNUSED )
PORT_START("IN-P2")
PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_START2 )
PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_JOYSTICK_UP ) PORT_8WAY PORT_PLAYER(2)
PORT_BIT( 0x04, IP_ACTIVE_LOW, IPT_JOYSTICK_DOWN ) PORT_8WAY PORT_PLAYER(2)
PORT_BIT( 0x08, IP_ACTIVE_LOW, IPT_JOYSTICK_LEFT ) PORT_8WAY PORT_PLAYER(2)
PORT_BIT( 0x10, IP_ACTIVE_LOW, IPT_JOYSTICK_RIGHT ) PORT_8WAY PORT_PLAYER(2)
PORT_BIT( 0x20, IP_ACTIVE_LOW, IPT_BUTTON1 ) PORT_PLAYER(2)
PORT_BIT( 0x40, IP_ACTIVE_LOW, IPT_BUTTON2 ) PORT_PLAYER(2)
PORT_BIT( 0x80, IP_ACTIVE_LOW, IPT_UNUSED )
PORT_START( "pcvideo_cga_config" )
PORT_BIT( 0xff, IP_ACTIVE_HIGH, IPT_UNUSED )
INPUT_PORTS_END
/***********************************
* Graphics Layouts *
***********************************/
/****************************************
* Graphics Decode Information *
****************************************/
//static GFXDECODE_START( 4enlinea )
//GFXDECODE_END
/****************************************
* Machine Start/Reset *
****************************************/
void _4enlinea_state::machine_start()
{
}
void _4enlinea_state::machine_reset()
{
}
/***********************************
* Machine Drivers *
***********************************/
void _4enlinea_isa8_cards(device_slot_interface &device)
{
device.option_add_internal("4enlinea", ISA8_CGA_4ENLINEA);
}
/* TODO: irq sources are unknown */
INTERRUPT_GEN_MEMBER(_4enlinea_state::_4enlinea_irq)
{
if(m_irq_count == 0)
{
//device.execute().pulse_input_line(INPUT_LINE_NMI, attotime::zero);
}
else
device.execute().set_input_line(0, HOLD_LINE);
m_irq_count++;
m_irq_count&=3;
}
INTERRUPT_GEN_MEMBER(_4enlinea_state::_4enlinea_audio_irq)
{
device.execute().set_input_line(0, HOLD_LINE);
}
MACHINE_CONFIG_START(_4enlinea_state::_4enlinea)
/* basic machine hardware */
MCFG_DEVICE_ADD("maincpu", Z80, PRG_CPU_CLOCK)
MCFG_DEVICE_PROGRAM_MAP(main_map)
MCFG_DEVICE_IO_MAP(main_portmap)
MCFG_DEVICE_PERIODIC_INT_DRIVER(_4enlinea_state, _4enlinea_irq, 60) //TODO
// MCFG_DEVICE_PERIODIC_INT_DRIVER(_4enlinea_state, irq0_line_hold, 4*35)
MCFG_DEVICE_ADD("audiocpu", Z80, SND_CPU_CLOCK)
MCFG_DEVICE_PROGRAM_MAP(audio_map)
MCFG_DEVICE_IO_MAP(audio_portmap)
MCFG_DEVICE_PERIODIC_INT_DRIVER(_4enlinea_state, _4enlinea_audio_irq, 60) //TODO
// FIXME: determine ISA bus clock
ISA8(config, "isa", 0).set_cputag("maincpu");
MCFG_DEVICE_ADD("isa1", ISA8_SLOT, 0, "isa", _4enlinea_isa8_cards, "4enlinea", true)
/* 6845 clock is a guess, since it's a UM6845R embedded in the UM487F.
CRTC_CLOCK is 8MHz, entering for pin 1 of UM487F. This clock is used
only for UM6845R embedded mode. The frequency divisor is unknown.
CRTC_CLOCK / 4.0 = 66.961296 Hz.
CRTC_CLOCK / 4.5 = 59.521093 Hz.
CRTC_CLOCK / 5.0 = 53.569037 Hz.
*/
/* sound hardware */
SPEAKER(config, "mono").front_center();
AY8910(config, m_ay, SND_AY_CLOCK);
m_ay->port_a_read_callback().set_ioport("IN-P2");
m_ay->port_b_read_callback().set_ioport("IN-P1");
m_ay->add_route(ALL_OUTPUTS, "mono", 0.50);
MACHINE_CONFIG_END
/***********************************
* Rom Load *
***********************************/
ROM_START( 4enlinea )
ROM_REGION( 0x10000, "maincpu", 0 )
ROM_LOAD( "cuatro_en_linea_27c256__cicplay-2.ic6", 0x0000, 0x8000, CRC(f8f14bf8) SHA1(e48fbedbd1b9be6fb56a0f65db80eddbedb487c7) )
ROM_REGION( 0x10000, "audiocpu", 0 )
ROM_LOAD( "cuatro_en_linea_27c256__cicplay-1.ic19", 0x0000, 0x8000, CRC(307a57a3) SHA1(241329d919ec43d0eeb1dad0a4db6cf6de06e7e1) )
ROM_REGION( 0x0800, "eeprom", 0 ) /* default serial EEPROM */
ROM_LOAD( "cuatro_en_linea_x24c16p__nosticker.ic17", 0x000, 0x800, CRC(21f81f5a) SHA1(00b10eee5af1ca79ced2878f4be4cac2bb8d26a0) )
ROM_REGION( 0x200, "plds", 0 )
ROM_LOAD( "cuatro_en_linea_gal16v8as__nosticker.ic04", 0x000, 0x117, CRC(094edf29) SHA1(428a2f6568ac1032833ee0c65fa8304967a58607) )
ROM_END
/***********************************
* Game Drivers *
***********************************/
/* YEAR NAME PARENT MACHINE INPUT CLASS INIT ROT COMPANY FULLNAME FLAGS */
GAME( 1991, 4enlinea, 0, _4enlinea, 4enlinea, _4enlinea_state, empty_init, ROT0, "Compumatic", "Cuatro en Linea", MACHINE_NOT_WORKING )