Copy Link
Add to Bookmark
Report

LaC's n64 hardware dox... v0.5 (public release)

Nintendo64's profile picture
Published in 
N64 various
 · 6 Oct 2019

LaC's n64 hardware dox... v0.5 (public release)

release notes


This little doc is my attempt to make the n64 coding scene a little better I hope. It is a compilation of stuff i've been working on for some months... a result of a lot of reversing of several games compiled with libultra, and some help from certain people. It is mainly for people who wish to code without using a developement library (like libultra). It is specifically for doing intros, trainers, etc... to attach to roms. It is very simplistic, and assumes you have some knowledge already. In other words this doc isn't really meant for people just starting. You should probably have read the libultra docs and be familiar with the n64 hardware... and the purpose of things like the AI,PI,SI,VI etc...
I suppose some emulation author could find use of this too.

Some people will probably be mad I released this doc, but I guess since nothing is really happening in the n64 scene it might get things moving? Especially from people that really want to hack. Also I have noticed some really crappy and inaccurate info being released which basically looks like stuff ripped from czn intros or something and contains no real knowledge. Yes all you freedom of information people... you want info... hack it yourself. Or read this doc, then hack some more ;)
Also I can't guarantee the 100% accuracy of any of this document. Also I liked to be greeted if you are using my dox or my source code in your work.

NOTE: SOME symbols in the follow text will reference symbols #define'd in RCP.H or r4300.h so make sure you look there when you are confused. Yes I know t hey are part of the standard devkit and some people don't have it... but i'm sure you can find these files if you really need to.

-VI (video interface)

Accessing the video on the n64 is very easy (like most things in this doc)

* initialization:
The video hardware is initialized by simply writing all the necessary values to the vi regs. I'm only going to discuss one mode here, but u can easily find the values for other modes by just printing out reg values to the screen after initing your favorite mode with libultra. You can also alter values of course to make your own modes. That I will not discuss here. The one i'm discussing is the simple 320x240 RGBA 16bit non-antialiasing mode.

The base address of the VI regs are mapped at 0xa4400000, so to simply write a value to a reg in r4300 asm would be like this:
;;this is just an example but it happens to be the write to the
;;VI_H_WIDTH_REG defined in RCP.H
lui at,0xa440 ;at=0xa4400000
li t0,0x140 ;t0=0x140
sw t0,0x8(at) ;write t0 to reg at $at+0x8

in C:

IO_WRITE(VI_H_WIDTH_REG,0x140); //IO_WRITE and IO_READ are in r4300.h

Ok to initialize this mode here are the values to write for each reg:

-> (VI_CONTROL_REG, 0x0000320e)
-> (VI_DRAM_ADDR_REG,0)
-> (VI_H_WIDTH_REG,0x140)
-> (VI_V_INTR_REG,0x2)
-> (VI_V_CURRENT_LINE_REG,0x0)
-> (VI_TIMING_REG,0x03e52239)
-> (VI_V_SYNC_REG,0x0000020d)
-> (VI_H_SYNC_REG,0x00000c15)
-> (VI_H_SYNC_LEAP_REG,0x0c150c15)
-> (VI_H_VIDEO_REG,0x006c02ec)
-> (VI_V_VIDEO_REG,0x002501ff)
-> (VI_V_BURST_REG,0x000e0204)
-> (VI_X_SCALE_REG,0x200);
-> (VI_Y_SCALE_REG,0x01000400)

All the values are pretty obvious and dont need much explaining. And with a little hacking you can come up with your own modes.

If you've managed to get this far you know that the video screen is just showing zebra stripes or some garbage. The reason for this is you have not set the frame buffer for this video mode. This is what osViSwapBuffer() is for (if you have used libultra). to set your frame buffer simply write the rdram address of the buffer to VI_DRAM_ADDR_REG or just change the init code to have it set it up.
Now you can simply write your graphics to the buffer and they'll be updated.

NOTE: There is ALOT of other things you can tweek, change, by messing with the VI regs... mess around... figure it out.
Also note that there can be cache problems with the video if you are using a virtual address like 0x80200000 in your VI_DRAM_ADDR_REG, try using the physical address 0xa0200000 instead or make sure you are writing back and invalidatiing the cache lines. if you are confused about physical and virtual addresses or how the cache works, read the r4300 man... and if you still don't understand it totally, join the club.

* end init

* vertical retrace wait:
pseudo code:
while ( VI_CURRENT_REG != 512 ) {wait} // I got this value from bpoint
* end retrace wait

-AI (audio interface)

The audio interface is probably the easiest thing to do.

* initialization:

None needed. Altho I suppose you can include setting the sound frequency and/or enabling the AI_CONTROL_REG as initialization (step #4 below)

* end init

* setting frequency:

  1. Set the dac rate write to AI_DACRATE_REG this value: (VI_NTSC_CLOCK/freq)-1 ^could be pal

  2. Set the bitrate (4bit, 8bit, or 16bit) write to the AI_BITRATE_REG the value: bitrate-1

* end frequency

* sending sound buffer

  1. Before sending a buffer, its a good idea to make sure one isnt already being played. Simply read AI_STATUS_REG then AND it with AI_STATUS_FIFO_FULL. if the result is true... then wait.
  2. Write the 64bit aligned address of the sound buffer to AI_DRAM_ADDR_REG
  3. Write the length of the buffer be played to AI_LEN_REG NOTE: this length must be multiple of 8, no larger than 262144 bytes.
  4. Write AI_CONTROL_DMA_ON to the AI_CONTROL_REG (only needed once)

* end sound buffer

NOTE: If you have read the libultra manuals you will notice when it discusses the AI routines (osAi.man) it says the AI regs are double buffered. This is important to realize that you can send one buffer while another is playing. Also note that the AI_LEN_REG counts down to 0 as the current buffer is being played. This can be useful to tell how much of the buffer is left.

-PI (peripheral interface)

* init pif: ( you should do this before you do anything! )

  1. Simply write 0x8 to PIF_RAM_START+0x3c

* end init pif

* PI DMA transfer

  1. Wait for previous dma transfer to finish (see next explanation)

  2. Write the physical dram address of the dma transfer to PI_DRAM_ADDR_REG NOTE: To convert from a virtual address to physical, simply AND the address with 0x1fffffff.

  3. Write the physical cart address to PI_CART_ADDR_REG.

  4. Write the length-1 of the dma transfer to PI_WR_LEN_REG this is from cart->rdram change this to RD ^ for the other way also note you must write a 0x2 to PI_STATUS_REG in order to write to the cart space (0xb0000000)

NOTE: The cart addr must be 2 byte aligned, and the rdram addres 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.

* end PI DMA transfer

* PI DMA wait

  1. Read PI_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 PI_STATUS_REG and the PI in general.


* end PI DMA wait

-reading and writing the new sram chip (DS1) -

Sram is mapped at 0xa8000000.
The trick is that you cannot write to it directly, you must us the PI.
Actually it is possible to write to it directly, but I dont know how because
it needs to be timed carefully.
Its a little tricky which requires writing some values into some PI regs
to initialize the PI correctly for the type of transfer protocol the sram
needs for successful data transfer.

* Init the PI for sram

  1. write 0x05 to the PI_BSD_DOM2_LAT_REG.

  2. write 0x0c to the PI_BSD_DOM2_PWD_REG.

  3. write 0x0d to the PI_BSD_DOM2_PGS_REG.

  4. write 0x02 to the PI_BSD_DOM2_RLS_REG.

* End init PI for sram

Now you should be able to use the PI to transfer between rdram and sram.
(refer to the dox above concerning PI, but replace the ROM address with
sram address 0xa8000000).


-SI (serial interface)

The SI is very similar to the PI for obvious reasons. It is used mainly for
accessing the joyport and pifram... which will be dicussed in the next
section.

* SI DMA transfer
1. Wait for previous dma transfer to finish (see next explanation)

2. Write the physical dram address of the dma transfer to SI_DRAM_ADDR_REG

3. 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 addres 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.

* end SI DMA tranfer

* SI DMA wait

1. 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.

* end SI DMA wait

-PIF Usage- (controller reading/detection)

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. In this version of the
document I will only cover reading joysticks and detection.

Below is a very brief and not so detailed view of pif command structure
and an example of using them to perform some operations.

first this is how pif ram is setup:

[64byte block] at 0xbfc007c0 (1fc007c0)
{ command data recv
channel 1 - 00 00 00 00 : 00 00 00 00 - 8 bytes
channel 2 - 00 00 00 00 : 00 00 00 00 - 8 bytes
channel 3 - 00 00 00 00 : 00 00 00 00 - 8 bytes
channel 4 - 00 00 00 00 : 00 00 00 00 - 8 bytes
channel 5 - 00 00 00 00 : 00 00 00 00 - 8 bytes
00 00 00 00 : 00 00 00 00 - 8 bytes (dummy data)
00 00 00 00 : 00 00 00 00 - 8 bytes (dummy data)
00 00 00 00 : 00 00 00 00 - 8 bytes (dummy data)
} ^^pif status control byte

This is how you should visualize it for operations I describe below. For
other stuff it is setup differently... but in all cases it is just
64 bytes.

Each channel can contain a command in the first 4 bytes (the left column).

Each command has a structure like so:

byte 1 = 0xff for new command | 0xfe for end of commands
byte 2 = number of bytes to send
byte 3 = number of bytes to recieve
byte 4 = Command Type

Command Types:

00 = get status
01 = read button values
02 = read from mempack
03 = write to mempack
ff = reset controller
04 = read eeprom
05 = write eeprom

Here is an example on how to build a command for reading a joystick:

* Init the joysticks for reading

send the pif command block to pif_ram 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 ff010401 - ffffffff || joy1 ff010401 - ffffffff
joy2 ff010401 - ffffffff || 00000000 - ffffffff
joy3 ff010401 - ffffffff || 00000000 - ffffffff
joy4 ff010401 - ffffffff || 00000000 - ffffffff
fe000000 - 00000000 || fe000000 - 00000000
00000000 - 00000000 || 00000000 - 00000000
00000000 - 00000000 || 00000000 - 00000000
00000000 - 00000001 || 00000000 - 00000001
} || }
----------------------------++------------------------------
after sending this the joystick values will now be updated in pif RAM
NOTE: make sure you put the ffffffff in the data column, otherwise it
will cause errors.

ff010401 is the command that reads the joystick values.
|
ff is basically a flag for a new command.
01 says we are going to send 1 byte (the command type).
04 says we are going to read 4 bytes (into the data column)
01 is the command type (read button values).

You will notice the 5th channel command is fe, this command signals
the end of the command block. The 00000001 tells the pif there is a new
command block to be processed. Without this the command block will not
be executed.

* end Init joysticks


* 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 command column
that the error occured in. For instance... lets say you did the example
above and you tried to read controller values. Well if you tried to
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 upper 4 bits of the 3rd byte in the command column.

The Error values are as follows:

0 - no error, operation successful.
8 - error, device not present for specified command.
4 - error, unable to send/recieve the number bytes for command type.


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:

-----------------------------------+
[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)

-----------------------------------+
[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

ff010300 is the command used to get the controller status.
|
ff is basically a flag for a new command.
01 says we are going to send 1 byte (the command type).
03 says we are going to read 3 bytes (into the data column)
00 is the command type (get controller status).

Here is and 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.

-----------------------------------+
[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 dunno.
The 3rd byte is useful. Its for detecting if there is something plugged
into the mempack slot on the controller.
1 = pack present
2 = nothing plugged in

* end Getting controller status

* reading/writing cart eeprom

COMING NEXT RELEASE

* end reading/writing cart eeprom

* reading/writing mempack eeprom

COMING NEXT RELEASE

* end reading/writing mempack eeprom


Future


I know this document isnt much as it stands but I plan on adding some rsp info into it and of course any other info I currently haven't included as time permits.
Also all my source code for the stuff in this doc might get released. but right now everything is meant to build with SN's assembler and linker. I wish to recode it so it compiles with a freeware assembler... so once I do that I will release source... or maybe before ;)

greets to people who helped me with some stuff!

nagra, bpoint, hartec, jovis, wild_fire, datawiz


Questions & Comments: about anything except where to get a devkit or libultra or roms or header file or whatever. In other words if you have a question about stuff in this doc and you are fairly intelligent, or you have a question about how to implement things in your n64 program or emulator...

contact LaC on IRC efnet in #n64dev or if you must:

EMAIL: LaC@dextrose.com

-EOF-

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT