Peripheral Interface

The Peripheral Interface (or PI, or Parallel Interface) is one of multiple I/O interfaces in the RCP, which is used to communicate with game cartridges or other devices connected to either the cartridge port or expansion port on the bottom of the console. (e.g. 64DD)

Memory mapped registers are used to configure the Peripheral Interface and initiate DMA reads and writes. The base address for these registers is, also known as PI_BASE. However, because all memory accesses in the CPU are made using virtual addresses, the following addresses must be offset appropriately. For non-cached reads/writes, add  to the address. As an example, to directly write to the PI_DRAM_ADDR register, use address.

The PI Bus
The PI bus is the bus where external devices can be connected, via either the cartridge port on the top of the console, or the expansion port at the bottom of the console. Notice both ports are electrically connected to the same bus, even if the connector is different.

The bus address is 32-bit and the values being transferred are 16-bits. So each access (read or write) is made to a 32-bit address with a 16-bit data. The PI (as master device) issues reads and writes to the bus with a wire protocol detailed below. Each device is expected to use an address range (a subset of the whole 32-bit address space); the device will receive all reads and writes requests from PI, and is expected to reply / execute those falling within the address range of interest. The PI has no way of knowing if one or more devices are attached to the bus, it does not know which address ranges are used by what device (there is no "address registration / reservation system"), and there is no handling of conflicts.

The PI will issue reads or writes as drive by the CPU via two different systems:


 * DMA: this allows to transfer multiple words. In general, the PI bus protocol allows the PI to write the address once, and then either reads or writes multiple consecutive words, and the DMA will use this mechanism to do quicker transfers. In fact, addresses in the PI bus are virtually split in "pages" of configurable size. The PI is allowed to read/write multiple words within the same page, so during the DMA will issue the address only once for page, and then read/write multiple words as requested. This is done to speed up transfers (as issuing a new address after every word would waste time).
 * Direct I/O: part of the 32-bit PI address space is memory mapped to the CPU address space. This means that when the CPU accesses one of these memory mapped addresses, the PI will perform a read or write on the bus. The mapped addresses are only those in the range   and   . Addresses outside of these ranges can only be accessed via DMA. Notice also that direct I/O accesses can only be done as 32-bit words (concatenating two consecutive 16-bit reads), see Memory map for more information.

NOTE: it is easy to get confused with the different kind of addresses. Addresses mentioned here are PI bus addresses, which is a 32-bit namespace by itself. Addresses in the CPU physical memory map are a different namespace. They can be confused because of the memory mapped addresses: accessing physical address 0x0700_0000 in the CPU does map exactly to PI address 0x0700_0000, but in general the two namespaces are technically separated. For instance, PI address 0x0000_1234 is a valid PI address on the bus where a device could be attached, but reading from physical address 0x0000_1234 on the CPU accesses RDRAM instead; in fact PI address 0x0000_1234 is not memory mapped, so the only way to access it is via DMA.

Domains
To cope with different peripherals, the PI allows to configure some parameters that affect the bus protocol:


 * PGS (page size). This is the size of a virtual page, and defines how often the PI must issue a new address during a DMA transfer. For instance, if the configure page size is 32 16-bit words, assuming an aligned transfer, the PI will issue an address at the start, and then read (or write) 32 consecutive words.
 * LAT (latency). Number of RCP clock cycles to wait between the address and the transfer of the first word
 * PWD
 * RLS

The PI stores two set of configurations for these 4 registers, and uses them for different ranges of the address space. These two sets are called "domain 1" and "domain 2". Most of the address space is accessed using the "domain 1" configuration, but a few ranges are accessed as "domain 2". See this table for the mapping: There is no way to have more than two domains, nor to decide which domain is used for some specific address. The above table is hardcoded in the PI itself, and cannot the changed. In general, software that needs to change domain parameters before accessing a device is advised to do that in a transactional way, so that the default values are restored after the access for other peripherals.

Open bus behavior
Writes made to addresses with no "receiver" devices cause no harm; the writes are just ignored. As explained above, the PI has absolutely no notion if devices are attached or not (and whether they care about some addresses) so all writes will always be performed as if somebody cared about them. In particular, notice also that PI will also execute writes to the ROM address space (as it has no notion that the ROM is read-only, nor that a ROM is mapped to those addresses!): the cartridge will then ignore those writes.

Reads made to addresses with no "receiver" devices cause an open-bus behavior: the 32-bit word returned by PI is the 16-bit lowest part of the address put on the bus, repeated in both halves. For instance, a direct I/O 32-bit read from PI address  will return the value. When reading unmapped areas via DMA, the rule is the same but the address returned is the address of the page being accessed (the only one physically put on the bus), and it is repeated for all words read until page change.

= Registers = Table Notation: R = Readable bit W = Writable bit U = Undefined/Unused bit -n = Default value n at power on [x:y] = Specifies bits x to y, inclusively

0x0460 0000 - PI_DRAM_ADDR

 * U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0


 * RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0
 * colspan="8" | DRAM_ADDR[23:16]
 * colspan="8" | DRAM_ADDR[23:16]


 * RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0
 * colspan="8" | DRAM_ADDR[15:8]
 * colspan="8" | DRAM_ADDR[15:8]


 * RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || R-0
 * colspan="8" | DRAM_ADDR[7:0]
 * colspan="8" | DRAM_ADDR[7:0]

Extra Details:
 * Note that DMA transfers are buggy if DRAM_ADDR[2:0] are not all zero, see below.

0x0460 0004 - PI_CART_ADDR

 * RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0
 * colspan="8" | CART_ADDR[31:24]
 * colspan="8" | CART_ADDR[31:24]


 * RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0
 * colspan="8" | CART_ADDR[23:16]
 * colspan="8" | CART_ADDR[23:16]


 * RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0
 * colspan="8" | CART_ADDR[15:8]
 * colspan="8" | CART_ADDR[15:8]


 * RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || R-0
 * colspan="8" | CART_ADDR[7:0]
 * colspan="8" | CART_ADDR[7:0]

0x0460 0008 - PI_RD_LEN

 * U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0


 * RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0
 * colspan="8" | RD_LEN[23:16]
 * colspan="8" | RD_LEN[23:16]


 * RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0
 * colspan="8" | RD_LEN[15:8]
 * colspan="8" | RD_LEN[15:8]


 * RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0
 * colspan="8" | RD_LEN[7:0]
 * colspan="8" | RD_LEN[7:0]

Extra Details:
 * Writing to this register will start the DMA transfer. Reading appears to always return `0x7F` (more research required).

0x0460 000C - PI_WR_LEN

 * U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0


 * RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0
 * colspan="8" | WR_LEN[23:16]
 * colspan="8" | WR_LEN[23:16]


 * RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0
 * colspan="8" | WR_LEN[15:8]
 * colspan="8" | WR_LEN[15:8]


 * RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0
 * colspan="8" | WR_LEN[7:0]
 * colspan="8" | WR_LEN[7:0]

Extra Details:
 * Writing to this register will start the DMA transfer. Reading appears to always return `0x7F` (more research required).

0x0460 0010 - PI_STATUS

 * U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0


 * U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0


 * U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0


 * U-0 || U-0 || U-0 || U-0 || R-0 || R-0 || RW-0 || RW-0
 * — || — || — || — || colspan="4"|Details below
 * — || — || — || — || colspan="4"|Details below

READ:                                WRITE: [3]   Interrupt (DMA completed)      [3]    - [2]   DMA error                      [2]    - [1]   I/O busy                       [1]    Clear Interrupt [0]   DMA is busy                    [0]    Reset DMA controller and stop any transfer being done

0x0460 00n4 - PI_BSD_DOMn_LAT

 * U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0


 * U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0


 * U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0


 * RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0
 * colspan="8" | LAT[7:0]
 * colspan="8" | LAT[7:0]

Extra Details:
 * During IPL2, the N64 will initialize Domain 1's LAT using data read from the cartridge ROM header. All official ROMs set LAT = 64 (meaning (64+1)*16 = 1040ns).

0x0460 00n8 - PI_BSD_DOMn_PWD

 * U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0


 * U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0


 * U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0


 * RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0 || RW-0
 * colspan="8" | PWD[7:0]
 * colspan="8" | PWD[7:0]

Extra Details:
 * During IPL2, the N64 will initialize Domain 1's PWD using data read from the cartridge ROM header. All official ROMs set PWD = 18 (meaning (18+1)*16 = 304ns).

0x0460 00nC - PI_BSD_DOMn_PGS

 * U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0


 * U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0


 * U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0


 * U-0 || U-0 || U-0 || U-0 || U-0 || RW-0 || RW-0 || RW-0
 * — || — || — || — || colspan="4" | PGS[3:0]
 * — || — || — || — || colspan="4" | PGS[3:0]

Extra Details:
 * During IPL2, the N64 will initialize Domain 1's PGS using data read from the cartridge ROM header. All official ROMs set PGS = 7 (meaning 2^(7+2) = 512 bytes).

Page Size only matters for DMA transfers; all direct accesses via the PI are only ever 32 bits wide.

The maximum number of transfers will only happen when the address's least significant bits are all 0.

0x0460 00n0 - PI_BSD_DOMn_RLS

 * U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0


 * U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0


 * U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || U-0


 * U-0 || U-0 || U-0 || U-0 || U-0 || U-0 || RW-0 || RW-0
 * — || — || — || — || — || — || colspan="2" | RLS[1:0]
 * — || — || — || — || — || — || colspan="2" | RLS[1:0]

Extra Details:
 * During IPL2, the N64 will initialize Domain 1's RLS using data read from the cartridge ROM header. All official ROMs set RLS = 3 (meaning (3+1)*16 = 64ns).

= Physical Bus Pinout = The PI Bus is a Bi-directional and multiplexed interface with a 16bit data path to the ROM, 64DD, Flash Ram and cart RAM chips. It is used to send both the wanted address and data to and from the RCP. This is not to be confused with the serial EEPROM, CIC and RTC (real time clock) chips that go through the SI interface and PIF chip via the cartridge port as well.

Constant Read:


=Aligned DMA Transfer= An aligned DMA transfer is when the PI_DRAM_ADDR_REG is set to a 64bit (8byte) aligned address. The PI_CART_ADDR_REG can be any 16bit (2Byte) value as will transfer from that offset to RDRAM.

The PI_RD_LEN_REG and PI_WR_LEN_REG can be any length, as long as it is a 2 byte aligned amount (more testing is to be done on this to confirm this)

= Unaligned DMA transfer = An un-aligned ROM dma transfer is when you use the PI_DRAM_ADDR_REG and not set it as a 8 Byte aligned address and use variable PI_RD_LEN_REG and PI_WR_LEN_REG lengths.

The following rules are based on assumptions via the created test ROMs by Krom, Mazamars312 and Lemmy (https://github.com/PeterLemon/N64/tree/master/CPUTest/DMAAlignment-PI-cart)