PIF-NUS
The PIF-NUS (or PIF, or PIF(P)-NUS on PAL) manages multiple critical functions of the N64 console. It is a physical microchip found on the console's motherboard, which is based on the Sharp SM5 Microcontroller. It is not clear whether SGI or Nintendo intended this to stand for "Peripheral InterFace" or not. While the naming is unintuitive, the Peripheral (or Parallel) Interface is used to read/write to the game ROM and devices like the 64DD; whereas, the PIF handles the following:
- Console startup and piracy protections
- Stores the first 2 stages of the Initial Program Load (IPL) that is executed by the VR4300 CPU
- Console reset button to avoid corrupting save game data
- Controller and EEPROM read/write via JoyBus protocol
Pinout
Notice This section requires more research. Pin names and descriptions may be inaccurate. |

N64 Function | SM5 Function | Pin | Pin | SM5 Fuction | N64 Function | Direction | ||
---|---|---|---|---|---|---|---|---|
Output | 2MHz Clock (for CIC, EEPROM) | Pin 1 | Pin 28 | VDD | VDD | Power | ||
RC Cold | Pin 2 | Pin 27 | Reset Button | Input | ||||
Output | CIC-NUS DCLK (/Talk) | Pin 3 | Pin 26 | N/C (No Connect) | ||||
RC Rand | Pin 4 | Pin 25 | INT 2 VR4300 CPU | Output | ||||
Bidirectional | CIC-NUS DIO | Pin 5 | Pin 24 | Cartridge/Expansion Joybus | Input | |||
Output | /Cold | Pin 6 | Pin 23 | Cartridge/Expansion Joybus | open-drain output | |||
Output | NMI VR4300 CPU | Pin 7 | Pin 22 | Player 4 Controller | Input | |||
Input | Power Good | Pin 8 | Pin 21 | Player 4 Controller | Output | |||
Input | 16MHz CLK from RCP | Pin 9 | Pin 20 | Player 3 Controller | Input | |||
Input | Test 0 | ?? | Pin 10 | Pin 19 | Player 3 Controller | Output | ||
Input | PChCmd Serial Interface | Pin 11 | Pin 18 | Player 2 Controller | Input | |||
Input | Test 1 | ?? | Pin 12 | Pin 17 | Player 2 Controller | Output | ||
Output | PChRsp Serial Interface | Pin 13 | Pin 16 | Player 1 Controller | Input | |||
Power | GND | GND | Pin 14 | Pin 15 | Player 1 Controller | Output |
Internal ROMs and RAM
Since PIF is based on the Sharp SM5 which is a programmable microcontroller, its logic is executed by a firmware that is burnt into an internal ROM, called PIF-SM5-ROM. This firmware is written for the SM5 4-bit core, and has been dumped via chip decapping. The repository PIF-NUS disassembly contains a commented disassembly and a decompiled C code for it. The jago85/UltraPIF_MCUproject on GitHub is a compatible implementation based on the STM32 architecture that can be inspected for further studying what PIF does in details. Everything described in this page is implemented by the means of this internal firmware.
Moreover, PIF contains a second internal ROM (1984 bytes) and a small RAM (64 bytes). These memories are usually referred to as PIF-ROM and PIF-RAM, but it is important not confuse this PIF-ROM with the previous PIF-SM5-ROM. Both PIF-ROM and PIF-RAM are memory mapped to the VR4300 address space via the SI interface in RCP (so each access actually requires a serial bus transmission and is thus quite slow).
The PIF-ROM contains the first two stages of code for the VR4300 boot process (IPL1 and IPL2) and is only memory mapped to VR4300 during the boot. After the boot process is finished, before jumping into the game code, the PIF locks the PIF-ROM for security reason, so that it cannot be accessed by VR4300 anymore. The PIF-ROM is slightly different between PAL and NTSC console: it actually hardcodes the region and communicate it to VR4300 during the boot via the PIF-RAM.
Dumping the PIF-ROM can be done via software thanks to a loophole: it is in fact possible to boot the console once, setup a hardware breakpoint at BFC0 0000
via the MIPS COP0 Watch register, and then soft-reset the console; as soon as the boot resumes, the interrupt will trigger; a registered handler for that interrupt would then be able to read the contents of PIF-ROM that are now unlocked, and dump them somewhere (eg: into SRAM). Check the hcs64/pif_rom_dumper project on GitHub for an example of implementing this technique.
The PIF-RAM is always available to be accessed by VR4300 and is used to perform communication with the PIF. Normally, it is used as part of the Joybus protocol to communicate with controllers and EEPROMs.
RAM-based communication protocol
Communication between VR4300 and PIF happens using the 64-byte PIF-RAM. Normally (after boot), the VR4300 writes to it using the SI DMA, which is the DMA In charge of driving the serial line between the RCP and the PIF, hence allowing to transfer data from/to the PIF. The SI DMA allows the CPU to efficiently read and write the contents of PIF-RAM asynchronously and efficiently.
The logic in response to VR4300 writes is executed by PIF-NUS firmware (in the PIF-SM5-ROM). To further investigate its inner workings, see PIF-NUS disassembly for more details.
The last byte of PIF-RAM (offset 0x3F) is called the "command byte" and is interpreted as a bit mask: each bit corresponds to a different command that VR4300 asks the PIF to perform. While PIF is running, it is constantly monitor PIF-RAM and soon as it sees a bit going to 1 in the command byte, it performs the requested function and then turns off the bit. It is possible for the VR4300 to set more than one bit at the same time, but in general they are not fully orthogonal with each other. The rest of the PIF-RAM is used to provide the arguments for the requested command.
Bit | Command | Description | Arguments | Results |
---|---|---|---|---|
0x01 | Configure joybus frame | This is the most used command during normal game run. It is used to configure the PIF in preparation for reading the controllers or otherwise communicating with peripherals connected to the 4 front ports.. The PIF-RAM must be prepared with a joybus frame containing commands. Then, any time a 64-byte DMA read is run, the PIF will do the requested commands and writes the results to PIF-RAM. | A joybus frame must be provided in PIF-RAM starting at 0 (see below). | None |
0x02 | Challenge / response for protection (CIC-NUS-6105) | The CIC-NUS-6105 implements a challenge/response security protocol that was used as anti-piracy measure. The VR4300 can execute this protection protocol any time it wants to verify that an authentic CIC-NUS-6105 is present in the cartridge: a random challenge string is provided by VR4300, sent to CIC, and the response is sent back. | 15 challenge bytes at offset 0x30 in PIF-RAM. | 15 response bytes at offset 0x30 in PIF-RAM. |
0x04 | Unused | PIF does not use or look at this bit | ||
0x08 | Terminate boot process | This command must be sent by VR4300 when the boot process is done. PIF expects this command before 5 seconds from boot, otherwise it freezes itself and the whole console.
Notice that no official IPL3 do this, so this must be done by the application itself (eg: libdragon code). Setting this bit enables the use of the reset button. This bit must also be set again after soft resets. |
None | None |
0x10 | ROM lockout | This command asks the PIF to lock the PIF-ROM. It is part of the sequence to terminate the boot. After this command is received, PIF makes sure that the PIF-ROM is not exposed anymore via the serial bus (and thus accessible by VR4300) for security purposes. | None | None |
0x20 | Acquire checksum | This commands tells the PIF that the 6-byte checksum (IPL2 checksum algorithm) has been written by the CPU in PIF-RAM. When PIF sees this command, it reads the checksum and copies to some internal memory, clearing the checksum in PIF-RAM. Then, it sets bit 0x80 in the command byte to notify the CPU that the command has finished. | 6 byte checksum at offset 0x32 in PIF-RAM | Bit 0x80 of command byte is set when the checksum has been read by PIF. |
0x40 | Run checksum | This commands tells PIF to verify whether the provided checksum matches the checksum provided by CIC. This is run in the context of IPL2, and refers to the IPL2 checksum algorithm, which is used to authenticate the contents of IPL3. PIF was provided the expected checksum from CIC at boot, and the CPU-calculated checksum via command 0x20.
If the checksum fails, PIF simply halts the CPU, freezing the console until power off. Otherwise, it continues execution. |
None | Continue the PIF boot, or freeze the CPU |
0x80 | Unused | This bit is used as response bit (ack) by PIF in three situations:
|
None | None |
Joybus frame (controller and EEPROM communication)
As explained above, when the VR4300 writes to PIF-RAM (normally, using SI DMA) a command byte with value 0x1, the PIF firmware is alerted that the PIF-RAM now contains a new Joybus frame to process.
A joybus frame is a description of multiple joybus handshakes to perform with each peripheral on the various ports. The PIF can communicate with up to 5 different joyous channels. Channels 0-3 are mapped to the 4 front ports where controllers are normally attached. Channel 4 instead is tied to the cartridge bus and allows to drive custom serial peripherals present within the cartridge; in the commercial era, it has been used to either access EEPROMs used for save games, or in a single case ("Doubutsu no mori", aka "Animal Forest") to communicate with a RTC chip. The frame has a specific binary format that is decoded by the PIF firmware. This link to the PIF-NUS disassembly shows the decompiled C code that performs the parsing of this frame.
Frame parsing and handshakes
There are two distinct phases in handling each PIF frame:
- Parsing. When the VR4300 writes to PIF-RAM and changes the command byte (last byte) so that the LSB is set to 1 (bitmask 0x01), the PIF firmware proceeds to parse the PIF-RAM contents. It decodes the frame whose format is detailed in this section, and it stores in internal RAM the pointers to the beginning of each channel's handshakes within the PIF-RAM. At this point, no handshake is actually performed with joybus devices. After the parsing is done, bit 0x01 in the command byte is turned off.
- Executing. When the VR4300 requests a read from PIF-RAM using a SI DMA transfer, the PIF firmware proceeds to execute the handshakes, using the pointers stored in the previous step to find the handshakes in PIF-RAM. The transfers are executed in reverse order (starting from channel 4 down to 0). The replies are stored in PIF-RAM within the space reserved in each handshake. After all the handshakes are finished, the actual SI DMA transfer is performed to copy the data to RDRAM.
Some notes related to this:
- It is perfectly valid to write a new frame to PIF-RAM once, and then execute it multiple times, by issuing multiple SI DMA reads. Every time a SI DMA read is performed, new data is potentially returned, as reading actually does trigger execution of the handshakes.
- From the VR4300 point of view, the SI DMA read will take a longer than usual time, as it does need first to wait for the handshakes to be performed. The actual time will thus be the sum of the time it takes to perform the handshakes (which is roughly linear with the number of transmitted and received bytes), plus the time to actually transfer the PIF-RAM contents to RDRAM. These two phases are not visible from VR4300: they will just appear as SI DMA being in progress.
- Handshakes are executed only when a SI DMA read is performed, not when the VR4300 directly read PIF-RAM through its memory mapped address. On the other hand, parsing is performed at any time in which the bitmask 0x1 is found set in the command byte, whether it has been written via SI DMA or direct memory mapped write.
Joybus handhakes
Each frame is composed by a sequence of up to 5 joybus handshakes, intermixed with an unbounded number of escape codes. The PIF firmware will start parsing the first handshake from the first byte of PIF-RAM, and will interpret it as the handshake for the first joybus channel; the next handshake will be the one run on the second channel, and so on until the fifth. After that, the remaining contents of PIF-RAM are ignored.
A joybus handshake is a sequence of bytes in the the following format:
TX RX tt[...] rr[...]
where:
TX
is the number of bytes to transmit to the device (valid range is0x01 - 0x3F
, and the top 2 bits are ignored)RX
is the number of bytes to received from the device (valid range is0x00 - 0x3F
, and the top 2 bits are ignored)tt
is the data to transmit (must be exactlyTX
bytes)rr
is the space where received data will be written (must be exactlyRX
bytes)
For instance:
03 02 AA BB CC 00 00
This handshake will be run by transmitting 3 bytes to the device, and then receiving 2 bytes. Then the actual bytes that will be transmitted are 0xAA 0xBB 0xCC
, and the two bytes received as reply will be written over the two 0x00 0x00
bytes. The contents in PIF-RAM of the bytes in the receive space are ignored and will simply be overwritten.
Notice that PIF is unaware of the actual joybus protocol on the wire; it doesn't know or care what 0xAA 0xBB 0xCC
means for a controller. It just knows that it needs to write 3 bytes on the serial, and then read 2 bytes.
Normally, the first transmitted byte will be the joybus command. The joybus command table lists all known commands for all known joybus peripherals, and for each command lists the number of transmitted and received bytes. For instance, the "Info" command (0x00) is made by transmitting only one byte (the command itself) and receiving three bytes. So the correct joybus handshake to encode in the PIF-RAM will be:
01 03 00 00 00 00
The first byte (0x01
) is the number of bytes to transmit, while the second byte (0x03
) is the number of bytes to receive. The PIF will then transmit just a single byte (the next 0x00
) to the devices, while the reply will be stored in the following three bytes (0x00 0x00 0x00
).
If a handshake does not fully fit in PIF-RAM, parsing is aborted and the last incomplete handshake is ignored.
TX byte: special flags
The top 2 bits of the TX byte are ignored during the parsing phase of PIF-RAM. Instead, those bytes at checked when the handshakes are actually performed, with the following meaning:
Bit | Mask | Description | Notes |
---|---|---|---|
7 | 0x80 | Skip bit | If this bit is found set at execution time, the handshake for this channel is skipped, and no new contents are written in PIF-RAM in the receive space. |
6 | 0x40 | Reset bit | If this bit is found set at execution time, the joybus channel is reset (using the same reset functionality performed by the escape code 0xFD, see below). The exact behavior on the wire is unknown at this point. |
RX byte: special flags
Similarly to the TX byte, the top 2 bits of the RX byte are ignored during the parsing phase. Instead, they are reset by the PIF firmware at the beginning of the execution phase, and then later set to provide handshake error flags:
Bit | Mask | Description | Notes |
---|---|---|---|
7 | 0x80 | No device | This bit is set if the handshake failed because no device appears to be connected to the joybus channel. |
6 | 0x40 | Timeout | This bit is set if the handshake failed because of a timeout while trying to receive bytes. A common case is when the handshake instructed the PIF to receive more bytes than those actually sent back by the device. |
Escape codes
In addition to handshakes, PIF-RAM can contain 1-byte "escape codes", that are stored in place of the TX
byte. This is a list of all codes recognized by the PIF firmware:
Escape code | Description | Notes |
---|---|---|
0x00 | Skip channel | This byte signals that no handshake must be performed on the current channel. When the PIF firmware finds it, it skips it, and then start parsing next byte as handshake for the following channel. |
0xFD | Reset transmission | This byte is used as a PIF-side reset of joybus communication on the channel. The exact behavior on the wire is unknown at this point. |
0xFE | End of frame | This byte signals the PIF firmware that the joybus frame is finished, even before the fifth channel's handshake is parsed. When the PIF firmware finds this code, it stops processing the frame in PIF-RAM. |
0xFF | Nop | This byte is treated as a nop and is simply skipped. |
Notice that escape codes are checked as first thing by the firmware; so if the current byte in PIF-RAM is exactly one of the above values, it is treated as an escape code, otherwise it is treated as a TX byte and thus the beginning of a handshake.
Both "skip" and "reset" can then be performed in two different ways, though with identical results: either as single-byte escape codes, or as handshakes where the TX byte uses the special 2 MSBs.
Console startup
- PIF and VR4300 boot at power on.
- VR4300 starts running code from address
0xBFC0 0000
which is mapped to PIF ROM via SI interface. - PIF starts communicating with the CIC inside the cartridge
- CIC sends 1 nibble (4-bits): region identifier (0x1 = NTSC, 0x5 = PAL)
- CIC sends two 1-byte "seeds" that will be used to compute checksums. We call them IPL2 seed and IPL3 seed. These seeds are sent with some scrambling on the wire, possibly as obfuscation
- CIC sends a 6 byte checksum (again, slightly obfuscated). This is the expected result for the IPL2 checksum algorithm (see below).
- PIF checks that the region identifier matches the region of the console (which is hardcoded within the PIF SM5 ROM itself). This is the actual region check, preventing cartridges of different regions from working on the console.
- If the values don't match, the PIF stops the boot by freezing the CPU (halting it via the NMI line)
- PIF writes several booting information (including the two seeds) to the PIF-RAM word at offset
0x24-0x27
(mapped at0xBFC0 07E4
), so that the CPU can later access them. - PIF writes bit 0x80 in the command byte to signal VR4300 that the data is now available in PIF-RAM.
- Meanwhile, the VR3000 is executing the IPL1 code directly fetching opcodes from PIF-ROM.
- These instructions are executed in this very slow manner. Thankfully IPL1 is only 52 instructions + some looping.
- It performs some really basic hardware initialization.
- It then copy the rest of the PIF-ROM (IPL2) to the RSP IMEM. Notice that at this point RDRAM is not initialized yet, so it cannot be used. RSP IMEM is instead available without any initialization and is much faster than PIF ROM thanks to the parallel bus.
- Jump to RSP IMEM to execute IPL2
- IPL2 is executed by the VR4300 reading the instructions from RSP IMEM
- More general hardware initialization
- The CPU reads the booting information (region and CIC seeds) from PIF-RAM at
0xBFC0 07E4
. NOTE: there seems to be no sync here, the code just assumes that the PIF has won the race and the information is already available when the CPU looks for it. - If the booting information says that it is a 64DD disk, it will jump to
0xA600 0000
- Send command 0x10 to PIF, and PIF disables access to PIF-ROM (IPL1).
- Load IPL3 from the cartridge ROM (offset 0x40-0x1000) into the RSP DMEM
- Run the IPL2 checksum algorithm over the contents of IPL3. This is done using the IPL2 seed provided by CIC at the beginning, and read from PIF-RAM. The output is a 6-byte checksum.
- VR4300 asks PIF to verify whether the calculated 6-byte checksum is correct (see PIF command 0x20 and 0x40). PIF compares it with the checksum it received from CIC at boot, and if it's different, it halts the VR4300 via the NMI line.
- Jump to RSP DMEM to execute IPL3.
- IPL3 is executed by the VR4300 reading the instructions from the RSP DMEM.
- Initialize RDRAM
- Depending on reset type
- Power On: Invalidate VR4300 ICache & DCache
- Reset : Writeback VR4300 ICache & DCache
- Now that RDRAM is available, IPL3 copies the second half of itself from DMEM to RDRAM (at address 0x8000'0040) and jumps there. This makes execution even faster, as running from RDRAM also allows instruction cache to be used.
- The code DMAs the first MB of cartridge ROM (after the IPL3 itself, starting from offset 0x1000) to RDRAM at the address specified at offset 0x08 in the ROM header (called "initial PC"). The fixed size of 1 MiB that cannot be changed and was deemed a good default.
- Run the IPL3 checksum algorithm over the first MB of ROM. This is done using the IPL3 seed provided by CIC at the beginning. The output is a 8-byte checksum.
- The 8-byte checksum is compared against the checksum stored at offset 0x10 in the ROM (part of the ROM Header). If it doesn't match, VR4300 halts itself.
- Reset RSP
- Clear Interrupts
- Clear IPL3 from DMEM
- Clear IPL2 from IMEM
- Jump to Game code in RDRAM. The initial PC is stored at offset 0x08 in the ROM (part of the ROM Header), though in some IPL3 variants it is slightly descrambled first.
- The game code is expected to quickly send command 0x08 to PIF. If it doesn't within about 5 seconds from boot, the PIF halts the VR4300 via the NMI line.
- The PIF (on console) and CIC (on cartridge) begins doing a communication protocol which follows the challenge/response authentication pattern.
- The protocol continues to run as long as the console is powered on. If there is ever a failure in the data exchange or there is no answer (eg: the cartridge is removed), PIF halts the CPU via the NMI line.
IPL2 checksum algorithm
This is the algorithm performed by IPL2. It uses a 8-bit seed (provided by CIC, which changes across CIC variants) and produces a 6-byte checksum value. It is run over the contents of IPL3 as found in the game ROM to authenticate it. The calculated checksum is then compared against the correct checksum (provided by CIC). This allows to only run the IPL3 variant expected by the CIC.
Notice that the correct checksum value is never transmitted to VR4300: CIC sends it to PIF at boot, and PIF keeps it. Then IPL2 (run by VR4300) computes the checksum value, and sends it to PIF via command 0x20 (see above). This command asks PIF to compare the checksum calculated by VR4300 to that provided by CIC and provides a boolean answer to VR4300.
A reverse-engineered implementation of the checksum can be found on the jago85/PifChecksum repository on Github.
IPL3 checksum algorithm
This is the algorithm performed by IPL3. It is run over the contents of the first megabyte of cartridge ROM to authenticate it. It uses a 8-bit seed (provided by CIC, which changes across CIC variants) plus a 32-bit magic number (hardcoded in the IPL3 itself, which again changes across CIC variants) and produces a 8-byte checksum value. The calculated checksum is then compared against the correct checksum, which is written in the ROM header at offset 0x10.
Notice that the checksum algorithm is slightly tweaked across the different IPL3/CIC variant, even though the core of it is mostly the same.
When building a homebrew ROM using an official IPL3 variant, it is necessary to compute this checksum (using the correct seed, depending on the IPL3 variant) and store the result in the header, otherwise the ROM will not boot on a real console.
A reverse-engineered implementation of the checksum can be found in the n64crc tool. This tool implements all the different variations of the algorithm, depending on the exact IPL3/CIC pair.
CIC Chip | 8-bit IPL2 Seed* | 6-byte IPL2 checksum | 8-bit IPL3 Seed | IPL3 32-bit Magic | IPL3 Initial Checksum* |
---|---|---|---|---|---|
6101 | 0x3F
|
0x45CC73EE317A
|
0x3F
|
0x5D588B65
|
0xF8CA4DDC
|
6102, 7101 | 0x3F
|
0xA536C0F1D859
|
0x3F
|
0x5D588B65
|
0xF8CA4DDC
|
7102 | 0x3F
|
0x44160EC5D9AF
|
0x3F
|
0x5D588B65
|
0xF8CA4DDC
|
6103, 7103 | 0x78
|
0x586FD4709867
|
0x78
|
0x6C078965
|
0xA3886759
|
6105, 7105 | 0x91
|
0x8618A45BC2D3
|
0x91
|
0x5D588B65
|
0xDF26F436
|
6106, 7106 | 0x85
|
0x2BBAD4E6EB74
|
0x85
|
0x6C078965
|
0x1FEA617A
|
*IPL2 Seed: notice that, even though the 8-bit seed for IPL2 and IPL3 could in theory be different, they are the same in all known CIC variants. Most emulators do get this wrong because they do not emulate the full PIF checksum verification, so they have no way of knowing the actual seed, and wrong numbers got carried over through copy and paste.
*Initial Checksum: computed at the beginning of the checksum algorithm with: (CIC 8-bit Seed) * (IPL3 32-bit Magic) + 1
and truncating the result to 32-bits. This value is noted here because many tools (like n64crc) hardcode this value rather than the IPL3 seed.
Console Reset
The reset process is driven by the PIF, which is connected to the physical reset button. The actual reset is done via a NMI to VR4300 which resets it by starting again the full boot process, but it is important to notice that RCP is not reset in any way. The boot code expects the RCP to be idle when the boot is initiated and is not guaranteed to work if the RCP is active in any way (DMAs in progress, RDP drawing triangles, RSP executing code, etc.), which means that it is up to the VR300 to stop issuing commands to the RCP and putting it in idle state before the reset is executed. To do so, VR4300 is given a forewarn that a reset is incoming via an interrupt (aptly called "pre-NMI") and is given grace time of 500ms before the actual NMI arrives.
This is the full sequence:
- User presses Console Reset button
- PIF receives an interrupt signaling that the button was pressed
- PIF toggles VR4300 Interrupt 2 (INT2) also known as "pre-NMI".
- This is the time and opportunity for the game to finish saving game data and stop issuing commands to RCP to avoid graphics/audio corruption and/or a hard freeze.
- PIF sends the RESET command to CIC (command
0b11
) - CIC waits for 500ms (grace time)
- CIC acknowledges the RESET command to PIF by writing a 0 bit.
- PIF waits (indefinitely) until the reset button is released.
- PIF toggles VR4300 Non-Maskable Interrupt (NMI) which resets it.
- PIF also unlocks the internal PIF ROM so that the boot process can start executing IPL1.