Sega Model 1: new driver, TGP HLE checked bit for bit against the real TGP

Virtua Racing, Virtua Formula, Virtua Fighter, Star Wars Arcade, Wing War
(and R360) and NetMerc, with current MAME's sets.

- Driver from current MAME: interrupt controller, interval timers, the
  UART to the Model 1 sound board, the Digital Sound Board for Star Wars
  Arcade; the I/O board firmware is simulated (inputs, DIP switches,
  analog channels, backup RAM).
- Video: C port of current MAME's model1_v.cpp; no fused multiply-add and
  x86 float to int conversions, so every CPU renders as x86.
- TGP: the geometry DSP runs in C (HLE), started from MAME 0.215. With the
  MB86233 core the real TGP program can run next to it (development
  builds, MODEL1_TGP=oracle): every function the games call was rewritten
  from the 315-5573, 315-5711 and 315-5724 programs until the results
  match bit for bit, bugs of the real programs included. Virtua Fighter
  gets its own table (MAME 0.215 used Virtua Racing's, with guessed camera
  functions).
- The TGP sits in the V60 I/O space too (Wing War boots it with an OUT).
- Save states keep the TGP FIFOs, the data ROM bank, the timers and the
  renderer's light setup, so a state loads in a new session.
- C89, own square root helper.

Details and open questions: docs/sega-model1.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_013Vje3pEtpLHubqCGwCgoQX
This commit is contained in:
VanniandClaude Opus 5.5 committed 2026-10-21 12:34:33 +02:00
1 parent 8343ab6b0e
commit 25900bae57
9 files changed
+6198 -2729

No files matched your search

+203
View File
@@ -0,0 +1,203 @@
# Sega Model 1: how the driver works and where it differs from the hardware
Sets: `vr`, `vformula`, `vf`, `swa`, `swaj`, `wingwar`, `wingwaru`,
`wingwarj`, `wingwar360`, `netmerc`.
Files: `src/drivers/model1.c` (boards, maps, ROMs), `src/machine/model1_machine.c`
(the TGP), `src/vidhrdw/model1_vidhrdw.c` (the renderer, ported from current
MAME), `src/sndhrdw/segadsb.c` (the Digital Sound Board of Star Wars Arcade).
## The TGP (geometry coprocessor)
The TGP is a Fujitsu MB86233 DSP running a program from a 315-xxxx ROM:
315-5573 (Virtua Racing, Virtua Formula), 315-5724 (Virtua Fighter), 315-5711
(Star Wars Arcade, Wing War, NetMerc). The V60 sends it commands (a function
number and its arguments) through a FIFO and reads the results back.
The driver runs the TGP functions in C (high level emulation, HLE), started
from MAME 0.215, the last MAME with an HLE TGP. In MAME 0.215 Virtua Racing had
no HLE at all and Star Wars Arcade and Wing War did not work.
### Checked against the real program
The core also contains the MB86233 (from the Model 2 work), so the real TGP
program can run next to the HLE. A development build (`-DMODEL1_TGP_DEV`)
reads `MODEL1_TGP` from the environment:
- `lle`: the game runs on the real TGP program only.
- `oracle`: the game runs on the real TGP program, the HLE gets the same
input, and every call is compared: function, arguments, the results of
both. The pairing follows the input stream (a call ends when the DSP takes
the next function word), so a function giving a wrong number of results
does not shift the others. The TGP RAM both sides keep is compared every
frame. Every 600 frames it prints, for each function, the calls that were
exact, near (rounding), wrong, or gave a wrong number of results.
With this, every function the games call was rewritten from the DSP program
until the results match bit for bit: same table lookups (sine, 1/x, 1/sqrt,
atan come from the TGP's table ROM through its I/O ports, with one
multiply-subtract step), same order of the sums, same comparisons (the ALU's
`-0` is neither negative nor positive). Checked on attract and play of
`vr` (three tracks), `vformula`, `vf`, `swa`, `wingwar`, `wingwar360` and
`netmerc`: no call differs and the TGP RAM stays identical.
Virtua Fighter's program (315-5724) is marked as a bad dump in MAME, but it
runs the game on the MB86233 core, so it is checked the same way. About half
of its functions differ from 315-5573 (matrix stack and saved matrices at
other addresses, the camera, collision bitmaps, ground box test, list walks
in the copro RAM), so it has its own table. MAME 0.215 used the Virtua Racing
table for it, with guessed camera functions (one returned random numbers),
which put the camera far from the fighters. For Virtua Fighter the oracle
also compares the TGP's internal RAM after every call. Over 9000 frames of
attract and fights, 55 functions and 3.8 million calls give the same results
and the same RAM.
The table lookups, the copro RAM (0x2000 words; with address bit 0x40000 set
the address steps by 4) and the list functions follow current MAME.
### Program bugs reproduced
The HLE reproduces what the real programs do, bugs included, because the
games run with them on the real boards:
- 315-5711 `roty` starts its first sum from the DSP's `d` register, which
still holds the dispatch address (a denormal): the first matrix element
gets `0x6c` added to it.
- 315-5711 `normalize` never loads y; it uses whatever the scratch word at
`$71` holds from an earlier function (tracked by the HLE).
- 315-5711 `f47` starts its sum of squares from the z difference.
- 315-5711 `fsqrt` skips the mantissa step and does not return a square root.
- The matrix stack keeps 7 levels; past them the depth only counts (315-5711
pushes again past 8, while its pops past 7 only count). The matrix slots
and the 315-5711 accumulator stack sit in the same TGP RAM, as on the
board.
- 315-5711 `f52` (0x34, matrix inverse; Star Wars Arcade calls it) works
by cofactors, never reads the first element, ORs two elements where a
subtract is expected, and adds in the second element the scratch word
`$11` an earlier function left (push, multiply and read leave a matrix
element or argument there; tracked by the HLE).
- 315-5711 `f56` (0x38, segment against a polygon list) stops at the first
polygon hit, not the nearest one.
- The 315-5711 games use the current matrix before setting it: the HLE starts
from what the TGP RAM holds at power on (zeros in the emulated TGP).
### What is not emulated
- Table entries a program leaves empty (it returns at once) take no
arguments and give no results; 315-5724 functions no game was seen calling
are not emulated, except the simple ones (normalize, divide, set the
translation).
- 315-5711 functions no game was seen calling: self tests 0x04, 0x3d, 0x3e
(results 0), 0x42 (matrix stack pop and multiply, with bugs overwriting the
stack), 0x21 (the program jumps into the middle of another function). They
take and give the right number of words so the command stream stays in step.
### ROMs
The TGP program ROM stays in the sets as in current MAME. For Star Wars
Arcade, Wing War and NetMerc the HLE follows 315-5711 with CRC 6a21f304, as
current MAME. An older dump (CRC c5ddb8fc) differs in two words: the entry
of `f52` (which then reads the wrong RAM and gives NaNs) and the end test
of `f56`. It loads with a checksum warning; the HLE does not use the ROM.
The V60 reaches the TGP in its I/O space as well as in memory, with the
same mirrors as current MAME: Wing War sends its boot command (8, clear the
matrix stack) with an OUT to a mirror of the FIFO, and the real program
waits for it before taking any other command.
## I/O board
The Model 1 I/O board (model1io, model1io2: a Z80 with its own BIOS) is
simulated: inputs, DIP switches, analog channels and the backup are put where
the games read them in the 2 KB dual port RAM:
00-07 analog channels
08-0a IN0 - IN2 (active low)
0b-0d DSW1 - DSW3 (active low, all off)
0e board switches
0f outputs (lamps, coin counters)
11 drive board / motion base output
20 command (2 store the backup 0x100-0x1ff, 3 load it), 21 status
The DIP switches must read as off: 0 turned on the debug displays of Virtua
Racing and NetMerc. The backup is saved in the NVRAM file.
## Sound
Model 1 sound board (68000, two MultiPCM, YM3438) behind an i8251 UART at
31250 baud, as current MAME. Star Wars Arcade also has the Digital Sound
Board (Z80 + MPEG audio), which listens to the sound board's UART.
The simulated I/O board answers at once, so the V60 sends its first sound
commands before the 68000 has finished its init. The real UART holds one byte
and would lose the others; Virtua Fighter then stayed silent. The bytes for
the 68000 are kept in a queue.
Virtua Racing and Virtua Fighter play no sound in attract mode with their
default settings, as on the board.
## Save states
Besides the RAMs and registers, a save state keeps what MAME 0.78 does not
save by itself and what a game sets only once: the TGP FIFOs (a state can
fall between the words of a command, or before the V60 read the results),
the data ROM bank, the time left of the interval and UART timers, and the
renderer's view (viewport, light, light parameters: Virtua Fighter sends
them only when a stage starts, and without them a loaded state drew the
fighters black). Checked by loading in a new process: Virtua Fighter and
Virtua Racing's attract mode go on frame for frame as in a straight run
(analog controls: see Open questions).
## Other boards
- Link board (Virtua Racing, Virtua Formula, Wing War): not emulated; the
games see it with its CPU in reset and run stand-alone.
- R360 motion base (`wingwar360`): the game states it sends are answered as
the real base does, so a game starts. No motion.
- Polhemus tracker (`netmerc`): the board (a 386 with its own program) that
tracks the visor is not emulated. As in current MAME, the visor pose the
game reads at dual port RAM 0x80-0x8b (x, y, z, then three angles in
radians * 8192) is set once at reset, looking ahead. The gun on its mount
is not tracked: it uses the stick (analog channels 0 and 2), and moving
goes where the gun points.
- NetMerc gun mount calibration: the ends of both axes are kept in the
backup RAM (0x38 / 0x3c horizontal, 0x40 / 0x44 vertical). At boot the
game sets its factory calibration (0x46-0xc4, 0x21-0x69) only when one of
them is zero, so a new NetMerc backup RAM is 0-filled (the other games
get 1s, which Virtua Fighter needs): with 1s it kept them, divided by
zero, spun the gun, failed its checksum and stayed on one attract
screen. The stick covers the same range as the factory calibration.
- NetMerc sound: `mpr-18134` (the first sample ROM of the first MultiPCM)
is a bad dump, all 0xff, in the only set known (MAME has the same one).
That MultiPCM has no sample table, so the music it plays is silent; the
effects, on the second MultiPCM, play.
- NetMerc defaults (test menu, GAME ASSIGNMENTS): country Japan, attract
sound off, so attract mode is silent and starts with the Japan warning.
## Video
`model1_vidhrdw.c` is a C port of current MAME's `model1_v.cpp`. Two choices
keep the output the same on every CPU: float to int conversions follow x86
(out of range gives `INT_MIN`, other CPUs saturate), and the files of the TGP
and the renderer are compiled without fused multiply-add (GCC and clang fuse
`a*b+c` on PowerPC and ARM64). Big-endian PowerPC renders pixel for pixel as
x86.
Polygons with the moire flag (Virtua Fighter's shadows) are drawn as a
checkerboard, every other pixel, as in current MAME; a 720p capture of a
real Virtua Fighter board shows the same pattern.
Compared with captures of real boards: Virtua Fighter (the stages up to
Akira's: backgrounds, floors, colours, lighting, camera, shadows), Virtua
Racing (Bay Bridge: start grid, bridge, HUD), Star Wars Arcade (attract:
Star Destroyer, cockpit battle, hangar), Wing War and NetMerc.
## Open questions
- Virtua Fighter: the eight stages up to Akira's were compared with a
capture of a real board (backgrounds, floors, colours, camera); Dural's
stage and the bonus stage were not.
- After loading a state during play, an analog control (Virtua Racing's
accelerator) starts again from its rest position: the core's input
system does not save it. Digital inputs and attract mode go on as in a
straight run.
+9
View File
@@ -8,6 +8,15 @@
***************************************************************************/
/* no fused multiply-add: the TGP rounds every product and sum, and x86 (the
reference) does not fuse them either. GCC and clang fuse a*b+c by default
on CPUs that have the instruction (PowerPC, ARM64) */
#if defined(__clang__)
#pragma STDC FP_CONTRACT OFF
#elif defined(__GNUC__)
#pragma GCC optimize("fp-contract=off")
#endif
#include <math.h>
#include <stdio.h>
#include <string.h>
+9 -1
View File
@@ -6345,6 +6345,15 @@ Other Sun games
/* Sega Model 1 Hardware */
DRIVER( vr ) /* (c) 1992 Sega */
DRIVER( vformula ) /* (c) 1993 Sega */
DRIVER( vf ) /* (c) 1993 Sega */
DRIVER( swa ) /* (c) 1994 Sega */
DRIVER( swaj )
DRIVER( wingwar ) /* (c) 1994 Sega */
DRIVER( wingwaru )
DRIVER( wingwarj )
DRIVER( wingwar360 )
DRIVER( netmerc ) /* (c) 1993 Sega */
DRIVER( vf2 ) /* (c) 1994 Sega, Model 2A */
DRIVER( vf2b )
DRIVER( vf2a )
@@ -6429,7 +6438,6 @@ Other Sun games
DRIVER( overrevba )
DRIVER( pltkids ) /* (c) 1998 Psikyo, Model 2B */
DRIVER( pltkidsa ) /* (c) 1998 Psikyo, Model 2A */
DRIVER( vf ) /* (c) 1993 Sega */
/* Oriental Soft */
DRIVER( 1945kiii ) /* (c) 2000 Oriental */
+1358 -407
View File
File diff suppressed because it is too large. Load diff
+56
View File
@@ -0,0 +1,56 @@
/***************************************************************************
Sega Model 1
***************************************************************************/
#ifndef MODEL1_H
#define MODEL1_H
#include "cpu/mb86233/mb86233.h"
/* machine: TGP */
#define MODEL1_TGP_HLE 0
#define MODEL1_TGP_LLE 1
#define MODEL1_TGP_ORACLE 2
#define MODEL1_TGP_TABLE_VR 0 /* 315-5573: Virtua Racing, Virtua Formula */
#define MODEL1_TGP_TABLE_SWA 1 /* 315-5711: Star Wars Arcade, Wing War, NetMerc */
#define MODEL1_TGP_TABLE_VF 2 /* 315-5724: Virtua Fighter */
extern int model1_tgp_mode;
extern int model1_tgp_table;
extern int model1_tgp_cpu;
extern struct mb86233_config model1_tgp_config;
void model1_tgp_init( void );
void model1_tgp_reset( void );
void model1_tgp_oracle_frame( void );
void model1_tgp_oracle_report( void );
READ16_HANDLER( model1_tgp_copro_adr_r );
WRITE16_HANDLER( model1_tgp_copro_adr_w );
READ16_HANDLER( model1_tgp_copro_ram_r );
WRITE16_HANDLER( model1_tgp_copro_ram_w );
READ16_HANDLER( model1_tgp_copro_r );
WRITE16_HANDLER( model1_tgp_copro_w );
READ16_HANDLER( model1_fifoin_status_r );
/* V60 */
void v60_stall( void );
/* video */
extern UINT16 *model1_display_list0, *model1_display_list1;
extern UINT16 *model1_color_xlat;
extern UINT16 *model1_mr, *model1_mr2;
extern int model1_sound_irq;
READ16_HANDLER( model1_listctl_r );
WRITE16_HANDLER( model1_listctl_w );
WRITE16_HANDLER( model1_paletteram_w );
VIDEO_START( model1 );
VIDEO_UPDATE( model1 );
VIDEO_EOF( model1 );
void model1_video_vblank_start( void );
#endif
+2 -1
View File
@@ -11,7 +11,8 @@
#define SEGADSB_Z80 1 /* DSB: Z80 */
#define SEGADSB_68K 2 /* DSB2: 68000 */
void segadsb_init( int type, int cpu ); /* from DRIVER_INIT: MPEG data in REGION_SOUND2 */
extern int segadsb_mpeg_region; /* region of the MPEG data, REGION_SOUND2 by default */
void segadsb_init( int type, int cpu ); /* from DRIVER_INIT */
void segadsb_reset( void );
void segadsb_midi_in( UINT8 data ); /* a byte of the game's sound line */
+3091 -1293
View File
File diff suppressed because it is too large. Load diff
+8 -2
View File
@@ -297,16 +297,22 @@ static void dsb2_timer_cb( int param )
* Init, reset, sound start
*************************************/
int segadsb_mpeg_region = REGION_SOUND2;
void segadsb_init( int type, int cpu )
{
dsb_type = type;
dsb_cpu = cpu;
dsb_stream = -1;
if( type == SEGADSB_NONE )
{
segadsb_mpeg_region = REGION_SOUND2;
return;
}
dsb_mpeg_rom = memory_region( REGION_SOUND2 );
dsb_mpeg_len = memory_region_length( REGION_SOUND2 );
dsb_mpeg_rom = memory_region( segadsb_mpeg_region );
dsb_mpeg_len = memory_region_length( segadsb_mpeg_region );
segadsb_mpeg_region = REGION_SOUND2; /* back to the default for the next game */
dsb_decoder = auto_malloc( sizeof( struct mpeg_audio ) );
mpeg_audio_init( dsb_decoder, dsb_mpeg_rom, MPEG_AUDIO_L2 );
+1462 -1025
View File
File diff suppressed because it is too large. Load diff