Vs. DualSystem

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

User avatar
zeroone
Posts: 939
Joined: Mon Dec 29, 2014 1:46 pm
Location: New York, NY
Contact:

Vs. DualSystem

Post by zeroone »

I'm trying to determine the plausibility of integrating Vs. DualSystem support with Netplay in my emulator. But I can't seem to get any of the DualSystem ROMs to function. From the wiki and other sources, I've collected the following information:

The DualSystem uses 2 CPUs, one configured as Master and the other Slave. That status can be determined by reading 4016.7 (0 = Slave, 1 = Master).

The CPUs share a common memory region (6000--67FF), though it can only be accessed by 1 CPU at a time. Access is coordinated via 4016.1 on both CPUs. Specifically, when Master 4016.1 drops from 1 to 0, that triggers an interrupt in Slave. Slave acknowledges that the interrupt handler completed by dropping its 4016.1 from 1 to 0. I think the cycle works something like:

1. Master and Slave both start out with 4016.1 set to 1.
2. Master reads and writes from shared memory.
3. Master 4016.1 drops from 1 to 0 indicating that it's done with shared memory.
4. That drop triggers an interrupt on Slave.
5. Slave's interrupt handler reads and writes from shared memory.
6. Slave 4016.1 drops from 1 to 0 indicating that it's done with shared memory.
7. Master sets 4016.1 to 1.
8. Slave responds to rise in Master's 4016.1 by setting its 4016.1 to 1.
9. Goto 1

I suspect, but I can't find documentation to back this up, that there is some external clock that causes interrupts in Master that drives that cycle. If that exists, can someone point me to some info on it.

Regarding the Vs. DualSystem ROMs. I'm looking at Balloon Fight (VS) [!].nes. PRG ROM and CHR ROM are twice the size as their counterparts in Balloon Fight (U) [!].nes. I didn't compare the bytes, but I assume the CHR ROMs are probably the same, but the code in PRG ROMs might be different, enabling 1 CPU to act as Master and the other Slave. But that could also be achieved through configuration; so, it might be exactly the same ROMs repeated twice in the file.

Anyway, I'd like to get VS Balloon Fight running in a single CPU (with some hacking of my emulator) if that's possible. Specifically, can I fool the Master into thinking that a Slave exists, when there is no actual Slave connected? In other words, what is the minimum things that I need to do to simulate a fake Slave?

Regarding Netplay, I need to know how much data is transferred in shared memory region and how often that region is accessed. E.g. can I transfer that data once-per-frame and keep the CPUs working? If I can simulate a fake Slave, then I can analyze how often shared memory is accessed and determine if Netplay is possible.

Any input is much appreciated. Thx.
lidnariq
Posts: 11429
Joined: Sun Apr 13, 2008 11:12 am

Re: Vs. DualSystem

Post by lidnariq »

zeroone wrote:That status can be determined by reading 4016.7 (0 = Slave, 1 = Master).
Backwards... I know I helped Sour implement Vs. System recently, and he was having seriously problems with it backwards. The current text on the wiki was his suggested wording ... are you using an older snapshot of the wiki?

Nocash's EveryNES has it backwards.
The CPUs share a common memory region (6000--67FF)
2 KiB of RAM, but mirrored over the entire range from $6000-$7FFF.
4016.1 drops from 1 to 0, that triggers an interrupt in Slave. Slave acknowledges that the interrupt handler completed by dropping its 4016.1 from 1 to 0.
/IRQ is level triggered, not edge triggered. IRQ will re-start until the other CPU releases it.
Anyway, I'd like to get VS Balloon Fight running in a single CPU (with some hacking of my emulator) if that's possible. Specifically, can I fool the Master into thinking that a Slave exists, when there is no actual Slave connected? In other words, what is the minimum things that I need to do to simulate a fake Slave?
The specifics of the protocol is game-specific, so unfortunately you're not going to get away with faking something. You simply must emulate the second CPU. You probably don't have to emulate the second PPU.
User avatar
zeroone
Posts: 939
Joined: Mon Dec 29, 2014 1:46 pm
Location: New York, NY
Contact:

Re: Vs. DualSystem

Post by zeroone »

Can we gather all the DualSystem information onto a dedicated Wiki page or a separate block within the existing VS page? It seems scattered. And I did use NoCash's info to fill in gaps, which apparently mixed me up.
lidnariq wrote:/IRQ is level triggered, not edge triggered. IRQ will re-start until the other CPU releases it.
Can you spell that out for me in terms of 4016.1 reads and writes? The wiki says that Master sets 4016.1 to 1 to gain shared memory access and when Master sets 4016.1 to 0, Slave gains shared memory access. When Master writes 0 to 4016.1, I assume that asserts the interrupt on Slave. And Slave writes 0 to 4016.1 to acknowledge that it's done with shared memory?

If I knew a little more about what data is passed over shared memory, it sounds like I could fake the Slave. Of course, that's game specific.

@Sour please give me some hints!
User avatar
Memblers
Site Admin
Posts: 4044
Joined: Mon Sep 20, 2004 6:04 am
Location: Indianapolis
Contact:

Re: Vs. DualSystem

Post by Memblers »

The VS schematic is available, if that helps. The shared RAM is in the upper-left corner.
https://archive.org/details/vsschematicmds-02-cpu
Sour
Posts: 890
Joined: Sun Feb 07, 2016 6:16 pm

Re: Vs. DualSystem

Post by Sour »

It's not exactly fresh in my mind, but Mesen actually runs both CPUs (and both PPUs) at once (running 1 full CPU instruction on one before switching to the other, which isn't perfectly accurate, but close enough).

Logic used on $4016 writes (pseudocode, see VsControlManager::UpdateSlaveMasterBit in Mesen if you want to see the actual code):

Code: Select all

void UpdateSlaveMasterBit(uint8_t slaveMasterBit)
{
	//slaveMasterBit is the value of bit 1 ($02) that was just written to $4016
	CPU* otherCpu= _currentCpu->GetOtherCpu();
	if(otherCpu != nullptr) {
		if(_currentCpu->IsMasterCpu()) {
			//Update memory access - this is only done when the master CPU writes to $4016
			if(slaveMasterBit) {
				//Set memory at $6000-$7FFF as readable/writable for master CPU
				//Set memory at $6000-$7FFF as open bus for slave CPU
			} else {
				//Set memory at $6000-$7FFF as open bus for master CPU
				//Set memory at $6000-$7FFF as readable/writable for slave CPU
			}
		}

		if(slaveMasterBit) {
			otherCpu->ClearIrqSource(IRQSource::External);
		} else {
			//When low, asserts /IRQ on the other CPU
			otherCpu->SetIrqSource(IRQSource::External);
		}
	}
}
For netplay/savestates/movies, the state of the master & slave consoles are both saved together in the same data stream. For the "P1" and "P2" inputs of the slave console, I cheated and internally mapped them to the standard P3 and P4 logic, which allows netplay and movies to work without any Dualsystem-specific changes.

Hopefully this helps a bit? I can't really offer any advice on how to fake the slave CPU, though, since I just emulated everything and display both screens in the emulator.

Edit: RE: Netplay, Mesen emulates both CPUs on both machines when playing via netplay. It doesn't try to synchronize the shared RAM between both PCs - that is most likely not viable since the RAM is probably read/written to every frame (e.g to synchronize the position of players/monsters in Balloon fight, for example)
Last edited by Sour on Mon Dec 31, 2018 4:40 pm, edited 1 time in total.
tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Vs. DualSystem

Post by tepples »

Does the Dualsystem PCB actually say "master" or "slave"?
NewRisingSun
Posts: 1510
Joined: Thu May 19, 2005 11:30 am

Re: Vs. DualSystem

Post by NewRisingSun »

zeroone wrote:Regarding the Vs. DualSystem ROMs. I'm looking at Balloon Fight (VS) [!].nes
There is also my conversion utility that will create canonical NES ROM files from MAME split ROMs.
zeroone wrote:Anyway, I'd like to get VS Balloon Fight running in a single CPU (with some hacking of my emulator) if that's possible. Specifically, can I fool the Master into thinking that a Slave exists, when there is no actual Slave connected? In other words, what is the minimum things that I need to do to simulate a fake Slave?
Fake certain responses in the shared ram at $6000. Doing so can only be done on a game-specific basis. Here is what I did before I added actual emulation of a second CPU to NintendulatorNRS:

Code: Select all

void	MAPINT	Write4 (int Bank, int Addr, int Val) {
	if (Addr ==0x16) {
		EMU->SetIRQ((Val &2)? 1: 0);
		// Fake Vs. Dual System
		if (~Val &2) switch (ROM->PRGROMCRC32) {
			// Vs. Balloon fight
			case 0x9213A19E: case 0xAD407F52: case 0x6AD67502:
				switch((EMU->GetCPUReadHandler(6))(6, 0x000)) {
					case 0x00: (EMU->GetCPUWriteHandler(6))(6, 0x000, 0x00); break;
					case 0x55: (EMU->GetCPUWriteHandler(6))(6, 0x000, 0xAA); break;
					case 0xAA: (EMU->GetCPUWriteHandler(6))(6, 0x000, 0x55); break;
				}
				switch((EMU->GetCPUReadHandler(6))(6, 0x220)) {
					case 0x00: (EMU->GetCPUWriteHandler(6))(6, 0x220, 0x55); break;
					case 0x55: (EMU->GetCPUWriteHandler(6))(6, 0x220, 0xAA); break;
				}
				switch((EMU->GetCPUReadHandler(6))(6, 0x0FF)) {
					case 0x00: (EMU->GetCPUWriteHandler(6))(6, 0x0FF, 0x11); break;
					case 0x11: (EMU->GetCPUWriteHandler(6))(6, 0x0FF, 0x88); break;
				}
				break;
			// Vs. Wrecking Crew
			case 0x008A9C16: case 0x30C42B1E: case 0x12B36F73:
				switch((EMU->GetCPUReadHandler(6))(6, 0x051)) {
					case 0x00: (EMU->GetCPUWriteHandler(6))(6, 0x051, 0x01); break;
				}
				switch((EMU->GetCPUReadHandler(6))(6, 0x053)) {
					case 0x00: (EMU->GetCPUWriteHandler(6))(6, 0x053, 0x01); break;
				}
				break;
			// Vs. Baseball
			case 0xF5DEBF88: case 0xB5853830: case 0xC4DD2523: case 0x13A91937: case 0xF64D7252: case 0x968A6E9D: 
			case 0x44691677: case 0x327BD71D: case 0x038E1E1B: case 0x60C90D8A:
				switch((EMU->GetCPUReadHandler(6))(6, 0x002)) {
					case 0x63: (EMU->GetCPUWriteHandler(6))(6, 0x002, 0x64); break;
				}
				switch((EMU->GetCPUReadHandler(6))(6, 0x00A)) {
					case 0x01: (EMU->GetCPUWriteHandler(6))(6, 0x00A, 0x00); break;
				}
				break;
			// Vs. Tennis
			case 0xB90497AA: case 0xBC202DB6: case 0x2AF7E14E: case 0x777EE984: case 0xD46B8C5F:
				switch((EMU->GetCPUReadHandler(6))(6, 0x002)) {
					case 0x00: (EMU->GetCPUWriteHandler(6))(6, 0x002, 0x80); break;
					case 0x40: (EMU->GetCPUWriteHandler(6))(6, 0x002, 0xC0); break;
				}
				switch((EMU->GetCPUReadHandler(6))(6, 0x113)) {
					case 0x00: (EMU->GetCPUWriteHandler(6))(6, 0x113, 0x01); break;
				}
				break;
			// Vs. Mahjong
			case 0x381E5E08:
				switch((EMU->GetCPUReadHandler(6))(6, 0x002)) {
					case 0x3C: (EMU->GetCPUWriteHandler(6))(6, 0x002, 0xA5); break;
				}
				switch((EMU->GetCPUReadHandler(6))(6, 0x011)) {
					case 0x00: (EMU->GetCPUWriteHandler(6))(6, 0x011, 0x88); break;
				}
				break;
		}
	}
	_Write4(Bank, Addr, Val);
}
lidnariq
Posts: 11429
Joined: Sun Apr 13, 2008 11:12 am

Re: Vs. DualSystem

Post by lidnariq »

zeroone wrote:Can we gather all the DualSystem information onto a dedicated Wiki page or a separate block within the existing VS page? It seems scattered. And I did use NoCash's info to fill in gaps, which apparently mixed me up.
I've added a bit of a summary about how the Vs. System is like and how it's dislike a plain NES at the top of the page. I hope it clears up any confusion....

The only thing not mentioned on the Vs. System page is the specifics of mapper 99. I'm not clear what I could possibly say to make it clearer.

There's no other information on the wiki about this. What would we add?

zeroone wrote:Can you spell that out for me in terms of 4016.1 reads and writes?
It means that as long as the last value CPU "A" wrote to $4016 had the 2s bit clear (".1"), and CPU "B" has not disabled interrupts ("sei" or due to being in its NMI or IRQ handler), CPU "B" will always enter its IRQ handler.

This is just like any other IRQ in the NES, it's just that it's coming from a CPU instead of a timer.
And Slave writes 0 to 4016.1 to acknowledge that it's done with shared memory?
Maybe! Maybe not! The hardware only imposes these exact behaviors:
* The primary CPU controls which CPU has access to RAM
* When the primary CPU asserts /IRQ on the secondary CPU, this also gives the secondary CPU access to RAM.
* The secondary CPU can assert /IRQ on the primary CPU.

Any higher-level interpretation will be fragile and game-specific.



Tepples: All the OEM documentation uses "M" and "S" ubiquitously.
Last edited by lidnariq on Mon Dec 31, 2018 7:23 pm, edited 1 time in total.
tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Vs. DualSystem

Post by tepples »

lidnariq wrote:The hardware only imposes these exact behaviors:
* The primary CPU controls which CPU has access to RAM
* When the primary CPU asserts /IRQ on the secondary CPU, this also gives the secondary CPU access to RAM.
* The secondary CPU can assert /IRQ on the primary CPU.
How can each CPU determine whether /IRQ was asserted because of APU DMC or because of a request from the other CPU? Is looking for a signature in shared RAM the primary way?

How are vertical blanks aligned between the two PPUs? Synchronized? Constant offset? Offset that drifts over the course of a game?
lidnariq wrote:All the OEM documentation uses "M" and "S" ubiquitously.
I imagine it'd be more polite nowadays, in the era of Python issue 34605, to expand these as "main" and "secondary". Or am I missing something? EDIT: Withdrawn, as consensus has not yet formed
NewRisingSun
Posts: 1510
Joined: Thu May 19, 2005 11:30 am

Re: Vs. DualSystem

Post by NewRisingSun »

tepples wrote:How can each CPU determine whether /IRQ was asserted because of APU DMC or because of a request from the other CPU? Is looking for a signature in shared RAM the primary way?
The IRQ handler usually checks $4016 D7.
lidnariq
Posts: 11429
Joined: Sun Apr 13, 2008 11:12 am

Re: Vs. DualSystem

Post by lidnariq »

tepples wrote:How can each CPU determine whether /IRQ was asserted because of APU DMC or because of a request from the other CPU?
The pithy answer is "they don't use DMC or frame interrupts".
NewRisingSun wrote:The IRQ handler usually checks $4016 D7.
That's to handle only having one set of ROMs that can run on both sides, not an indication that the IRQ is caused by the other CPU.
How are vertical blanks aligned between the two PPUs? Synchronized? Constant offset? Offset that drifts over the course of a game?
Ok, these are things that should be on the page.

To answer:

PPUs are perfectly synchronized, but entirely by accident. All CPUs and PPUs are fed by the same 21.477MHz crystal, and all CPUs and PPUs are released from reset at the same time. The RGB PPUs never have missing dots under any conditions, so cannot get out of sync. Since they are fed by the same clock, they cannot drift.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: Vs. DualSystem

Post by koitsu »

tepples wrote:Does the Dualsystem PCB actually say "master" or "slave"?
I see what you're doing. Stop now.
User avatar
zeroone
Posts: 939
Joined: Mon Dec 29, 2014 1:46 pm
Location: New York, NY
Contact:

Re: Vs. DualSystem

Post by zeroone »

Thanks everyone for the great responses. It's going to take me a bit to fully digest all this.

There are only 5 VS. DualSystem ROMs that I am aware of: Balloon Fight, Baseball, Mahjong, Tennis and Wrecking Crew. I have all of them as listed as mapper 0, except for Baseball, which is mapper 99. But I could not confirm those mapper numbers. Does anyone have a table of them?
lidnariq
Posts: 11429
Joined: Sun Apr 13, 2008 11:12 am

Re: Vs. DualSystem

Post by lidnariq »

Nocash additionally lists a version of Ice Climber.

No games for the Vs. System are really ever mapper 0; they're all actually mapper 99. If only 8 KiB of CHR is present—which I think specifically means only Vs. Mahjong—when $4016.2 is set there will be garbage graphics—it'll fetch open bus. All other Vs. DualSystem games seem to have 16 KiB CHR.

MAME enumerates 6 Vs. DualSystem games, plus five more variants (earlier versions or bad dumps):
"vstennis", 32 KiB PRG and 16 KiB CHR per side, different PRG and CHR for both sides
("vstennisa" and "vstennisb", same, with different PRG from above but identical CHR)
"wrecking" [crew], 32 KiB PRG and 16 KiB CHR per side, the same CHR on both sides
"balonfgt" [balloon fight], 32 and 16, same CHR on both sides
"vsmahjng" [mahjong], 24 and 8, same CHR on both sides
"vsbball" [baseball], 32 and 16, same CHR on both sides
("vsbballj", "vsbballja", and "vsbballjb", 32 and 16, all with the same CHR that differs from above)
"iceclmrd", 32 and 16, same CHR on both sides and almost-identical PRG.
User avatar
zeroone
Posts: 939
Joined: Mon Dec 29, 2014 1:46 pm
Location: New York, NY
Contact:

Re: Vs. DualSystem

Post by zeroone »

I am looking at the Dipswitch definitions file in Mesen. The number of switches and options suggests that the VS. DualSystem games have 16 DIPs instead of 8 DIPs. Perhaps this is possible since there are 4 controller ports instead of 2?

Is there a table in MAME or Nintendulator-NRS by any chance? I can't seem to locate it is in the sources.
lidnariq wrote:No games for the Vs. System are really ever mapper 0; they're all actually mapper 99. If only 8 KiB of CHR is present—which I think specifically means only Vs. Mahjong—when $4016.2 is set there will be garbage graphics—it'll fetch open bus. All other Vs. DualSystem games seem to have 16 KiB CHR.


I've used other sources for reference, but I have VS Mighty Bomb Jack and VS Slalom as mapper 0. In addition, many other VS games are not mapper 99. They appear to work :)
Post Reply