summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/drivers/pachifev.cpp
blob: 83c81ef81861b39fea0e042b9a97aab0383f965d (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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
// license:BSD-3-Clause
// copyright-holders:Tomasz Slanina
/**********************************************************************************************************

PACHI FEVER / (c) 1983 SANKI DENSHI KOGYO

===========================================================================================================

Many thanks to Olivier Galibert and Wilbert Pol for identify the CPU

Driver by Tomasz Slanina

- CPU has scratched surface and custom marks "GEN480830   DBS 30102" plus TI logo.
  There's "GENZUK 01" print on the PCB, near the chip.
  It's  TMS9995 or derivative ( decrementer + lvl3 interrupt, internal ram)
  XTAL:12.000MHZ
- VDP has also custom label ( "RY 050012    DDU 30600" ) plus TI logo
  Seems to be TMS9928A
  XTAL:10.738MHZ
- 2xY2404 ( SN76489A comaptible? ) for music and sfx
- MSM5205 - sample player (see below)

- TODO:
  - what's the correct game title - Pachifever ? Fever 777 ?
  - unknown writes ($ffxx range)
  - coins : try to understand why they don't always register (use PORT_IMPULSE ?)
  - controls : unused bits (is the BUTTON1 used _only_ for entering initials?)
  - controls : make PLUNGER (or whatever it is in reality) implementation more clear
               and find a better way to handle cocktail mode;
               here's some code used to read plunger pos(angle?):

0284: 04CC             clr  R12             ; CRU address
0286: 0208 FF00        li   R8,>ff00        ; R8=ff00 - initial data ($ff)
028A: D688             movb R8,*R10         ; ff-> ff40  - write to output
028C: 3449             stcr R9,1            ; CRU read (one bit)
028E: 1603             jne  >0296           ; not zer0 - end
0290: 0228 FC00        ai   R8,>fc00        ; R8=R8-4
0294: 18FA             joc  >028a           ; loop

0296: D6A0 020F        movb @>020f,*R10     ; 00 ->ff40 - end of controls read
029A: 045B             b    *R11            ; b $1ca - process data in R8


Stephh's notes (based on the game TMS9995 code and some tests) :

  - "Difficulty" Dip Switch is a guess, but I can't see what else it can be. The values (-8 to +8) are
    correct, but it's impossible for me to tell what are the easiest and the hardest settings.
  - In "attract mode", the game plays the 4th level. This "attract mode" will end when the computer's
    balls reaches the limit or when the time is over (same rules as for the players - see gameplay below).
  - Gameplay :
      * On levels 1 to 3, your number of balls must reach the limit based on DSW2 bits 6 and 7
        to complete the level; once this is done, you go to next level or bonus stage (after level 3).
        Time is reset to value based on DSW2 bits 6 and 7 only at the beginning of level 1,
        while balls are reset on each level to value based on DSW1 bits 4 and 5.
      * On the bonus stage, you only have 3 balls (which are decremented from your number of balls)
        to get maximum bonus time, bonus balls, and points. During this stage, time is not decremented.
      * On level 4, your number of balls must reach the limit based on DSW2 bits 4 and 5 (making the game
        sometimes harder) to complete the level; once this is done, the game is over for current player.
        The main difference with levels 1 to 3 is that balls are not reset at the beginning of the level.
      * Once time or game is over, the game switches player 2 (if available). When all players have ended
        their game, they can decide to continue : a message is displayed but you don't see any timer.
        When you continue, only the balls aren't reset, while score, time and level (GASP !) are.
        If you want to continue in a 2 players game, BOTH players will have to continue, which means that
        you must have at least 2 credits ("REPLAY") and that you can't continue player 2 without player 1.
      * If you manage to get a score, use BUTTON1 to cycle through available symbols (letters A-Z and '.'),
        and pull the plunger to at least 63% (the code expects a value >= 0xa0) to go to next initial.
        Be aware that again there is a timer to do so, but that again the timer is not displayed.
  - Useful addresses :
      * 0xe001.b : level (0x00-0x04 : 0x01 = level 1 - 0x02 = level 2 - 0x00 = level 3 - 0x3 = bonus - 0x04 = level 4)
      * 0xe00f.b : player (0x00 = P1 - 0x01 = P2)
      * 0xe016.w : P1 balls (MSB first)
      * 0xe018.d : P1 score (MSB first)
      * 0xe01c.w : P1 time  (MSB first)
      * 0xe01e.w : P2 balls (MSB first)
      * 0xe024.w : P2 time  (MSB first)
      * 0xe020.d : P1 score (MSB first)
      * 0xe028.w : limit (DSW2 bits 6 and 7) for levels 1 to 3 (MSB first)
      * 0xe02a.w : limit (DSW2 bits 4 and 5) for level  4      (MSB first)

***********************************************************************************************************/

#include "emu.h"
#include "cpu/tms9900/tms9995.h"
#include "sound/msm5205.h"
#include "sound/sn76496.h"
#include "video/tms9928a.h"
#include "speaker.h"

#define USE_MSM 0
#define NUM_PLUNGER_REPEATS    50

class pachifev_state : public driver_device
{
public:
	pachifev_state(const machine_config &mconfig, device_type type, const char *tag)
		: driver_device(mconfig, type, tag),
		m_maincpu(*this, "maincpu") { }

	/* controls related */
	int m_power;
	int m_max_power;
	int m_input_power;
	int m_previous_power;
	int m_cnt;

	uint32_t m_adpcm_pos;
	uint8_t m_adpcm_idle;
	uint8_t m_trigger;
	uint8_t m_adpcm_data;
	DECLARE_WRITE8_MEMBER(controls_w);
	DECLARE_READ8_MEMBER(controls_r);
	virtual void machine_start() override;
	virtual void machine_reset() override;
	DECLARE_WRITE_LINE_MEMBER(vblank_w);
	required_device<cpu_device> m_maincpu;
	void pachifev(machine_config &config);
	void pachifev_cru(address_map &map);
	void pachifev_map(address_map &map);
};

WRITE8_MEMBER(pachifev_state::controls_w)
{
	if(!data)
	{
		/*end of input read*/
		m_power=0;
		m_max_power=m_input_power;
		if(--m_cnt <= 0) /* why to do it N times? no idea.. someone should fix it */
		{
			m_cnt=0;
			m_input_power=0;
		}
	}
}

READ8_MEMBER(pachifev_state::controls_r)
{
	int output_bit=(m_power < m_max_power)?0:1;
	++m_power;
	return output_bit;
}

void pachifev_state::pachifev_map(address_map &map)
{
	map(0x0000, 0xdfff).rom();

	map(0xe000, 0xe7ff).ram();
	map(0xf000, 0xf0fb).noprw();  /* internal ram */
	map(0xff00, 0xff00).portr("IN0");
	map(0xff02, 0xff02).portr("IN1");
	map(0xff04, 0xff04).portr("DSW1");
	map(0xff06, 0xff06).portr("DSW2");
	map(0xff08, 0xff08).portr("DSW3");
	map(0xff10, 0xff10).rw("tms9928a", FUNC(tms9928a_device::vram_read), FUNC(tms9928a_device::vram_write));
	map(0xff12, 0xff12).rw("tms9928a", FUNC(tms9928a_device::register_read), FUNC(tms9928a_device::register_write));
	map(0xff20, 0xff20).w("y2404_1", FUNC(y2404_device::write));
	map(0xff30, 0xff30).w("y2404_2", FUNC(y2404_device::write));
	map(0xff40, 0xff40).w(this, FUNC(pachifev_state::controls_w));
	map(0xff50, 0xff50).nopw(); /* unknown */
	map(0xfffa, 0xfffb).noprw(); /* decrementer */
	map(0xfffc, 0xffff).noprw(); /* nmi */
}

void pachifev_state::pachifev_cru(address_map &map)
{
	map(0x000, 0x000).r(this, FUNC(pachifev_state::controls_r));
}


/* verified from TMS9995 code */
static INPUT_PORTS_START( pachifev )
	/* 0xff00, cpl'ed -> 0xf0a0 (internal RAM) */
	PORT_START("IN0")
	PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_BUTTON1 )                 /* select initial for player 1 and player 2 (upright) */
	PORT_BIT( 0x10, IP_ACTIVE_LOW, IPT_START1 )
	PORT_BIT( 0x20, IP_ACTIVE_LOW, IPT_START2 )
	PORT_BIT( 0x80, IP_ACTIVE_LOW, IPT_COIN1 )
	PORT_BIT( 0x4d, IP_ACTIVE_LOW, IPT_UNUSED )

	/* 0xff02, cpl'ed -> 0xf0a1 (internal RAM) */
	PORT_START("IN1")
	PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_BUTTON1 ) PORT_COCKTAIL   /* select initial for player 2 (cocktail) */
	PORT_BIT( 0xfd, IP_ACTIVE_LOW, IPT_UNUSED )

	/* 0xff04, cpl'ed -> 0xf0a2 (internal RAM) */
	PORT_START("DSW1")
	PORT_DIPNAME( 0x07, 0x07, "Difficulty ?" )              /* table at 0x5000 - see notes */
	PORT_DIPSETTING(    0x07, "-8" )
	PORT_DIPSETTING(    0x06, "-6" )
	PORT_DIPSETTING(    0x05, "-4" )
	PORT_DIPSETTING(    0x04, "-2" )
	PORT_DIPSETTING(    0x03, "+2" )
	PORT_DIPSETTING(    0x02, "+4" )
	PORT_DIPSETTING(    0x01, "+6" )
	PORT_DIPSETTING(    0x00, "+8" )
	PORT_DIPNAME( 0x08, 0x08, DEF_STR( Cabinet ) )
	PORT_DIPSETTING(    0x08, DEF_STR( Upright ) )
	PORT_DIPSETTING(    0x00, DEF_STR( Cocktail ) )
	PORT_DIPNAME( 0x30, 0x00, "Balls" )                     /* table at 0x5020 */
	PORT_DIPSETTING(    0x30, "25" )
	PORT_DIPSETTING(    0x20, "50" )
	PORT_DIPSETTING(    0x10, "100" )
	PORT_DIPSETTING(    0x00, "200" )
	PORT_DIPNAME( 0xc0, 0xc0, DEF_STR( Coinage ) )
	PORT_DIPSETTING(    0xc0, DEF_STR( 1C_1C ) )
	PORT_DIPSETTING(    0x80, DEF_STR( 1C_2C ) )
	PORT_DIPSETTING(    0x40, DEF_STR( 1C_3C ) )
	PORT_DIPSETTING(    0x00, DEF_STR( 1C_4C ) )

	/* 0xff06, cpl'ed -> 0xf0a3 (internal RAM) */
	PORT_START("DSW2")
	PORT_DIPNAME( 0x03, 0x00, "Time" )                      /* table at 0x5028 */
	PORT_DIPSETTING(    0x00, "180" )
	PORT_DIPSETTING(    0x01, "120" )
	PORT_DIPSETTING(    0x02, "150" )
	PORT_DIPSETTING(    0x03, "90" )
	PORT_DIPUNUSED( 0x04, 0x04 )
	PORT_DIPUNUSED( 0x08, 0x08 )
	PORT_DIPNAME( 0x30, 0x30, "Limit (Level 4)" )           /* table at 0x5038 - stored at 0xe02a.w */
	PORT_DIPSETTING(    0x30, "500" )
	PORT_DIPSETTING(    0x20, "1000" )
	PORT_DIPSETTING(    0x10, "1500" )
	PORT_DIPSETTING(    0x00, "2000" )
	PORT_DIPNAME( 0xc0, 0xc0, "Limit (Levels 1 to 3)" )     /* table at 0x5030 - stored at 0xe028.w */
	PORT_DIPSETTING(    0xc0, "300" )
	PORT_DIPSETTING(    0x80, "500" )
	PORT_DIPSETTING(    0x40, "1000" )
	PORT_DIPSETTING(    0x00, "1500" )

	/* 0xff08, cpl'ed -> 0xf0a4 (internal RAM) */
	PORT_START("DSW3")
	PORT_DIPNAME( 0x01, 0x00, DEF_STR( Demo_Sounds ) )
	PORT_DIPSETTING(    0x01, DEF_STR( Off ) )
	PORT_DIPSETTING(    0x00, DEF_STR( On ) )
	PORT_DIPUNUSED( 0x02, 0x02 )
	PORT_DIPUNUSED( 0x04, 0x04 )
	PORT_DIPNAME( 0x18, 0x18, "Bonus Time" )                /* table at 0x3500 */
	PORT_DIPSETTING(    0x18, "5" )
	PORT_DIPSETTING(    0x10, "8" )
	PORT_DIPSETTING(    0x08, "10" )
	PORT_DIPSETTING(    0x00, "15" )
	PORT_DIPNAME( 0xe0, 0xe0, "Bonus Ball" )                /* table at 0x3508 */
	PORT_DIPSETTING(    0xe0, "3" )
	PORT_DIPSETTING(    0xc0, "4" )
	PORT_DIPSETTING(    0xa0, "5" )
	PORT_DIPSETTING(    0x80, "8" )
	PORT_DIPSETTING(    0x60, "10" )
	PORT_DIPSETTING(    0x40, "11" )
	PORT_DIPSETTING(    0x20, "13" )
	PORT_DIPSETTING(    0x00, "15" )

	PORT_START("PLUNGER_P1")
	PORT_BIT( 0x3f, 0x00, IPT_POSITIONAL ) PORT_MINMAX(0x00,0x3f) PORT_SENSITIVITY(30) PORT_KEYDELTA(4) PORT_CENTERDELTA(0xff)

	PORT_START("PLUNGER_P2")
	PORT_BIT( 0x3f, 0x00, IPT_POSITIONAL ) PORT_MINMAX(0x00,0x3f) PORT_SENSITIVITY(30) PORT_KEYDELTA(4) PORT_CENTERDELTA(0xff) PORT_COCKTAIL
INPUT_PORTS_END


#if USE_MSM


WRITE_LINE_MEMBER(pachifev_state::pf_adpcm_int)
{
	if (m_adpcm_pos >= 0x4000 || m_adpcm_idle)
	{
		m_adpcm_idle = 1;
		m_msm->reset_w(1);
		m_trigger = 0;
	}
	else
	{
		uint8_t *ROM = memregion("adpcm")->base();

		m_adpcm_data = ((m_trigger ? (ROM[m_adpcm_pos] & 0x0f) : (ROM[m_adpcm_pos] & 0xf0)>>4) );
		m_msm->data_w(m_adpcm_data & 0xf);
		m_trigger^=1;
		if(m_trigger == 0)
		{
			m_adpcm_pos++;
			if((ROM[m_adpcm_pos] & 0xff) == 0xff)
				m_adpcm_idle = 1;
		}
	}
}

#endif

void pachifev_state::machine_reset()
{
	tms9995_device* cpu = static_cast<tms9995_device*>(machine().device("maincpu"));
	// Pulling down the line on RESET configures the CPU to insert one wait
	// state on external memory accesses
	cpu->ready_line(CLEAR_LINE);
	cpu->reset_line(ASSERT_LINE);

	m_power=0;
	m_max_power=0;
	m_input_power=0;
	m_previous_power=0;
	m_cnt=0;

#if USE_MSM
	m_adpcm_pos = 0;
#endif
}


WRITE_LINE_MEMBER(pachifev_state::vblank_w)
{
	if (state)
	{
		static const char *const inname[2] = { "PLUNGER_P1", "PLUNGER_P2" };

		/* I wish I had found a better way to handle cocktail inputs, but I can't find a way to access internal RAM */
		/* (bit 5 of 0xf0aa : 0 = player 1 and 1 = player 2 - bit 6 of 0xf0aa : 0 = upright and 1 = cocktail). */
		/* All I found is that in main RAM, 0xe00f.b determines the player : 0x00 = player 1 and 0x01 = player 2. */
		address_space &ramspace = m_maincpu->space(AS_PROGRAM);
		uint8_t player = 0;

		if ((ramspace.read_byte(0xe00f) == 0x01) && ((ioport("DSW1")->read() & 0x08) == 0x00))
			player = 1;

		int current_power=ioport(inname[player])->read() & 0x3f;
		if(current_power != m_previous_power)
		{
			popmessage    ("%d%%", (current_power * 100) / 0x3f);
		}

		if( (!current_power) && (m_previous_power) )
		{
			m_input_power=m_previous_power;
			m_cnt=NUM_PLUNGER_REPEATS;
		}

		m_previous_power=current_power;
	}

}

void pachifev_state::machine_start()
{
	save_item(NAME(m_power));
	save_item(NAME(m_max_power));
	save_item(NAME(m_input_power));
	save_item(NAME(m_previous_power));
	save_item(NAME(m_cnt));
}

MACHINE_CONFIG_START(pachifev_state::pachifev)

	// CPU TMS9995, standard variant; no line connections
	MCFG_TMS99xx_ADD("maincpu", TMS9995, XTAL(12'000'000), pachifev_map, pachifev_cru)

	/* video hardware */
	MCFG_DEVICE_ADD( "tms9928a", TMS9928A, XTAL(10'738'635) / 2 )
	MCFG_TMS9928A_VRAM_SIZE(0x4000)
	MCFG_TMS9928A_SCREEN_ADD_NTSC( "screen" )
	MCFG_SCREEN_UPDATE_DEVICE( "tms9928a", tms9928a_device, screen_update )
	MCFG_SCREEN_VBLANK_CALLBACK(WRITELINE(*this, pachifev_state, vblank_w))

	/* sound hardware */
	SPEAKER(config, "mono").front_center();
#if USE_MSM
	MCFG_DEVICE_ADD("adpcm", MSM5205, XTAL(384'000))  /* guess */
	MCFG_MSM5205_VCLK_CB(WRITELINE(*this, pachifev_state,pf_adpcm_int))    /* interrupt function */
	MCFG_MSM5205_PRESCALER_SELECTOR(MSM5205_S48_4B)    /* 8kHz */
	MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 1.00)
#endif
	MCFG_DEVICE_ADD("y2404_1", Y2404, XTAL(10'738'635)/3) /* guess */
	MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.30)
	MCFG_DEVICE_ADD("y2404_2", Y2404, XTAL(10'738'635)/3) /* guess */
	MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.30)
MACHINE_CONFIG_END

ROM_START( pachifev )
	ROM_REGION( 0x10000, "maincpu", 0 )
	ROM_LOAD( "ic42.00",   0x00000, 0x2000, CRC(9653546e) SHA1(0836d01118241d38bbf61732275afe3ae47d0622) )
	ROM_LOAD( "ic43.01",   0x02000, 0x2000, CRC(5572dce5) SHA1(fad45b33e095ac6e3ed3d7cdc3d8678c153a1b38) )
	ROM_LOAD( "ic44.02",   0x04000, 0x2000, CRC(98b3841f) SHA1(0563139877bf01e1673767ee1798bbcf68adadea) )
	ROM_LOAD( "ic45.03",   0x06000, 0x2000, CRC(6b76e6fa) SHA1(5be10ab0b76e2061fc7e9c77649572955bee7661) )
	ROM_LOAD( "ic46.04",   0x08000, 0x2000, CRC(1c8c66d7) SHA1(3b9b05f35b20d798651c7d5fdb35e6af956615a1) )
	/* empty ROM socket  - no data for 0xa000 - 0xbfff */
	ROM_LOAD( "ic48.50",   0x0c000, 0x2000, CRC(4ff52b70) SHA1(a459b52712250d2ecdbe6aeb8c400806867e9857) )

	ROM_REGION( 0x4000, "adpcm", 0 )
	ROM_LOAD( "ic66.10",   0x0000, 0x2000, CRC(217c573e) SHA1(6fb90865d1d81f5ea00fa7916d0ccb6756ef5ce5) )

ROM_END

GAME( 1983, pachifev,  0,       pachifev,  pachifev, pachifev_state, empty_init, ROT270, "Sanki Denshi Kogyo", "Pachifever", MACHINE_IMPERFECT_SOUND )