SI Registers Detailed
Most taken from Lac's hw-dox.
SI (serial interface)
The SI is very similar to the PI for obvious reasons. It is used mainly for accessing the pifram... which will be dicussed in the next section.
SI DMA transfer
- Wait for previous dma transfer to finish (see next explanation)
- Write the physical dram address of the dma transfer to SI_DRAM_ADDR_REG
- Write PIF_RAM_START to the SI_PIF_ADDR_RD64B_REG or the SI_PIF_ADDR_WR64B_REG, depending on what you wish to do (read or write). This will cause a 64B read or write between pif_ram and rdram.
NOTE: The SI addr must be 2 byte aligned, and the rdram address must 8-byte aligned. Once again make sure you write back the cache lines and invalidate the cache lines if needed, or you will run into trouble.
SI DMA wait
- Read SI_STATUS_REG then AND it with 0x3, if its true... then wait until it is not true.
NOTE: Look at RCP.H for more information on the SI_STATUS_REG and the SI in general.
PIF Usage
- New in v0.7*
In previous versions of this document I regret to say that when I explained how the pif command structure works I really goofed up. A lot of the information was based on speculation and was not tested enough to be proven. In this version I explain a totally different understanding of how the pif command processing is done by the pif chip. So if you are going off the information from previous dox, I seriously suggest you read this section again. It is much more detailed and logical now.
If you have done research and peeked at the RCP.h file you should already know some things about the pif. The SI is used to send commands to the pif ram that tell the pif what to do. The SI is also used to read the results of those commands back. You can tell the pif to do alot of stuff. for instance... reading joysticks, reading mempacks, detecting joysticks, detecting mempacks, activating the rumblepack, detecting the rumble pack, reading cartridge eeprom... etc.
Below is a detailed view of pif command structure processing and an example of using them to perform some operations.
At First, this is how pif ram should be visualized: |{Diagram 1.0}| [64byte block] at 0xbfc007c0 (1fc007c0) { 00 00 00 00 : 00 00 00 00 - 8 bytes 00 00 00 00 : 00 00 00 00 - 8 bytes 00 00 00 00 : 00 00 00 00 - 8 bytes 00 00 00 00 : 00 00 00 00 - 8 bytes 00 00 00 00 : 00 00 00 00 - 8 bytes 00 00 00 00 : 00 00 00 00 - 8 bytes 00 00 00 00 : 00 00 00 00 - 8 bytes 00 00 00 00 : 00 00 00 00 - 8 bytes } ^^pif status/control byte Commands are processed from any byte in pifram. ie: The pif chip steps thru each byte to load commands. Each command has a structure like so: byte 1 (t) = x number of bytes to send to pif chip byte 2 (r) = x number of bytes to recieve from pif chip byte 3 (c) = Command type
Command Processing
The pif chip constantly looks at the last byte of pifram and uses it sort of as a semaphore to decide what to do with pifram. This last byte I will refer to as the status byte or the control byte. If the pif chip sees the control byte has been set to 01 (bit 0) it will know there are commands waiting to be processed in pifram. After it has decided this, it goes through each byte of pifram in a scanning process in order to execute commands. The Scanning process is described below:
while(not end of pifram) t = get byte if t >= 0 then r get byte send next t number of bytes to pif chip do pif chip command execution if executed then get r bytes from pif chip end if increment channel else if t==fe then exit while loop end if end while
The t value is very important to the processing. If it is a negative value then it does basically nothing but read the next byte into t again.If it is a positive value or zero we know it needs to do something. It now gets the next byte from pifram and puts it into r. r is used later to tell how much data to get back from pifram. Next it sends all the bytes needed for the command to the pif chip... t being the x number of bytes to send. The pif chip now has all the data it needs to _attempt_ to execute a command. If we sent 0 bytes to the pif chip it will not execute a command and we will receive no bytes from the pif chip in r. But the channel counter (I will explain channels later) will increment even if no command was really executed. A t value of 0 is basically a null command. When the pif chip executes certain commands it expects certain limits on values for t and r. Usually these values are fixed. If the values are not what it expects it will attempt to process the command. If it could not process the command it will let you know by setting the r value in pifram with an error value I will describe later. It is a good idea to realize that bits 6 and 7 of the r value are not to be used by the pif command. These are actually the error bits. ie: MASK r &= ~0xC0
|{Diagram 1.0b}| Command Types: | Command | Description |t |r | +---------+--------------------------+-----+ | 00 | request info (status) |01|03| | 01 | read button values |01|04| | 02 | read from mempack slot |03|21| | 03 | write to mempack slot |23|01| | 04 | read eeprom |02|08| | 05 | write eeprom |10|01| | ff | reset |01|03| NOTE: values are in hex ==Channels== The pif commands operate on certain channels in the n64. The channels can also be refered to as ports. The n64 has 8(?) channels (1-5), though 6, 7, 8 aren't normally used (reserved for RTC?). The first 4 channels (channels 0-3) are the joystick ports. The last two channels (4 and 5) I am pretty sure are in the cartridge port. Carts that have eeprom have it at either channel 4 or 5 or both. There are some carts that have two eeprom chips in them, and I believe channel 5 is used for the 2nd eeprom in these carts... Although I have not tested this theory. To get a better understanding of how all this works, here is an example on how to build a command for reading a joystick: * Init the joysticks for reading |{Diagram 1.1}| Send the pif command block to pifram using the SI DMA -----------------------------++------------------------------ such a block to read 4 joys: || such a block to read 1 joy: [64byte block] || [64byte block] { command data || { joy1 ff 010401 - ffffffff || joy1 ff 010401 - ffffffff joy2 ff 010401 - ffffffff || ff ffffff - ffffffff joy3 ff 010401 - ffffffff || ff ffffff - ffffffff joy4 ff 010401 - ffffffff || ff ffffff - ffffffff fe 000000 - 00000000 || fe 000000 - 00000000 00 000000 - 00000000 || 00 000000 - 00000000 00 000000 - 00000000 || 00 000000 - 00000000 00 000000 - 00000001 || 00 000000 - 00000001 } || } -----------------------------++------------------------------ After sending this the joystick values will now be updated in pif RAM NOTE: the ffffffff is put into the data column just for filling the space that the pif chip will write the data to. You can put anything there because the pifchip will never try to process it, because it skips past all bytes it writes the r bytes to. 0xff | 0xff is padding so that the joystick values end up in the 2nd column, this is done because it is convenient to align the data when reading the pifram back to rdram. It is not necessary to align it, so this ff is not really needed. IE: 010401ff would work just fine, but the joystick values would start at the 4th byte rather than the 5th. 0x010401 is the command that reads the joystick values. | t 0x01 says we are going to send 1 byte (the command type). r 0x04 says we are going to read 4 bytes (into the data column) c 0x01 is the command type (read button values). Here is the step by step process in which the pif chip processes this command block: NOTE: channel always starts out at 0. 1. reads in ff into t tx < 0 so it does nothing 2. reads in 01 into t 3. reads in 04 into r 4. send tx number of bytes to pif chip (just send the command 01) 5. pif chip executes the command sucessfully and sends no error bytes 6. pif chip sends r bytes back to pifram 7. pif chip increments the channel. 8. pif chip now skips over the r bytes it wrote and then basically returns to step 1 9. once it hits fe or is at the end of pifram, it sets the control byte to 0 and exits. * end Init joysticks NICE INFO: The 0x01 (diagram 1.1) tells the pif there is a new command block to be processed. Without this the command block will not be executed. You will notice that the reason for the one being there is actually that it is being written to the pif's status control. This may not be actually what it is called but it seems to serve a similar function. This is the same byte you are writing to when you initialize the pif (see: * init pif). You will also notice that this byte will be set to 0x80 after holding down the reset button. 0x80 means the pif is busy. The value will be set to 0x00 after you let go of the reset button or .5 seconds passes (whichever comes first). After this .5 seconds a NMI will be generated which will reset the r4300 and the n64. Also note that this byte is set to 0x00 once a command has been executed by the pif. This is why when you read the pif ram after sending a command the last byte is no longer 1, but 0. If it is still 1 after reading it back, then you know your command didnt execute. * Read Joysticks The joy values can be read from the spaces marked by 0xFFFFFFFF in the block above. Of course you must first DMA from pif ram back to rdram. Or you can just read the data directly by making a pointer to 0xbfc007c0 (start of the pif_ram), although I would not recommend that method. Here would be a sufficient C code to read in a controller's values: void siReadJoy(int cont,OSContPad *p) { unsigned char pif_block[64]; si_DMA_from_pif (pif_block); memcpy (p,pif_block+((cont*8)+4),4); } The OSContPad structure is in the libultra header file OS.H * end Read Joysticks * Detecting if Joysticks are connected This is very easy and can be done after you send any command to read or write something to the controllers. Whenever you try and execute a command on a channel and that device on the channel (like a joystick) is not present the pif will write an error value to the r byte of the command that the error occured in. For instance... lets say you did the example above and you tried to read controller values. Well if you read the controller values for all four joystick channels you will notice that if you don't have a joystick physically plugged in to the port(s) you are reading from, then no values will appear. Well I think this is an obvious result. But also notice that the pif will put an error value into the r byte of the command. The Error values are as follows: 0x00 - no error, operation successful. 0x80 - error, device not present for specified command. 0x40 - error, unable to send/recieve the number bytes for command type. ie: bits 6 and 7 of the r value are never used in commands. MASK: 0xC0 This would be an example of the result of trying to read 4 controllers (like in above example) and only a joystick in port 3 is connected: |{Diagram 1.2}| -----------------------------------+ [64byte block] read from pif ram | { command data | joy1 ff018401 - ffffffff <--- 8 is the error code for device joy2 ff018401 - ffffffff | not present. joy3 ff010401 - 00000000 <--- read was successful on this joy4 ff018401 - ffffffff | channel, no buttons being pressed fe000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | } | -----------------------------------+ This would be an example of the result of trying to read 5 bytes for the read joystick command: (all 4 joysticks are connected) |{Diagram 1.3}| -----------------------------------+ [64byte block] sent to pif ram | { command data | joy1 ff010501 - ffffffff <--- joy2 ff010501 - ffffffff <--- note we tried to read 5 instead joy3 ff010501 - ffffffff <--- of 4. The device only allows you joy4 ff010501 - ffffffff <--- to read 4 bytes with that command fe000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000001 | } | -----------------------------------+ -----------------------------------+ [64byte block] read from pif ram | { command data | joy1 ff014501 - 00000000 <--- (note that no buttons are being joy2 ff014501 - 00000000 <--- pressed on any controller) joy3 ff014501 - 00000000 <--- notice the 4. It is the error joy4 ff014501 - 00000000 <--- code for send/recieve. fe000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | } | -----------------------------------+ NOTE: Even though we tried to read an extra byte for the buttons values the button values will still appear... but the error code will still be generated because there is only 4 bytes to be read, not 5. * end Detecting if Joysticks are connected * Getting controller status 0x010300 is the command used to get the controller status. | 0x01 says we are going to send 1 byte (the command type). 0x03 says we are going to read 3 bytes (into the data column) 0x00 is the command type (get controller status). Here is an example of reading the status from 4 controllers. Only the first two controllers are actually plugged in. There is a pack in the 1st controller and there is no pack in the second controller. |{Diagram 1.4}| -----------------------------------+ [64byte block] sent to pif ram | { command data | joy1 ff010300 - ffffffff | joy2 ff010300 - ffffffff | joy3 ff010300 - ffffffff | joy4 ff010300 - ffffffff | fe000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000001 | } | -----------------------------------+ -----------------------------------+ [64byte block] read from pif ram | { command data | joy1 ff010300 - 050001ff <--- notice only 3 bytes were read joy2 ff010300 - 050002ff <--- that is why the last byte is joy3 ff018300 - ffffffff | still ff joy4 ff018300 - ffffffff | fe000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | } | -----------------------------------+ The first two bytes in the data column is the controller type. I'm not exactly sure what use this is... do steering wheels have a different controller type? I don't know. The 3rd byte is useful. Its for detecting if there is something plugged into the mempack slot on the controller. 1 = something is plugged into the mempack slot 2 = nothing plugged in 4 = pad address crc from controller slot read/write (mempack slot) * end Getting controller status * Resetting the controllers WARNING: Some of this info could be wrong, I have yet to test fully. This command is performed by the osContReset() function in libultra. 0x0103ff is the command for resetting | t 0x01 says we are going to send 1 byte (the command type). r 0x03 says we are going to read 3 bytes (into the data column) c 0xff is the command type (reset). As stated in the osContReset() man page... this command will reset all the joysticks and return the values to a neutral position. I know this is needed for calibration. For instance, if you turn on the n64 with the analog stick held in an off-center position then the values will be off-center until the controller is reset. What is also nice about this command is that it can be used to not only reset the controllers but grab the status of the controllers at the same time. This is why we are reading three bytes (just like in the example above). I am not sure what happens when you try this command on channels >= 4. See Diagram 1.4 * End Resetting the controllers * reading/writing cart eeprom The cart eeprom is better know as the cart sram... its the little eeprom chip that is in alot of the 1st generation cartridges and some of the newer cartridges. Although people call it sram, it really isn't. Its eeprom, which is accessed much differently that sram. Reading and writing sram is described in the PI section of this document. Eeprom is also what is in the mempacks, which is described in the next section. Cart eeprom is written to using pif commands. The way commands and data are formatted in pifram is no different than the way that they are done for accessing the controller ports. The first 4 bytes are zero because this is not a controller command and we will not be accessing the 4 joystick channels. IE: when the pif chip processes a command block, if it reads a 0 in t then it knows it will be executing a NOP command and incrementing the channel counter. -Probing the eeprom- The first thing you must do is test to see if the eeprom is there. Normally you could test if the eeprom is there by just attempting to write some bytes to it and reading them back. Of course we do not want to do this because eeprom contains important save-game data we would not like to destroy just for a simple test. So we will have to "probe" the eeprom to detect if its there. You do this in a similar way that you get the status of the controllers. Here is an example pif block to send to the pif ram to probe for eeprom: |{Diagram 1.5}| The cartridge is assumed to have 512 bytes of eeprom in it. ----------------------------------+ [64byte block] sent to pif ram | { | 00000000 - ff010300 <-- this command should look familiar. ffffffff - fe000000 | notice when sending any command 00000000 - 00000000 | for eeprom we should have made 00000000 - 00000000 | sure 4 other channels have been 00000000 - 00000000 | processed. 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000001 | } | ----------------------------------+ ----------------------------------+ [64byte block] read from pif ram | { | 00000000 - ff010300 | 008000ff - fe000000 <-- this result shows that there is 00000000 - 00000000 | an eeprom present. 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | } | ----------------------------------+ The 3 byte status is read into the left column (3rd word). the 0x8000 from that read tells us that there is eeprom there. If the cartridge did not have any eeprom in it the buffer read from pif ram would of looked like: |{Diagram 1.6}| ----------------------------------+ [64byte block] read from pif ram | { | 00000000 - ff018300 <-- tells us there was no eeprom there ffffffff - fe000000 | to probe. (error bits) 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | } | ----------------------------------+ -Writing cart eeprom- The command for writing the cart eeprom is 0x0a0105xx | t 0x0a says we are going to send 10 bytes to the pif (command+offset+data) r 0x01 says we are going to get 1 byte (the error byte) c 0x05 is the command type (write eeprom) xx is the offset to write to ^^ this value can differ depending on the size of the eeprom. The offset is the number of the 8-byte block to write to. ie: if you have 512 bytes of eeprom in the cart, then this value can be anywhere between 0x0 and 0x40. |{Diagram 1.7}| The cartridge is assumed to have 512 bytes of eeprom in it. ----------------------------------+ [64byte block] sent to pif ram | { | 00000000 - 0a010521 <-- this command will write the next deadbeef - a5b6c7d8 | 8 bytes to block 0x21 in eeprom. ffffffff - fe000000 | 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000001 | } | ----------------------------------+ ----------------------------------+ [64byte block] read from pif ram | { | 00000000 - 0a010521 | deadbeef - a5b6c7d8 | 00ffffff - fe000000 <-- the 00 written notifies that the 00000000 - 00000000 | eeprom was written to. This value 00000000 - 00000000 | is that 1 extra error byte that 00000000 - 00000000 | the command specifies. 00000000 - 00000000 | 00000000 - 00000000 | } | ----------------------------------+ -Reading cart eeprom- The command for reading the cart eeprom is 0x020804xx | t 0x02 says we are going to send 2 bytes to the pif (command+offset) r 0x08 says we are going to get 8 bytes (1 block read from eeprom) c 0x04 is the command type (read eeprom) xx is the offset to read from ^^ this value can differ depending on the size of the eeprom. The offset is the number of the 8-byte block to read from. ie: if you have 512 bytes of eeprom in the cart, then this value can be anywhere between 0x0 and 0x40. |{Diagram 1.8}| The cartridge is assumed to have 512 bytes of eeprom in it. ----------------------------------+ [64byte block] sent to pif ram | { | 00000000 - 02080409 <-- this command will read 0x08 bytes ffffffff - fe000000 | from block 0x09 in eeprom. 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000001 | } | ----------------------------------+ ----------------------------------+ [64byte block] read from pif ram | { | 00000000 - 02080409 | deadbeef - a5b6c7d8 <-- the eight bytes read are stored 00000000 - 00000000 | here. 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | 00000000 - 00000000 | } | ----------------------------------+ * end reading/writing cart eeprom * reading/writing mempack port Important NOTE: As far as I know... mempack eeprom can be only 32k in size. Every address beyond 32768 is out of the mempack address range and is most likely data not meant for mempack eeprom... but most likely meant for the Rumblepack or some other peripheral that is connected to the controller. The command for reading from the mempack is 0x032102 xxxx | t 0x03 says we are going to send 3 bytes to the pif (command+16bit_offset) NOTE: the offset also contains a 5-bit address CRC. The offset must be aligned on a 32 byte boundry, so the address crc is in the low 5 bits. Also the address must be shifted right 5 bits. The address is really similar to the cart eeprom address in that it is an address to a block, but in this case it is a 32 byte block. r 0x21 says we are going to get 33 bytes, that is one 32-byte-block read from the mempack slot + 1 byte for the data CRC (described later) c 0x02 is the command type (read mempack slot) xxxx is the offset to read from The command for write to the mempack is 230103xxxx | t 0x23 says we are going to send 35 bytes to the pif (command + 2-bytes-offset + data) NOTE: the offset also contains a 5-bit address CRC. The offset must be aligned on a 32 byte boundry, so the address crc is in the low 5 bits. Also the address must be shifted right 5 bits. The address is really similar to the cart eeprom address in that it is an address to a block, but in this case it is a 32 byte block. r 0x01 says we are going to get 1 byte for the data CRC (described later) c 0x03 is the command type (read mempack slot) xxxx is the offset to read from * end reading/writing mempack port * mempack port checksum Whenever you write / read data you must include an address and data CRC. These are stored in two different locations in the command structure (see the two commands above). These CRC algorithms are described below. NOTE: this is the data CRC when something is plugged into the mempack port. The data you get read or write to the mempack port obvious will need or have a data CRC... but when something is not plugged into the mempack port, then the CRC is calculated the same way except the final 8-bit result is NOT'd. You might wonder why there would even be a CRC when reading from the mempack port and nothing is plugged in. Well basically when nothing is plugged in, data is still read, but it is not valid data. This data is usually all 0's. The data CRC algo is still calculated but the result is of course NOT'd to let you know that the data is erronous. *******Data CRC routine (written in Pascal for pseudo-code purposes) ******** function ContDataCrc (data: PByteArray): byte; { PByteArray is a byte pointer } var temp,temp2 : byte; i,j : Integer; begin temp:=0; for i := 0 to 32 do begin for j := 7 downto 0 do begin if ((temp and $80)<>0) then temp2 := $85 else temp2 := $00; temp := temp shl 1; if (i = 32) then begin temp := temp or 0; end else begin if (((data[i]) and ($01 shl j))<>0) then temp:=(temp or 1) else temp:=(temp or 0); end; temp := (temp xor temp2); end; end; ContDataCrc:=temp; end; Address CRC routine: *******Addr CRC routine (written in Pascal for pseudo-code purposes) ******** function PackAddrCRC (int16 addr): byte; var t,t2 : byte; i,j : Integer; begin t:=0; for i := 0 to 15 do begin if ((t and $10)<>0) then t2 := $15 else t2 := $00; t := t shl 1; if ((addr and $400)<>0) then t := (t or $1) addr := addr shl 1; t := t xor t2; end PackAddrCRC:=(t and $1f); end; * end mempack port checksum * Rumblepak The Rumblepack, like the Mempack connects to the mempack port on the bottom off the n64 controller. Therefore you read/write to the mempack port in order to gain access to the rumble pack. -Checking for connection- In order to check if what is plugged into the mempack port is a rumble pack or a mempack, you need to do the following: 1. Use the controller command to get the status (see Diagram 1.4) This way you will at least know something is plugged into the port. Or you could just skip to step two and if the data crc you get back is NOT'd you will know nothing is plugged in. 2. Using the mempack read command, read offset block 0x400. This offset would be written as 0x8001 in the actual command. Because, remember you are storing the address crc in the lower 5 bits. 3. If the data you get is all 0x80's then you know a Rumblepack is there. If the data is 0x00's then its not a Rumblepack and is most likely a Mempack. -Rumbling- The Rumblepack rumbles based on two values... OFF and ON. There is no intensity values involved in rumbling. The whole idea is that the slower you turn it off and on, the higher the intensity rumble and vice versa. To turn the Rumblepack on you simply write a 32 byte block of 01's to offset 0x600. And to turn it off you write 00's instead. Its as simple as that... But remember you are using the standard mempack port writing commands so all the rules apply (CRC'ing etc...). * end Rumblepak