summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/drivers/unichamp.cpp
blob: 0c64b8c5d4e8acbfc8c07f59a6e13ff406334b19 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
// license:BSD-3-Clause
// copyright-holders:David Viens
/************************************************************************
 *  Unisonic Champion 2711 (late 1977 based on part dates)
 *
 *  Driver from plgDavid (David Viens)
 *
 *  Thanks to Sylvain De Chantal (Sly D.C.) for the 2 test units,
 *  carts and FAQ: http://www.ccjvq.com/slydc/index/faq/2711
 *
 *  Thanks to Paul Robson for the GIC font rom.
 *  (http://worstconsole.blogspot.ca/2012/12/the-worstconsoleever.html)
 *  Note a spare dead GIC has been given to Lord Nightmare and should be sent for decap!
 *
 *  The Unisonc Champion is the only known GI "Gimini Mid-Range 8950 Programmable Game Set"
 *  to ever reach the market, and only in limited quantities (aprox 500 units ever built)
 *
 *  Architecture:
 *  Master IC : AY-3-8800-1 Graphics Interface (A.K.A. GIC, 40 pin)
 *  Slave  IC : CP1610 CPU (40 pin, same as in the Intellivision)
 *  EXEC ROM  : 9501-01009 (40 pin) at 0x0800 (factory mapped)
 *
 *  The GIC generates the CPU Clock, the video signals and the audio.
 *  The CPU does NOT access the GIC directly.
 *  One way CPU->GIC 'communication' takes place through 256 bytes of shared RAM
 *  (using two 4x256 TMS4043NL-2 (2112-1) Static Rams at U3 and U4)
 *
 *  In this design the GIC only allows the CPU to use the BUS (and shared RAM)
 *  a fraction of the frame time. (4.33ms for each 16.69ms, or 26% of the time)
 *  (the real ratio of clocks is 7752/29868 )
 *
 *  Boot: When the GIC let go of !RESET_OUT the EXEC Rom pushes 0x800 onto
 *  the bus for the CPU to fetch and place in R7 to start execution.
 *  This first CPU slice only last 3ms, then the GIC sets the CPU's BUSRQ low,
 *  stalling it for 12.36ms, then sets it high for 4.33ms etc...
 *  59.95 times a second - NTSC
 ************************************************************************/

#include "emu.h"

#include "cpu/cp1610/cp1610.h"
#include "video/gic.h"

#include "bus/generic/slot.h"
#include "bus/generic/carts.h"

#include "emupal.h"
#include "screen.h"
#include "softlist.h"
#include "speaker.h"


class unichamp_state : public driver_device
{
public:
	unichamp_state(const machine_config &mconfig, device_type type, const char *tag)
		: driver_device(mconfig, type, tag),
		m_maincpu(*this, "maincpu"),
		m_gic(*this, "gic"),
		m_cart(*this, "cartslot"),
		m_ctrls(*this, "CTRLS"){}

	required_device<cpu_device> m_maincpu;
	required_device<gic_device> m_gic;
	required_device<generic_slot_device> m_cart;

	uint8_t m_ram[256];
	void init_unichamp();
	virtual void machine_start() override;
	virtual void machine_reset() override;
	DECLARE_PALETTE_INIT(unichamp);

	DECLARE_READ8_MEMBER(bext_r);

	DECLARE_READ8_MEMBER(unichamp_gicram_r);
	DECLARE_WRITE8_MEMBER(unichamp_gicram_w);

	DECLARE_READ16_MEMBER(unichamp_trapl_r);
	DECLARE_WRITE16_MEMBER(unichamp_trapl_w);

	DECLARE_READ16_MEMBER(read_ff);

	uint32_t screen_update_unichamp(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);

	void unichamp(machine_config &config);
	void unichamp_mem(address_map &map);
protected:
	required_ioport m_ctrls;
	virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;
};

PALETTE_INIT_MEMBER(unichamp_state, unichamp)
{
	/*
	palette.set_pen_color(GIC_BLACK, rgb_t(0x00, 0x00, 0x00));
	palette.set_pen_color(GIC_RED,   rgb_t(0xAE, 0x49, 0x41));//(from box shot)
	palette.set_pen_color(GIC_GREEN, rgb_t(0x62, 0x95, 0x88));//(from box shot)
	palette.set_pen_color(GIC_WHITE, rgb_t(0xFF, 0xFF, 0xFF));
	*/

	//using from intv.c instead as suggested by RB
	palette.set_pen_color(GIC_BLACK, rgb_t(0x00, 0x00, 0x00));
	palette.set_pen_color(GIC_RED,   rgb_t(0xFF, 0x3D, 0x10));
	//palette.set_pen_color(GIC_GREEN, rgb_t(0x38, 0x6B, 0x3F)); //intv's DARK GREEN
	palette.set_pen_color(GIC_GREEN, rgb_t(0x00, 0xA7, 0x56)); //intv's GREEN
	palette.set_pen_color(GIC_WHITE, rgb_t(0xFF, 0xFC, 0xFF));
}


void unichamp_state::unichamp_mem(address_map &map)
{
	map.global_mask(0x1FFF); //B13/B14/B15 are grounded!
	map(0x0000, 0x00FF).rw(FUNC(unichamp_state::unichamp_gicram_r), FUNC(unichamp_state::unichamp_gicram_w)).umask16(0x00ff);
	map(0x0100, 0x07FF).rw(FUNC(unichamp_state::unichamp_trapl_r), FUNC(unichamp_state::unichamp_trapl_w));
	map(0x0800, 0x0FFF).rom().region("maincpu", 0);   // Carts and EXE ROM, 10-bits wide
}


static INPUT_PORTS_START( unichamp )
	PORT_START( "CTRLS" )
	PORT_BIT( 0x01,  IP_ACTIVE_HIGH, IPT_KEYBOARD ) PORT_CODE(KEYCODE_Y) PORT_CHAR('Y')// P1 YES (EBCA0)
	PORT_BIT( 0x02,  IP_ACTIVE_HIGH, IPT_KEYBOARD ) PORT_CODE(KEYCODE_N) PORT_CHAR('N')// P1 NO  (EBCA1)
	PORT_BIT( 0x04,  IP_ACTIVE_HIGH, IPT_KEYBOARD ) PORT_CODE(KEYCODE_A) PORT_CHAR('A')// P2 YES (EBCA2)
	PORT_BIT( 0x08,  IP_ACTIVE_HIGH, IPT_KEYBOARD ) PORT_CODE(KEYCODE_S) PORT_CHAR('S')// P2 NO  (EBCA3)
	PORT_BIT( 0x10,  IP_ACTIVE_HIGH, IPT_UNUSED )
	PORT_BIT( 0x20,  IP_ACTIVE_HIGH, IPT_UNUSED )
	PORT_BIT( 0x40,  IP_ACTIVE_HIGH, IPT_UNUSED )
	PORT_BIT( 0x80,  IP_ACTIVE_HIGH, IPT_UNUSED )
INPUT_PORTS_END


void unichamp_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
	//TODO should we add an explicit Reset button in there just like the controller?
}


READ8_MEMBER(unichamp_state::bext_r)
{
	//The BEXT instruction pushes a user-defined nibble out on the four EBCA pins (EBCA0 to EBCA3)
	//and reads the ECBI input pin for HIGH or LOW signal to know whether or not to branch

	//The unisonic control system couldnt be simpler in design.
	//Each of the two player controllers has three buttons:
	//one tying !RESET(GIC pin 21) to ground when closed - resetting the WHOLE system.
	//a YES button (connecting EBCA0 to EBCI for Player1 and EBC2 to EBCI for Player2)
	//a NO  button (connecting EBCA1 to EBCI for Player1 and EBC3 to EBCI for Player2)

	//The CPU outputs a MASK of whatever it needs and checks the result.
	//EG: Any player can choose if one or two players are going to play the game for instance

	uint8_t port = ioport("CTRLS")->read() & 0x0F; ////only lower nibble

	//We need to return logical high or low on the EBCI pin
	return (port & offset)>0?1:0;
}


void unichamp_state::init_unichamp()
{
}

READ16_MEMBER(unichamp_state::read_ff)
{
	return 0xffff;
}

void unichamp_state::machine_start()
{
	if (m_cart->exists()){
		//flip endians in more "this surely exists in MAME" way?
		//NOTE The unichamp roms have the same endianness as intv on disk and in memory
		uint8_t*ptr   = m_cart->get_rom_base();
		size_t size = m_cart->get_rom_size();
		for(size_t i=0;i<size;i+=2){
			uint8_t TEMP = ptr[i];
			ptr[i] = ptr[i+1];
			ptr[i+1] = TEMP;
		}
		m_maincpu->space(AS_PROGRAM).install_read_handler(0x1000, 0x17ff,
					read16_delegate(FUNC(generic_slot_device::read16_rom),(generic_slot_device*)m_cart));
	} else
		m_maincpu->space(AS_PROGRAM).install_read_handler(0x1000, 0x17ff,
					read16_delegate(FUNC(unichamp_state::read_ff), this));
}

/* Set Reset and INTR/INTRM Vector */
void unichamp_state::machine_reset()
{
	/*
	the intv driver did not explain this but from the CP1600 manual:
	When MSYNC* goes inactive (high), the bus control signals issue lAB,
	and the CPU inputs from the bus into the PC the starting address of the main program.
	Note that the initialization address can be defined by the user at any desired bus address or
	can be the default address resulting from the logical state of the non-driven bus
	*/

	//The Unisonic EXEC ROM chip (9501-01009) is self mapped at 0x0800
	//The cart ROMS are self mapped to 0x1000
	//upon boot the EXEC ROM puts 0x0800 on the bus for the CPU to use as first INT vector

	m_maincpu->set_input_line_vector(CP1610_RESET,     0x0800);
	m_maincpu->set_input_line_vector(CP1610_INT_INTRM, 0x0804);//not used anyway
	m_maincpu->set_input_line_vector(CP1610_INT_INTR,  0x0804);//not used anyway

	/* Set initial PC */
	m_maincpu->set_state_int(cp1610_cpu_device::CP1610_R7, 0x0800);

	memset(m_ram, 0, sizeof(m_ram));
}

uint32_t unichamp_state::screen_update_unichamp(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
	return m_gic->screen_update(screen, bitmap, cliprect);
}

READ8_MEMBER( unichamp_state::unichamp_gicram_r )
{
	return m_ram[offset];
}

WRITE8_MEMBER( unichamp_state::unichamp_gicram_w )
{
	m_ram[offset] = data;
}

READ16_MEMBER( unichamp_state::unichamp_trapl_r )
{
	logerror("trapl_r(%x)\n",offset);
	return (int)0;
}

WRITE16_MEMBER( unichamp_state::unichamp_trapl_w )
{
	logerror("trapl_w(%x) = %x\n",offset,data);
}

MACHINE_CONFIG_START(unichamp_state::unichamp)
	/* basic machine hardware */

	//The CPU is really clocked this way:
	//MCFG_DEVICE_ADD("maincpu", CP1610, XTAL(3'579'545)/4)
	//But since it is only running 7752/29868 th's of the time...
	//TODO find a more accurate method? (the emulation will me the same though)
	MCFG_DEVICE_ADD("maincpu", CP1610, (7752.0/29868.0)*XTAL(3'579'545)/4)

	MCFG_DEVICE_PROGRAM_MAP(unichamp_mem)
	MCFG_QUANTUM_TIME(attotime::from_hz(60))
	MCFG_CP1610_BEXT_CALLBACK(READ8(*this, unichamp_state, bext_r))

	/* video hardware */
	MCFG_SCREEN_ADD("screen", RASTER)
	MCFG_SCREEN_RAW_PARAMS( XTAL(3'579'545),
							gic_device::LINE_CLOCKS,
							gic_device::START_ACTIVE_SCAN,
							gic_device::END_ACTIVE_SCAN,
							gic_device::LINES,
							gic_device::START_Y,
							gic_device::START_Y + gic_device::SCREEN_HEIGHT )

	MCFG_SCREEN_UPDATE_DRIVER(unichamp_state, screen_update_unichamp)
	MCFG_SCREEN_PALETTE("palette")

	MCFG_PALETTE_ADD("palette", 4)
	MCFG_PALETTE_INIT_OWNER(unichamp_state, unichamp)

	/* sound hardware */
	SPEAKER(config, "mono").front_center();
	MCFG_GIC_ADD( "gic", XTAL(3'579'545), "screen", READ8(*this, unichamp_state, unichamp_gicram_r) )
	MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.40)

	/* cartridge */
	MCFG_GENERIC_CARTSLOT_ADD("cartslot", generic_linear_slot, "unichamp_cart")
	MCFG_GENERIC_EXTENSIONS("bin,rom")
	MCFG_SOFTWARE_LIST_ADD("cart_list", "unichamp")

MACHINE_CONFIG_END


ROM_START(unichamp)
	ROM_REGION(0x1000,"maincpu", ROMREGION_ERASEFF)

	ROM_LOAD16_WORD( "9501-01009.u2", 0, 0x1000, CRC(49a0bd8f) SHA1(f4d126d3462ad351da4b75d76c75942d5a6f27ef))

	//these below are for local tests. you can use them in softlist or -cart
	//ROM_LOAD16_WORD( "pac-02.bin",   0x1000<<1, 0x1000, CRC(fe3213be) SHA1(5b9c407fe86865f3454d4be824a7f2bf53478f73))
	//ROM_LOAD16_WORD( "pac-03.bin",   0x1000<<1, 0x1000, CRC(f81f04bd) SHA1(82e2a0fda1787d5835c457ee5745b0db0cebe079))
	//ROM_LOAD16_WORD( "pac-04.bin",   0x1000<<1, 0x1000, CRC(cac09841) SHA1(bc9db83f26ed0810938156db6b104b4576754225))
	//ROM_LOAD16_WORD( "pac-05.bin",   0x1000<<1, 0x1000, CRC(d54a6090) SHA1(e85593096f43dcf14b08fd2c9fda277008a8df8b))
ROM_END


CONS( 1977, unichamp, 0, 0, unichamp, unichamp, unichamp_state, init_unichamp, "Unisonic", "Champion 2711", 0/*MACHINE_IMPERFECT_GRAPHICS*/ )